summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/core/OWNERS2
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java6
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java70
-rw-r--r--core/api/test-current.txt6
-rw-r--r--core/java/android/animation/Animator.java4
-rw-r--r--core/java/android/animation/AnimatorSet.java92
-rw-r--r--core/java/android/animation/ValueAnimator.java28
-rw-r--r--core/java/android/app/Activity.java10
-rw-r--r--core/java/android/app/ActivityManager.java45
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java7
-rw-r--r--core/java/android/content/pm/UserProperties.java2
-rw-r--r--core/java/android/hardware/SensorAdditionalInfo.java2
-rw-r--r--core/java/android/hardware/SensorManager.java14
-rw-r--r--core/java/android/hardware/SystemSensorManager.java8
-rw-r--r--core/java/android/hardware/display/DisplayManager.java4
-rw-r--r--core/java/android/hardware/display/DisplayManagerInternal.java28
-rw-r--r--core/java/android/os/BatteryManager.java2
-rw-r--r--core/java/android/os/UserManager.java8
-rw-r--r--core/java/android/os/image/DynamicSystemClient.java7
-rw-r--r--core/java/android/service/contentcapture/ContentCaptureServiceInfo.java2
-rw-r--r--core/java/android/text/util/Linkify.java12
-rw-r--r--core/java/android/view/HapticFeedbackConstants.java14
-rw-r--r--core/java/android/view/HapticScrollFeedbackProvider.java141
-rw-r--r--core/java/android/view/IWindowManager.aidl13
-rw-r--r--core/java/android/view/InsetsController.java16
-rw-r--r--core/java/android/view/InsetsFrameProvider.java13
-rw-r--r--core/java/android/view/ScrollFeedbackProvider.java63
-rw-r--r--core/java/android/view/View.java27
-rw-r--r--core/java/android/view/ViewRootImpl.java9
-rw-r--r--core/java/android/view/Window.java9
-rw-r--r--core/java/android/view/WindowManager.java57
-rw-r--r--core/java/android/view/WindowManagerImpl.java26
-rw-r--r--core/java/android/view/accessibility/AccessibilityNodeInfo.java16
-rw-r--r--core/java/android/view/autofill/AutofillFeatureFlags.java6
-rw-r--r--core/java/android/view/contentcapture/ContentCaptureManager.java28
-rw-r--r--core/java/android/view/contentcapture/IContentCaptureManager.aidl6
-rw-r--r--core/java/android/view/contentprotection/ContentProtectionEventProcessor.java228
-rw-r--r--core/java/android/view/textclassifier/TextClassification.java4
-rw-r--r--core/java/android/webkit/OWNERS3
-rw-r--r--core/java/android/widget/RemoteViews.java11
-rw-r--r--core/java/android/widget/Toast.java9
-rw-r--r--core/java/android/window/TransitionInfo.java11
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java40
-rw-r--r--core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java4
-rw-r--r--core/jni/android_view_InputEventReceiver.cpp14
-rw-r--r--core/jni/android_view_InputQueue.cpp2
-rw-r--r--core/jni/android_view_KeyCharacterMap.cpp4
-rw-r--r--core/jni/com_android_internal_content_NativeLibraryHelper.cpp7
-rw-r--r--core/res/res/layout/alert_dialog_button_bar_leanback.xml4
-rw-r--r--core/res/res/values/config.xml53
-rw-r--r--core/res/res/values/symbols.xml6
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java37
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityManagerTest.java6
-rw-r--r--core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java389
-rw-r--r--core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java89
-rw-r--r--core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java516
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java39
-rw-r--r--data/etc/com.android.networkstack.xml1
-rw-r--r--data/etc/services.core.protolog.json6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt24
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java121
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipResizeGestureHandler.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java15
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java19
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java47
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java6
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt8
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt31
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt64
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt56
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt9
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt10
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt25
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt13
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt28
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt14
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt40
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt50
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt52
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt13
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt32
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipResizeGestureHandlerTest.java9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java5
-rw-r--r--native/webview/OWNERS4
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml2
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt4
-rw-r--r--packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt3
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java9
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java9
-rw-r--r--packages/PackageInstaller/res/values/strings.xml6
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java5
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java7
-rw-r--r--packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java6
-rw-r--r--packages/SettingsLib/Android.bp3
-rw-r--r--packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt11
-rw-r--r--packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt34
-rw-r--r--packages/SettingsLib/res/values/strings.xml17
-rw-r--r--packages/SettingsLib/search/Android.bp16
-rw-r--r--packages/SettingsLib/search/common.mk10
-rw-r--r--packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java (renamed from packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java)12
-rw-r--r--packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java39
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java8
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java17
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java249
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java3
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java187
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java23
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java13
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java92
-rw-r--r--packages/SystemUI/AndroidManifest.xml3
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt12
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt23
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt16
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt44
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt4
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt4
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java3
-rw-r--r--packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml20
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_contents.xml8
-rw-r--r--packages/SystemUI/res/layout/auth_biometric_icon.xml26
-rw-r--r--packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json1
-rw-r--r--packages/SystemUI/res/values/strings.xml10
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt16
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/DejankUtils.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/SwipeHelper.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIService.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt47
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt312
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt212
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt62
-rw-r--r--packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java62
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java105
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java113
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeController.java37
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java88
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java57
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java52
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt181
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt78
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java41
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java131
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt224
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt319
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt48
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java164
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt283
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt107
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt30
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt297
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java82
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt9
-rw-r--r--services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java11
-rw-r--r--services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java79
-rw-r--r--services/autofill/java/com/android/server/autofill/Session.java355
-rw-r--r--services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java193
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java4
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java2
-rw-r--r--services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java5
-rw-r--r--services/core/java/com/android/server/BootReceiver.java5
-rw-r--r--services/core/java/com/android/server/am/ActiveServices.java15
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerConstants.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java7
-rw-r--r--services/core/java/com/android/server/am/BroadcastProcessQueue.java6
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueImpl.java2
-rw-r--r--services/core/java/com/android/server/am/BroadcastQueueModernImpl.java11
-rw-r--r--services/core/java/com/android/server/am/DropboxRateLimiter.java58
-rw-r--r--services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java21
-rw-r--r--services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java4
-rw-r--r--services/core/java/com/android/server/am/SettingsToPropertiesMapper.java7
-rw-r--r--services/core/java/com/android/server/am/UidObserverController.java32
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceBroker.java4
-rw-r--r--services/core/java/com/android/server/audio/AudioDeviceInventory.java2
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java19
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java16
-rw-r--r--services/core/java/com/android/server/display/LocalDisplayAdapter.java12
-rw-r--r--services/core/java/com/android/server/hdmi/HdmiControlService.java4
-rw-r--r--services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java431
-rw-r--r--services/core/java/com/android/server/input/InputFeatureFlagProvider.java19
-rw-r--r--services/core/java/com/android/server/input/KeyboardBacklightController.java150
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodMenuController.java13
-rw-r--r--[-rwxr-xr-x]services/core/java/com/android/server/notification/NotificationManagerService.java72
-rw-r--r--services/core/java/com/android/server/os/NativeTombstoneManager.java2
-rw-r--r--services/core/java/com/android/server/pm/DumpHelper.java9
-rw-r--r--services/core/java/com/android/server/pm/InstallPackageHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/InstallRequest.java6
-rw-r--r--services/core/java/com/android/server/pm/PackageInstallerService.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java14
-rw-r--r--services/core/java/com/android/server/pm/ResilientAtomicFile.java4
-rw-r--r--services/core/java/com/android/server/pm/SuspendPackageHelper.java4
-rw-r--r--services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java9
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerService.java12
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java17
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java13
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java11
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java16
-rw-r--r--services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java21
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java12
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java118
-rw-r--r--services/core/java/com/android/server/wallpaper/WallpaperManagerService.java105
-rw-r--r--services/core/java/com/android/server/webkit/OWNERS4
-rw-r--r--services/core/java/com/android/server/wm/AbsAppSnapshotController.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java18
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java32
-rw-r--r--services/core/java/com/android/server/wm/AppWarnings.java9
-rw-r--r--services/core/java/com/android/server/wm/DeviceStateController.java8
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java11
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java4
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java10
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java22
-rw-r--r--services/core/java/com/android/server/wm/SnapshotPersistQueue.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java4
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java8
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java33
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java56
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java10
-rw-r--r--services/core/jni/Android.bp2
-rw-r--r--services/core/jni/com_android_server_hint_HintManagerService.cpp45
-rw-r--r--services/core/jni/com_android_server_power_PowerManagerService.cpp34
-rw-r--r--services/core/jni/com_android_server_power_PowerManagerService.h5
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java38
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java20
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessPolicy.kt28
-rw-r--r--services/permission/java/com/android/server/permission/access/AccessState.kt13
-rw-r--r--services/permission/java/com/android/server/permission/access/permission/PermissionService.kt9
-rw-r--r--services/tests/PackageManagerServiceTests/server/Android.bp4
-rw-r--r--services/tests/PackageManagerServiceTests/server/AndroidTest.xml8
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java3
-rw-r--r--services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java2
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java28
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java8
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java67
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt3
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt5
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java17
-rw-r--r--services/tests/servicestests/Android.bp1
-rw-r--r--services/tests/servicestests/AndroidTest.xml5
-rw-r--r--services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java320
-rw-r--r--services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java116
-rw-r--r--services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java142
-rw-r--r--services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt355
-rw-r--r--services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt237
-rw-r--r--services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java118
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/OWNERS4
-rw-r--r--services/tests/uiservicestests/AndroidManifest.xml2
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java186
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java39
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java9
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java52
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java9
-rw-r--r--telephony/java/android/telephony/data/DataCallResponse.java16
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt140
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt)0
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt (renamed from tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt)0
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt26
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml7
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml8
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java33
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java21
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java6
-rw-r--r--tests/InputMethodStressTest/Android.bp1
-rw-r--r--tests/InputMethodStressTest/AndroidTest.xml1
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java6
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java9
-rw-r--r--tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java29
-rw-r--r--tests/UsbTests/Android.bp9
-rw-r--r--tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java42
371 files changed, 10322 insertions, 2802 deletions
diff --git a/apct-tests/perftests/core/OWNERS b/apct-tests/perftests/core/OWNERS
index 6abab6e27f8e..f8fe51c4fde9 100644
--- a/apct-tests/perftests/core/OWNERS
+++ b/apct-tests/perftests/core/OWNERS
@@ -12,3 +12,5 @@ per-file /apct-tests/perftests/core/src/android/content/res/* = file:/core/java/
per-file /apct-tests/perftests/core/src/android/content/om/* = felkachang@google.com
per-file /apct-tests/perftests/core/src/android/content/om/* = file:/core/java/android/content/om/OWNERS
+# Bug component: 44215
+per-file **Accessibility* = file:/core/java/android/view/accessibility/OWNERS \ No newline at end of file
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 4f5eb37d871d..cbc9263a2c3d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1631,7 +1631,8 @@ public class JobSchedulerService extends com.android.server.SystemService
jobStatus.getEstimatedNetworkDownloadBytes(),
jobStatus.getEstimatedNetworkUploadBytes(),
jobStatus.getWorkCount(),
- ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())));
+ ActivityManager.processStateAmToProto(mUidProcStates.get(jobStatus.getUid())),
+ jobStatus.getNamespaceHash());
// If the job is immediately ready to run, then we can just immediately
// put it in the pending list and try to schedule it. This is especially
@@ -2059,7 +2060,8 @@ public class JobSchedulerService extends com.android.server.SystemService
cancelled.getEstimatedNetworkDownloadBytes(),
cancelled.getEstimatedNetworkUploadBytes(),
cancelled.getWorkCount(),
- ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())));
+ ActivityManager.processStateAmToProto(mUidProcStates.get(cancelled.getUid())),
+ cancelled.getNamespaceHash());
}
// If this is a replacement, bring in the new version of the job
if (incomingJob != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index 90f1523104ee..0b08b6faf971 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -499,7 +499,8 @@ public final class JobServiceContext implements ServiceConnection {
job.getEstimatedNetworkDownloadBytes(),
job.getEstimatedNetworkUploadBytes(),
job.getWorkCount(),
- ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())));
+ ActivityManager.processStateAmToProto(mService.getUidProcState(job.getUid())),
+ job.getNamespaceHash());
sEnqueuedJwiAtJobStart.logSampleWithUid(job.getUid(), job.getWorkCount());
final String sourcePackage = job.getSourcePackageName();
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
@@ -1557,7 +1558,8 @@ public final class JobServiceContext implements ServiceConnection {
completedJob.getEstimatedNetworkUploadBytes(),
completedJob.getWorkCount(),
ActivityManager
- .processStateAmToProto(mService.getUidProcState(completedJob.getUid())));
+ .processStateAmToProto(mService.getUidProcState(completedJob.getUid())),
+ completedJob.getNamespaceHash());
if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
getId());
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 a8b4c695889c..edd531d13965 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
@@ -43,6 +43,7 @@ import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.text.format.DateFormat;
+import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.Pair;
@@ -51,6 +52,7 @@ import android.util.Slog;
import android.util.TimeUtils;
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;
@@ -65,10 +67,12 @@ import com.android.server.job.JobStatusShortInfoProto;
import dalvik.annotation.optimization.NeverCompile;
import java.io.PrintWriter;
+import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Objects;
+import java.util.Random;
import java.util.function.Predicate;
/**
@@ -88,6 +92,13 @@ public final class JobStatus {
private static final String TAG = "JobScheduler.JobStatus";
static final boolean DEBUG = JobSchedulerService.DEBUG;
+ private static MessageDigest sMessageDigest;
+ /** Cache of namespace to hash to reduce how often we need to generate the namespace hash. */
+ @GuardedBy("sNamespaceHashCache")
+ private static final ArrayMap<String, String> sNamespaceHashCache = new ArrayMap<>();
+ /** Maximum size of {@link #sNamespaceHashCache}. */
+ private static final int MAX_NAMESPACE_CACHE_SIZE = 128;
+
private static final int NUM_CONSTRAINT_CHANGE_HISTORY = 10;
public static final long NO_LATEST_RUNTIME = Long.MAX_VALUE;
@@ -231,6 +242,8 @@ public final class JobStatus {
final String sourceTag;
@Nullable
private final String mNamespace;
+ @Nullable
+ private final String mNamespaceHash;
/** An ID that can be used to uniquely identify the job when logging statsd metrics. */
private final long mLoggingJobId;
@@ -570,6 +583,7 @@ public final class JobStatus {
this.callingUid = callingUid;
this.standbyBucket = standbyBucket;
mNamespace = namespace;
+ mNamespaceHash = generateNamespaceHash(namespace);
mLoggingJobId = generateLoggingId(namespace, job.getId());
int tempSourceUid = -1;
@@ -814,6 +828,56 @@ public final class JobStatus {
return ((long) namespace.hashCode()) << 31 | jobId;
}
+ @Nullable
+ private static String generateNamespaceHash(@Nullable String namespace) {
+ if (namespace == null) {
+ return null;
+ }
+ if (namespace.trim().isEmpty()) {
+ // Input is composed of all spaces (or nothing at all).
+ return namespace;
+ }
+ synchronized (sNamespaceHashCache) {
+ final int idx = sNamespaceHashCache.indexOfKey(namespace);
+ if (idx >= 0) {
+ return sNamespaceHashCache.valueAt(idx);
+ }
+ }
+ String hash = null;
+ try {
+ // .hashCode() can result in conflicts that would make distinguishing between
+ // namespaces hard and reduce the accuracy of certain metrics. Use SHA-256
+ // to generate the hash since the probability of collision is extremely low.
+ if (sMessageDigest == null) {
+ sMessageDigest = MessageDigest.getInstance("SHA-256");
+ }
+ final byte[] digest = sMessageDigest.digest(namespace.getBytes());
+ // Convert to hexadecimal representation
+ StringBuilder hexBuilder = new StringBuilder(digest.length);
+ for (byte byteChar : digest) {
+ hexBuilder.append(String.format("%02X", byteChar));
+ }
+ hash = hexBuilder.toString();
+ } catch (Exception e) {
+ Slog.wtf(TAG, "Couldn't hash input", e);
+ }
+ if (hash == null) {
+ // If we get to this point, something went wrong with the MessageDigest above.
+ // Don't return the raw input value (which would defeat the purpose of hashing).
+ return "failed_namespace_hash";
+ }
+ hash = hash.intern();
+ synchronized (sNamespaceHashCache) {
+ if (sNamespaceHashCache.size() >= MAX_NAMESPACE_CACHE_SIZE) {
+ // Drop a random mapping instead of dropping at a predefined index to avoid
+ // potentially always dropping the same mapping.
+ sNamespaceHashCache.removeAt((new Random()).nextInt(MAX_NAMESPACE_CACHE_SIZE));
+ }
+ sNamespaceHashCache.put(namespace, hash);
+ }
+ return hash;
+ }
+
public void enqueueWorkLocked(JobWorkItem work) {
if (pendingWork == null) {
pendingWork = new ArrayList<>();
@@ -1117,10 +1181,16 @@ public final class JobStatus {
return true;
}
+ @Nullable
public String getNamespace() {
return mNamespace;
}
+ @Nullable
+ public String getNamespaceHash() {
+ return mNamespaceHash;
+ }
+
public String getSourceTag() {
return sourceTag;
}
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index f3763220f2b2..9dca29a4f287 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2169,6 +2169,10 @@ package android.net.wifi.sharedconnectivity.service {
package android.os {
+ public class BatteryManager {
+ field public static final int BATTERY_PLUGGED_ANY = 15; // 0xf
+ }
+
public final class BatteryStatsManager {
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void resetBattery(boolean);
method @RequiresPermission(android.Manifest.permission.DEVICE_POWER) public void setBatteryLevel(int, boolean);
@@ -3586,6 +3590,8 @@ package android.view {
field public static final int ACCESSIBILITY_TITLE_CHANGED = 33554432; // 0x2000000
field public static final int FLAG_SLIPPERY = 536870912; // 0x20000000
field public CharSequence accessibilityTitle;
+ field public float preferredMaxDisplayRefreshRate;
+ field public float preferredMinDisplayRefreshRate;
field public int privateFlags;
}
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 12026aa3f72a..4cad58521c09 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -568,13 +568,13 @@ public abstract class Animator implements Cloneable {
* repetition. lastPlayTime is similar and is used to calculate how many repeats have been
* done between the two times.
*/
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {}
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {}
/**
* Internal use only. This animates any animation that has ended since lastPlayTime.
* If an animation hasn't been finished, no change will be made.
*/
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {}
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {}
/**
* Internal use only. Adds all start times (after delay) to and end times to times.
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 60659dc12342..70c3d7ae3f82 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -825,8 +825,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
private void animateBasedOnPlayTime(
long currentPlayTime,
long lastPlayTime,
- boolean inReverse,
- boolean notify
+ boolean inReverse
) {
if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
@@ -857,8 +856,8 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
while (index < endIndex) {
long playTime = startEndTimes[index];
if (lastPlayTime != playTime) {
- animateSkipToEnds(playTime, lastPlayTime, notify);
- animateValuesInRange(playTime, lastPlayTime, notify);
+ animateSkipToEnds(playTime, lastPlayTime);
+ animateValuesInRange(playTime, lastPlayTime);
lastPlayTime = playTime;
}
index++;
@@ -868,15 +867,15 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
index--;
long playTime = startEndTimes[index];
if (lastPlayTime != playTime) {
- animateSkipToEnds(playTime, lastPlayTime, notify);
- animateValuesInRange(playTime, lastPlayTime, notify);
+ animateSkipToEnds(playTime, lastPlayTime);
+ animateValuesInRange(playTime, lastPlayTime);
lastPlayTime = playTime;
}
}
}
if (currentPlayTime != lastPlayTime) {
- animateSkipToEnds(currentPlayTime, lastPlayTime, notify);
- animateValuesInRange(currentPlayTime, lastPlayTime, notify);
+ animateSkipToEnds(currentPlayTime, lastPlayTime);
+ animateValuesInRange(currentPlayTime, lastPlayTime);
}
}
@@ -896,13 +895,11 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
@Override
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
initAnimation();
if (lastPlayTime > currentPlayTime) {
- if (notify) {
- notifyStartListeners(true);
- }
+ notifyStartListeners(true);
for (int i = mEvents.size() - 1; i >= 0; i--) {
AnimationEvent event = mEvents.get(i);
Node node = event.mNode;
@@ -916,31 +913,25 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
if (currentPlayTime <= start && start < lastPlayTime) {
animator.animateSkipToEnds(
0,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify) {
- mPlayingSet.remove(node);
- }
+ mPlayingSet.remove(node);
} else if (start <= currentPlayTime && currentPlayTime <= end) {
animator.animateSkipToEnds(
currentPlayTime - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify && !mPlayingSet.contains(node)) {
+ if (!mPlayingSet.contains(node)) {
mPlayingSet.add(node);
}
}
}
}
- if (currentPlayTime <= 0 && notify) {
+ if (currentPlayTime <= 0) {
notifyEndListeners(true);
}
} else {
- if (notify) {
- notifyStartListeners(false);
- }
+ notifyStartListeners(false);
int eventsSize = mEvents.size();
for (int i = 0; i < eventsSize; i++) {
AnimationEvent event = mEvents.get(i);
@@ -955,45 +946,39 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
if (lastPlayTime < end && end <= currentPlayTime) {
animator.animateSkipToEnds(
end - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify) {
- mPlayingSet.remove(node);
- }
+ mPlayingSet.remove(node);
} else if (start <= currentPlayTime && currentPlayTime <= end) {
animator.animateSkipToEnds(
currentPlayTime - node.mStartTime,
- lastPlayTime - node.mStartTime,
- notify
+ lastPlayTime - node.mStartTime
);
- if (notify && !mPlayingSet.contains(node)) {
+ if (!mPlayingSet.contains(node)) {
mPlayingSet.add(node);
}
}
}
}
- if (currentPlayTime >= getTotalDuration() && notify) {
+ if (currentPlayTime >= getTotalDuration()) {
notifyEndListeners(false);
}
}
}
@Override
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
initAnimation();
- if (notify) {
- if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
- notifyStartListeners(false);
- } else {
- long duration = getTotalDuration();
- if (duration >= 0
- && (lastPlayTime > duration || (lastPlayTime == duration
- && currentPlayTime < duration))
- ) {
- notifyStartListeners(true);
- }
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else {
+ long duration = getTotalDuration();
+ if (duration >= 0
+ && (lastPlayTime > duration || (lastPlayTime == duration
+ && currentPlayTime < duration))
+ ) {
+ notifyStartListeners(true);
}
}
@@ -1014,8 +999,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
) {
animator.animateValuesInRange(
currentPlayTime - node.mStartTime,
- Math.max(-1, lastPlayTime - node.mStartTime),
- notify
+ Math.max(-1, lastPlayTime - node.mStartTime)
);
}
}
@@ -1111,7 +1095,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
}
}
mSeekState.setPlayTime(playTime, mReversing);
- animateBasedOnPlayTime(playTime, lastPlayTime, mReversing, true);
+ animateBasedOnPlayTime(playTime, lastPlayTime, mReversing);
}
/**
@@ -1144,16 +1128,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
private void initChildren() {
if (!isInitialized()) {
mChildrenInitialized = true;
-
- // We have to initialize all the start values so that they are based on the previous
- // values.
- long[] times = ensureChildStartAndEndTimes();
-
- long previousTime = -1;
- for (long time : times) {
- animateBasedOnPlayTime(time, previousTime, false, false);
- previousTime = time;
- }
+ skipToEndValue(false);
}
}
@@ -1489,6 +1464,7 @@ public final class AnimatorSet extends Animator implements AnimationHandler.Anim
anim.mPauseTime = -1;
anim.mSeekState = new SeekState();
anim.mSelfPulse = true;
+ anim.mStartListenersCalled = false;
anim.mPlayingSet = new ArrayList<Node>();
anim.mNodeMap = new ArrayMap<Animator, Node>();
anim.mNodes = new ArrayList<Node>(nodeCount);
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index ead238f75ba4..5de7f387b206 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -1417,21 +1417,19 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
* will be called.
*/
@Override
- void animateValuesInRange(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateValuesInRange(long currentPlayTime, long lastPlayTime) {
if (currentPlayTime < 0 || lastPlayTime < -1) {
throw new UnsupportedOperationException("Error: Play time should never be negative.");
}
initAnimation();
long duration = getTotalDuration();
- if (notify) {
- if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
- notifyStartListeners(false);
- } else if (lastPlayTime > duration
- || (lastPlayTime == duration && currentPlayTime < duration)
- ) {
- notifyStartListeners(true);
- }
+ if (lastPlayTime < 0 || (lastPlayTime == 0 && currentPlayTime > 0)) {
+ notifyStartListeners(false);
+ } else if (lastPlayTime > duration
+ || (lastPlayTime == duration && currentPlayTime < duration)
+ ) {
+ notifyStartListeners(true);
}
if (duration >= 0) {
lastPlayTime = Math.min(duration, lastPlayTime);
@@ -1448,7 +1446,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
iteration = Math.min(iteration, mRepeatCount);
lastIteration = Math.min(lastIteration, mRepeatCount);
- if (notify && iteration != lastIteration) {
+ if (iteration != lastIteration) {
notifyListeners(AnimatorCaller.ON_REPEAT, false);
}
}
@@ -1464,7 +1462,7 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
}
@Override
- void animateSkipToEnds(long currentPlayTime, long lastPlayTime, boolean notify) {
+ void animateSkipToEnds(long currentPlayTime, long lastPlayTime) {
boolean inReverse = currentPlayTime < lastPlayTime;
boolean doSkip;
if (currentPlayTime <= 0 && lastPlayTime > 0) {
@@ -1474,13 +1472,9 @@ public class ValueAnimator extends Animator implements AnimationHandler.Animatio
doSkip = duration >= 0 && currentPlayTime >= duration && lastPlayTime < duration;
}
if (doSkip) {
- if (notify) {
- notifyStartListeners(inReverse);
- }
+ notifyStartListeners(inReverse);
skipToEndValue(inReverse);
- if (notify) {
- notifyEndListeners(inReverse);
- }
+ notifyEndListeners(inReverse);
}
}
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index aa1f5c0e5643..b2298064aaa4 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1084,6 +1084,16 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Update the forced status bar appearance.
+ * @hide
+ */
+ @Override
+ public void updateStatusBarAppearance(int appearance) {
+ mTaskDescription.setStatusBarAppearance(appearance);
+ setTaskDescription(mTaskDescription);
+ }
+
+ /**
* Update the forced navigation bar color.
* @hide
*/
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 1df1781fa3f1..d76e20114816 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -81,6 +81,7 @@ import android.util.ArrayMap;
import android.util.DisplayMetrics;
import android.util.Singleton;
import android.util.Size;
+import android.view.WindowInsetsController.Appearance;
import android.window.TaskSnapshot;
import com.android.internal.app.LocalePicker;
@@ -1558,6 +1559,8 @@ public class ActivityManager {
private int mColorBackgroundFloating;
private int mStatusBarColor;
private int mNavigationBarColor;
+ @Appearance
+ private int mStatusBarAppearance;
private boolean mEnsureStatusBarContrastWhenTransparent;
private boolean mEnsureNavigationBarContrastWhenTransparent;
private int mResizeMode;
@@ -1658,8 +1661,8 @@ public class ActivityManager {
final Icon icon = mIconRes == Resources.ID_NULL ? null :
Icon.createWithResource(ActivityThread.currentPackageName(), mIconRes);
return new TaskDescription(mLabel, icon, mPrimaryColor, mBackgroundColor,
- mStatusBarColor, mNavigationBarColor, false, false, RESIZE_MODE_RESIZEABLE,
- -1, -1, 0);
+ mStatusBarColor, mNavigationBarColor, 0, false, false,
+ RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
}
@@ -1677,7 +1680,7 @@ public class ActivityManager {
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- colorPrimary, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ colorPrimary, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1695,7 +1698,7 @@ public class ActivityManager {
@Deprecated
public TaskDescription(String label, @DrawableRes int iconRes) {
this(label, Icon.createWithResource(ActivityThread.currentPackageName(), iconRes),
- 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1707,7 +1710,7 @@ public class ActivityManager {
*/
@Deprecated
public TaskDescription(String label) {
- this(label, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1717,7 +1720,7 @@ public class ActivityManager {
*/
@Deprecated
public TaskDescription() {
- this(null, null, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(null, null, 0, 0, 0, 0, 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/**
@@ -1733,7 +1736,7 @@ public class ActivityManager {
@Deprecated
public TaskDescription(String label, Bitmap icon, int colorPrimary) {
this(label, icon != null ? Icon.createWithBitmap(icon) : null, colorPrimary, 0, 0, 0,
- false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ 0, false, false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
throw new RuntimeException("A TaskDescription's primary color should be opaque");
}
@@ -1749,14 +1752,15 @@ public class ActivityManager {
*/
@Deprecated
public TaskDescription(String label, Bitmap icon) {
- this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, false, false,
- RESIZE_MODE_RESIZEABLE, -1, -1, 0);
+ this(label, icon != null ? Icon.createWithBitmap(icon) : null, 0, 0, 0, 0, 0, false,
+ false, RESIZE_MODE_RESIZEABLE, -1, -1, 0);
}
/** @hide */
public TaskDescription(@Nullable String label, @Nullable Icon icon,
int colorPrimary, int colorBackground,
int statusBarColor, int navigationBarColor,
+ @Appearance int statusBarAppearance,
boolean ensureStatusBarContrastWhenTransparent,
boolean ensureNavigationBarContrastWhenTransparent, int resizeMode, int minWidth,
int minHeight, int colorBackgroundFloating) {
@@ -1766,6 +1770,7 @@ public class ActivityManager {
mColorBackground = colorBackground;
mStatusBarColor = statusBarColor;
mNavigationBarColor = navigationBarColor;
+ mStatusBarAppearance = statusBarAppearance;
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
ensureNavigationBarContrastWhenTransparent;
@@ -1794,6 +1799,7 @@ public class ActivityManager {
mColorBackground = other.mColorBackground;
mStatusBarColor = other.mStatusBarColor;
mNavigationBarColor = other.mNavigationBarColor;
+ mStatusBarAppearance = other.mStatusBarAppearance;
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
other.mEnsureNavigationBarContrastWhenTransparent;
@@ -1823,6 +1829,9 @@ public class ActivityManager {
if (other.mNavigationBarColor != 0) {
mNavigationBarColor = other.mNavigationBarColor;
}
+ if (other.mStatusBarAppearance != 0) {
+ mStatusBarAppearance = other.mStatusBarAppearance;
+ }
mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
mEnsureNavigationBarContrastWhenTransparent =
@@ -2094,6 +2103,14 @@ public class ActivityManager {
/**
* @hide
*/
+ @Appearance
+ public int getStatusBarAppearance() {
+ return mStatusBarAppearance;
+ }
+
+ /**
+ * @hide
+ */
public void setEnsureStatusBarContrastWhenTransparent(
boolean ensureStatusBarContrastWhenTransparent) {
mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
@@ -2102,6 +2119,13 @@ public class ActivityManager {
/**
* @hide
*/
+ public void setStatusBarAppearance(@Appearance int statusBarAppearance) {
+ mStatusBarAppearance = statusBarAppearance;
+ }
+
+ /**
+ * @hide
+ */
public boolean getEnsureNavigationBarContrastWhenTransparent() {
return mEnsureNavigationBarContrastWhenTransparent;
}
@@ -2223,6 +2247,7 @@ public class ActivityManager {
dest.writeInt(mColorBackground);
dest.writeInt(mStatusBarColor);
dest.writeInt(mNavigationBarColor);
+ dest.writeInt(mStatusBarAppearance);
dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
dest.writeInt(mResizeMode);
@@ -2246,6 +2271,7 @@ public class ActivityManager {
mColorBackground = source.readInt();
mStatusBarColor = source.readInt();
mNavigationBarColor = source.readInt();
+ mStatusBarAppearance = source.readInt();
mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
mResizeMode = source.readInt();
@@ -2294,6 +2320,7 @@ public class ActivityManager {
&& mColorBackground == other.mColorBackground
&& mStatusBarColor == other.mStatusBarColor
&& mNavigationBarColor == other.mNavigationBarColor
+ && mStatusBarAppearance == other.mStatusBarAppearance
&& mEnsureStatusBarContrastWhenTransparent
== other.mEnsureStatusBarContrastWhenTransparent
&& mEnsureNavigationBarContrastWhenTransparent
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 7be00a045403..3487e0b1f3e8 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -827,6 +827,12 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
*/
public static final int PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS = 1 << 4;
+ /**
+ * Whether AbiOverride was used when installing this application.
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_EXT_CPU_OVERRIDE = 1 << 5;
+
/** @hide */
@IntDef(flag = true, prefix = { "PRIVATE_FLAG_EXT_" }, value = {
PRIVATE_FLAG_EXT_PROFILEABLE,
@@ -834,6 +840,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable {
PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE,
PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK,
PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS,
+ PRIVATE_FLAG_EXT_CPU_OVERRIDE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface ApplicationInfoPrivateFlagsExt {}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 34a697ab285b..96af2b6caf9f 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -187,7 +187,7 @@ public final class UserProperties implements Parcelable {
* device policies from its parent profile.
*
*<p> All the user restrictions and device policies would be not propagated to the profile
- * with this property value. The {(TODO:b/256978256) @link DevicePolicyEngine}
+ * with this property value. The {@link com.android.server.devicepolicy.DevicePolicyEngine}
* uses this property to determine and propagate only select ones to the given profile.
*
* @hide
diff --git a/core/java/android/hardware/SensorAdditionalInfo.java b/core/java/android/hardware/SensorAdditionalInfo.java
index 12edc5eb7e33..59def9fb3325 100644
--- a/core/java/android/hardware/SensorAdditionalInfo.java
+++ b/core/java/android/hardware/SensorAdditionalInfo.java
@@ -63,7 +63,7 @@ public class SensorAdditionalInfo {
public final int[] intValues;
/**
- * Typical values of additional infomation type. The set of values is subject to extension in
+ * Typical values of additional information type. The set of values is subject to extension in
* newer versions and vendors have the freedom of define their own custom values.
*
* @hide
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index c2aebd7cfaca..f033f9740b3d 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -893,9 +893,9 @@ public abstract class SensorManager {
/**
* Flushes the FIFO of all the sensors registered for this listener. If there are events
- * in the FIFO of the sensor, they are returned as if the maxReportLantecy of the FIFO has
+ * in the FIFO of the sensor, they are returned as if the maxReportLatency of the FIFO has
* expired. Events are returned in the usual way through the SensorEventListener.
- * This call doesn't affect the maxReportLantecy for this sensor. This call is asynchronous and
+ * This call doesn't affect the maxReportLatency for this sensor. This call is asynchronous and
* returns immediately.
* {@link android.hardware.SensorEventListener2#onFlushCompleted onFlushCompleted} is called
* after all the events in the batch at the time of calling this method have been delivered
@@ -923,7 +923,7 @@ public abstract class SensorManager {
* Create a sensor direct channel backed by shared memory wrapped in MemoryFile object.
*
* The resulting channel can be used for delivering sensor events to native code, other
- * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommanded
+ * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommended
* for high performance sensor applications that use high sensor rates (e.g. greater than 200Hz)
* and cares about sensor event latency.
*
@@ -945,7 +945,7 @@ public abstract class SensorManager {
* Create a sensor direct channel backed by shared memory wrapped in HardwareBuffer object.
*
* The resulting channel can be used for delivering sensor events to native code, other
- * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommanded
+ * processes, GPU/DSP or other co-processors without CPU intervention. This is the recommended
* for high performance sensor applications that use high sensor rates (e.g. greater than 200Hz)
* and cares about sensor event latency.
*
@@ -1355,11 +1355,11 @@ public abstract class SensorManager {
* returned by {@link #getRotationMatrix}.
*
* @param X
- * defines the axis of the new cooridinate system that coincide with the X axis of the
+ * defines the axis of the new coordinate system that coincide with the X axis of the
* original coordinate system.
*
* @param Y
- * defines the axis of the new cooridinate system that coincide with the Y axis of the
+ * defines the axis of the new coordinate system that coincide with the Y axis of the
* original coordinate system.
*
* @param outR
@@ -1847,7 +1847,7 @@ public abstract class SensorManager {
* This method is used to inject raw sensor data into the HAL. Call {@link
* initDataInjection(boolean)} before this method to set the HAL in data injection mode. This
* method should be called only if a previous call to initDataInjection has been successful and
- * the HAL and SensorService are already opreating in data injection mode.
+ * the HAL and SensorService are already operating in data injection mode.
*
* @param sensor The sensor to inject.
* @param values Sensor values to inject. The length of this
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index d8ab6f7da82d..0f6f6019c0f7 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -565,7 +565,7 @@ public class SystemSensorManager extends SensorManager {
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(Intent.ACTION_DYNAMIC_SENSOR_CHANGED)) {
if (DEBUG_DYNAMIC_SENSOR) {
- Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANED broadcast");
+ Log.i(TAG, "DYNS received DYNAMIC_SENSOR_CHANGED broadcast");
}
// Dynamic sensors probably changed
mDynamicSensorListDirty = true;
@@ -610,7 +610,7 @@ public class SystemSensorManager extends SensorManager {
protected void unregisterDynamicSensorCallbackImpl(
DynamicSensorCallback callback) {
if (DEBUG_DYNAMIC_SENSOR) {
- Log.i(TAG, "Removing dynamic sensor listerner");
+ Log.i(TAG, "Removing dynamic sensor listener");
}
mDynamicSensorCallbacks.remove(callback);
}
@@ -740,7 +740,7 @@ public class SystemSensorManager extends SensorManager {
}
if (hardwareBuffer.getWidth() < MIN_DIRECT_CHANNEL_BUFFER_SIZE) {
throw new IllegalArgumentException(
- "Width if HaradwareBuffer must be greater than "
+ "Width if HardwareBuffer must be greater than "
+ MIN_DIRECT_CHANNEL_BUFFER_SIZE);
}
if ((hardwareBuffer.getUsage() & HardwareBuffer.USAGE_SENSOR_DIRECT_DATA) == 0) {
@@ -771,7 +771,7 @@ public class SystemSensorManager extends SensorManager {
/*
* BaseEventQueue is the communication channel with the sensor service,
- * SensorEventQueue, TriggerEventQueue are subclases and there is one-to-one mapping between
+ * SensorEventQueue, TriggerEventQueue are subclasses and there is one-to-one mapping between
* the queues and the listeners. InjectEventQueue is also a sub-class which is a special case
* where data is being injected into the sensor HAL through the sensor service. It is not
* associated with any listener and there is one InjectEventQueue associated with a
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index b4533042b4a3..76efce56dcf0 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -53,8 +53,6 @@ import android.view.Surface;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
-import libcore.util.EmptyArray;
-
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
@@ -667,7 +665,7 @@ public final class DisplayManager {
} else if (category == null || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
return getDisplays(displayIds, Objects::nonNull);
}
- return (Display[]) EmptyArray.OBJECT;
+ return new Display[0];
}
private Display[] getDisplays(int[] displayIds, Predicate<Display> predicate) {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index d6df03321298..94bff893b5a8 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -413,6 +413,15 @@ public abstract class DisplayManagerInternal {
public abstract HostUsiVersion getHostUsiVersion(int displayId);
/**
+ * Get the ALS data for a particular display.
+ *
+ * @param displayId The id of the display.
+ * @return {@link AmbientLightSensorData}
+ */
+ @Nullable
+ public abstract AmbientLightSensorData getAmbientLightSensorData(int displayId);
+
+ /**
* Get all available DisplayGroupIds.
*/
public abstract IntArray getDisplayGroupIds();
@@ -669,4 +678,23 @@ public abstract class DisplayManagerInternal {
return "RefreshRateLimitation(" + type + ": " + range + ")";
}
}
+
+ /**
+ * Class to provide Ambient sensor data using the API
+ * {@link DisplayManagerInternal#getAmbientLightSensorData(int)}
+ */
+ public static final class AmbientLightSensorData {
+ public String sensorName;
+ public String sensorType;
+
+ public AmbientLightSensorData(String name, String type) {
+ sensorName = name;
+ sensorType = type;
+ }
+
+ @Override
+ public String toString() {
+ return "AmbientLightSensorData(" + sensorName + ", " + sensorType + ")";
+ }
+ }
}
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 6bc0f6ea947c..092923e927a3 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -20,6 +20,7 @@ import android.Manifest.permission;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.SystemService;
+import android.annotation.TestApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -232,6 +233,7 @@ public class BatteryManager {
OsProtoEnums.CHARGING_POLICY_ADAPTIVE_LONGLIFE; // = 4
/** @hide */
+ @TestApi
public static final int BATTERY_PLUGGED_ANY =
BATTERY_PLUGGED_AC | BATTERY_PLUGGED_USB | BATTERY_PLUGGED_WIRELESS
| BATTERY_PLUGGED_DOCK;
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 01a886434376..0144d22d7aec 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -742,7 +742,7 @@ public class UserManager {
* The default value is <code>false</code>.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Key for user restrictions.
@@ -947,7 +947,7 @@ public class UserManager {
* this restriction will be set as a base restriction which cannot be removed by any admin.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Key for user restrictions.
@@ -1457,7 +1457,7 @@ public class UserManager {
* affected. The default value is <code>false</code>.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>Key for user restrictions.
@@ -1603,7 +1603,7 @@ public class UserManager {
* set.
*
* <p>Holders of the permission
- * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_USERS}
+ * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_MODIFY_USERS}
* can set this restriction using the DevicePolicyManager APIs mentioned below.
*
* <p>The default value is <code>false</code>.
diff --git a/core/java/android/os/image/DynamicSystemClient.java b/core/java/android/os/image/DynamicSystemClient.java
index 218ecc8c5571..88096ab91261 100644
--- a/core/java/android/os/image/DynamicSystemClient.java
+++ b/core/java/android/os/image/DynamicSystemClient.java
@@ -209,6 +209,13 @@ public class DynamicSystemClient {
public static final String ACTION_HIDE_NOTIFICATION =
"android.os.image.action.HIDE_NOTIFICATION";
+ /**
+ * Intent action: notify the service to post a status update when keyguard is dismissed.
+ * @hide
+ */
+ public static final String ACTION_NOTIFY_KEYGUARD_DISMISSED =
+ "android.os.image.action.NOTIFY_KEYGUARD_DISMISSED";
+
/*
* Intent Keys
*/
diff --git a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
index fb6061916d99..4aa5c00a6a52 100644
--- a/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
+++ b/core/java/android/service/contentcapture/ContentCaptureServiceInfo.java
@@ -48,7 +48,7 @@ import java.io.PrintWriter;
*
* @hide
*/
-public final class ContentCaptureServiceInfo {
+public class ContentCaptureServiceInfo {
private static final String TAG = ContentCaptureServiceInfo.class.getSimpleName();
private static final String XML_TAG_SERVICE = "content-capture-service";
diff --git a/core/java/android/text/util/Linkify.java b/core/java/android/text/util/Linkify.java
index 946fc30eecb3..fb6de56b0952 100644
--- a/core/java/android/text/util/Linkify.java
+++ b/core/java/android/text/util/Linkify.java
@@ -31,6 +31,7 @@ import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.text.style.URLSpan;
+import android.text.TextUtils;
import android.util.Log;
import android.util.Patterns;
import android.webkit.WebView;
@@ -672,8 +673,15 @@ public class Linkify {
@Nullable Context context) {
PhoneNumberUtil phoneUtil = PhoneNumberUtil.getInstance();
final Context ctx = (context != null) ? context : ActivityThread.currentApplication();
- final String regionCode = (ctx != null) ? ctx.getSystemService(TelephonyManager.class).
- getSimCountryIso().toUpperCase(Locale.US) : Locale.getDefault().getCountry();
+ String regionCode = Locale.getDefault().getCountry();
+ if (ctx != null) {
+ String simCountryIso = ctx.getSystemService(TelephonyManager.class).getSimCountryIso();
+ if (!TextUtils.isEmpty(simCountryIso)) {
+ // Assign simCountryIso if it is available
+ regionCode = simCountryIso.toUpperCase(Locale.US);
+ }
+ }
+
Iterable<PhoneNumberMatch> matches = phoneUtil.findNumbers(s.toString(),
regionCode, Leniency.POSSIBLE, Long.MAX_VALUE);
for (PhoneNumberMatch match : matches) {
diff --git a/core/java/android/view/HapticFeedbackConstants.java b/core/java/android/view/HapticFeedbackConstants.java
index 6551a1884af0..253073a83827 100644
--- a/core/java/android/view/HapticFeedbackConstants.java
+++ b/core/java/android/view/HapticFeedbackConstants.java
@@ -129,27 +129,25 @@ public class HapticFeedbackConstants {
public static final int REJECT = 17;
/**
- * A haptic effect to provide texture while a rotary input device is being scrolled.
+ * A haptic effect to provide texture while scrolling.
*
* @hide
*/
- public static final int ROTARY_SCROLL_TICK = 18;
+ public static final int SCROLL_TICK = 18;
/**
- * A haptic effect to signal that a list element has been focused while scrolling using a rotary
- * input device.
+ * A haptic effect to signal that a list element has been focused while scrolling.
*
* @hide
*/
- public static final int ROTARY_SCROLL_ITEM_FOCUS = 19;
+ public static final int SCROLL_ITEM_FOCUS = 19;
/**
- * A haptic effect to signal reaching the scrolling limits of a list while scrolling using a
- * rotary input device.
+ * A haptic effect to signal reaching the scrolling limits of a list while scrolling.
*
* @hide
*/
- public static final int ROTARY_SCROLL_LIMIT = 20;
+ public static final int SCROLL_LIMIT = 20;
/**
* The user has toggled a switch or button into the on position.
diff --git a/core/java/android/view/HapticScrollFeedbackProvider.java b/core/java/android/view/HapticScrollFeedbackProvider.java
new file mode 100644
index 000000000000..7e103a53d4ed
--- /dev/null
+++ b/core/java/android/view/HapticScrollFeedbackProvider.java
@@ -0,0 +1,141 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static com.android.internal.R.dimen.config_rotaryEncoderAxisScrollTickInterval;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * {@link ScrollFeedbackProvider} that performs haptic feedback when scrolling.
+ *
+ * <p>Each scrolling widget should have its own instance of this class to ensure that scroll state
+ * is isolated.
+ *
+ * @hide
+ */
+public class HapticScrollFeedbackProvider implements ScrollFeedbackProvider {
+ private static final String TAG = "HapticScrollFeedbackProvider";
+
+ private static final int TICK_INTERVAL_NO_TICK = 0;
+ private static final int TICK_INTERVAL_UNSET = Integer.MAX_VALUE;
+
+ private final View mView;
+
+
+ // Info about the cause of the latest scroll event.
+ /** The ID of the {link @InputDevice} that caused the latest scroll event. */
+ private int mDeviceId = -1;
+ /** The axis on which the latest scroll event happened. */
+ private int mAxis = -1;
+ /** The {@link InputDevice} source from which the latest scroll event happened. */
+ private int mSource = -1;
+
+ /**
+ * Cache for tick interval for scroll tick caused by a {@link InputDevice#SOURCE_ROTARY_ENCODER}
+ * on {@link MotionEvent#AXIS_SCROLL}. Set to -1 if the value has not been fetched and cached.
+ */
+ private int mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_UNSET;
+ /** The tick interval corresponding to the current InputDevice/source/axis. */
+ private int mTickIntervalPixels = TICK_INTERVAL_NO_TICK;
+ private int mTotalScrollPixels = 0;
+ private boolean mCanPlayLimitFeedback = true;
+
+ public HapticScrollFeedbackProvider(View view) {
+ this(view, /* rotaryEncoderAxisScrollTickIntervalPixels= */ TICK_INTERVAL_UNSET);
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public HapticScrollFeedbackProvider(View view, int rotaryEncoderAxisScrollTickIntervalPixels) {
+ mView = view;
+ mRotaryEncoderAxisScrollTickIntervalPixels = rotaryEncoderAxisScrollTickIntervalPixels;
+ }
+
+ @Override
+ public void onScrollProgress(MotionEvent event, int axis, int deltaInPixels) {
+ maybeUpdateCurrentConfig(event, axis);
+
+ if (mTickIntervalPixels == TICK_INTERVAL_NO_TICK) {
+ // There's no valid tick interval. Exit early before doing any further computation.
+ return;
+ }
+
+ mTotalScrollPixels += deltaInPixels;
+
+ if (Math.abs(mTotalScrollPixels) >= mTickIntervalPixels) {
+ mTotalScrollPixels %= mTickIntervalPixels;
+ // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
+ mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_TICK);
+ }
+
+ mCanPlayLimitFeedback = true;
+ }
+
+ @Override
+ public void onScrollLimit(MotionEvent event, int axis, boolean isStart) {
+ maybeUpdateCurrentConfig(event, axis);
+
+ if (!mCanPlayLimitFeedback) {
+ return;
+ }
+
+ // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
+ mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_LIMIT);
+
+ mCanPlayLimitFeedback = false;
+ }
+
+ @Override
+ public void onSnapToItem(MotionEvent event, int axis) {
+ // TODO(b/239594271): create a new `performHapticFeedbackForDevice` and use that here.
+ mView.performHapticFeedback(HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+ mCanPlayLimitFeedback = true;
+ }
+
+ private void maybeUpdateCurrentConfig(MotionEvent event, int axis) {
+ int source = event.getSource();
+ int deviceId = event.getDeviceId();
+
+ if (mAxis != axis || mSource != source || mDeviceId != deviceId) {
+ mSource = source;
+ mAxis = axis;
+ mDeviceId = deviceId;
+
+ mCanPlayLimitFeedback = true;
+ mTotalScrollPixels = 0;
+ calculateTickIntervals(source, axis);
+ }
+ }
+
+ private void calculateTickIntervals(int source, int axis) {
+ mTickIntervalPixels = TICK_INTERVAL_NO_TICK;
+
+ if (axis == MotionEvent.AXIS_SCROLL && source == InputDevice.SOURCE_ROTARY_ENCODER) {
+ if (mRotaryEncoderAxisScrollTickIntervalPixels == TICK_INTERVAL_UNSET) {
+ // Value has not been fetched yet. Fetch and cache it.
+ mRotaryEncoderAxisScrollTickIntervalPixels =
+ mView.getContext().getResources().getDimensionPixelSize(
+ config_rotaryEncoderAxisScrollTickInterval);
+ if (mRotaryEncoderAxisScrollTickIntervalPixels < 0) {
+ mRotaryEncoderAxisScrollTickIntervalPixels = TICK_INTERVAL_NO_TICK;
+ }
+ }
+ mTickIntervalPixels = mRotaryEncoderAxisScrollTickIntervalPixels;
+ }
+ }
+}
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index 48ae59b618fb..a9a5888207aa 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -480,10 +480,23 @@ interface IWindowManager
* Requests Keyboard Shortcuts from the displayed window.
*
* @param receiver The receiver to deliver the results to.
+ * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+ * not triggered by a KeyEvent.
+ * @see #requestImeKeyboardShortcuts(IResultReceiver, int)
*/
void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId);
/**
+ * Requests Keyboard Shortcuts from currently selected IME.
+ *
+ * @param receiver The receiver to deliver the results to.
+ * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+ * not triggered by a KeyEvent.
+ * @see #requestAppKeyboardShortcuts(IResultReceiver, int)
+ */
+ void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId);
+
+ /**
* Retrieves the current stable insets from the primary display.
*/
@UnsupportedAppUsage
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 7e4e4022f00f..5019b85ca503 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1099,21 +1099,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
// TODO: Support a ResultReceiver for IME.
// TODO(b/123718661): Make show() work for multi-session IME.
int typesReady = 0;
+ final boolean imeVisible = mState.isSourceOrDefaultVisible(
+ mImeSourceConsumer.getId(), ime());
for (int type = FIRST; type <= LAST; type = type << 1) {
if ((types & type) == 0) {
continue;
}
@AnimationType final int animationType = getAnimationType(type);
final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0;
- final boolean isImeAnimation = type == ime();
- if (requestedVisible && animationType == ANIMATION_TYPE_NONE
- || animationType == ANIMATION_TYPE_SHOW) {
+ final boolean isIme = type == ime();
+ var alreadyVisible = requestedVisible && (!isIme || imeVisible)
+ && animationType == ANIMATION_TYPE_NONE;
+ var alreadyAnimatingShow = animationType == ANIMATION_TYPE_SHOW;
+ if (alreadyVisible || alreadyAnimatingShow) {
// no-op: already shown or animating in (because window visibility is
// applied before starting animation).
if (DEBUG) Log.d(TAG, String.format(
"show ignored for type: %d animType: %d requestedVisible: %s",
type, animationType, requestedVisible));
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onCancelled(statsToken,
ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
@@ -1121,13 +1125,13 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
}
if (fromIme && animationType == ANIMATION_TYPE_USER) {
// App is already controlling the IME, don't cancel it.
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onFailed(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
continue;
}
- if (isImeAnimation) {
+ if (isIme) {
ImeTracker.forLogging().onProgress(
statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION);
}
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
index a47f34f4125e..83bdb087a539 100644
--- a/core/java/android/view/InsetsFrameProvider.java
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -227,6 +227,10 @@ public class InsetsFrameProvider implements Parcelable {
if (mArbitraryRectangle != null) {
sb.append(", mArbitraryRectangle=").append(mArbitraryRectangle.toShortString());
}
+ if (mMinimalInsetsSizeInDisplayCutoutSafe != null) {
+ sb.append(", mMinimalInsetsSizeInDisplayCutoutSafe=")
+ .append(mMinimalInsetsSizeInDisplayCutoutSafe);
+ }
sb.append("}");
return sb.toString();
}
@@ -252,6 +256,7 @@ public class InsetsFrameProvider implements Parcelable {
mInsetsSize = in.readTypedObject(Insets.CREATOR);
mInsetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
mArbitraryRectangle = in.readTypedObject(Rect.CREATOR);
+ mMinimalInsetsSizeInDisplayCutoutSafe = in.readTypedObject(Insets.CREATOR);
}
@Override
@@ -262,6 +267,7 @@ public class InsetsFrameProvider implements Parcelable {
out.writeTypedObject(mInsetsSize, flags);
out.writeTypedArray(mInsetsSizeOverrides, flags);
out.writeTypedObject(mArbitraryRectangle, flags);
+ out.writeTypedObject(mMinimalInsetsSizeInDisplayCutoutSafe, flags);
}
public boolean idEquals(InsetsFrameProvider o) {
@@ -280,13 +286,16 @@ public class InsetsFrameProvider implements Parcelable {
return mId == other.mId && mSource == other.mSource && mFlags == other.mFlags
&& Objects.equals(mInsetsSize, other.mInsetsSize)
&& Arrays.equals(mInsetsSizeOverrides, other.mInsetsSizeOverrides)
- && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle);
+ && Objects.equals(mArbitraryRectangle, other.mArbitraryRectangle)
+ && Objects.equals(mMinimalInsetsSizeInDisplayCutoutSafe,
+ other.mMinimalInsetsSizeInDisplayCutoutSafe);
}
@Override
public int hashCode() {
return Objects.hash(mId, mSource, mFlags, mInsetsSize,
- Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle);
+ Arrays.hashCode(mInsetsSizeOverrides), mArbitraryRectangle,
+ mMinimalInsetsSizeInDisplayCutoutSafe);
}
public static final @NonNull Parcelable.Creator<InsetsFrameProvider> CREATOR =
diff --git a/core/java/android/view/ScrollFeedbackProvider.java b/core/java/android/view/ScrollFeedbackProvider.java
new file mode 100644
index 000000000000..8d3491df5d9e
--- /dev/null
+++ b/core/java/android/view/ScrollFeedbackProvider.java
@@ -0,0 +1,63 @@
+/**
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+/**
+ * Interface to represent an entity giving consistent feedback for different events surrounding view
+ * scroll.
+ *
+ * @hide
+ */
+public interface ScrollFeedbackProvider {
+ /**
+ * The view has snapped to an item, with a motion from a given {@link MotionEvent} on a given
+ * {@code axis}.
+ *
+ * <p>The interface is not aware of the internal scroll states of the view for which scroll
+ * feedback is played. As such, the client should call
+ * {@link #onScrollLimit(MotionEvent, int, int)} when scrolling has reached limit.
+ *
+ * @param event the {@link MotionEvent} that caused the item to snap.
+ * @param axis the axis of {@code event} that caused the item to snap.
+ */
+ void onSnapToItem(MotionEvent event, int axis);
+
+ /**
+ * The view has reached the scroll limit when scrolled by the motion from a given
+ * {@link MotionEvent} on a given {@code axis}.
+ *
+ * @param event the {@link MotionEvent} that caused scrolling to hit the limit.
+ * @param axis the axis of {@code event} that caused scrolling to hit the limit.
+ * @param isStart {@code true} if scrolling hit limit at the start of the scrolling list, and
+ * {@code false} if the scrolling hit limit at the end of the scrolling list.
+ */
+ void onScrollLimit(MotionEvent event, int axis, boolean isStart);
+
+ /**
+ * The view has scrolled by {@code deltaInPixels} due to the motion from a given
+ * {@link MotionEvent} on a given {@code axis}.
+ *
+ * <p>The interface is not aware of the internal scroll states of the view for which scroll
+ * feedback is played. As such, the client should call
+ * {@link #onScrollLimit(MotionEvent, int, int)} when scrolling has reached limit.
+ *
+ * @param event the {@link MotionEvent} that caused scroll progress.
+ * @param axis the axis of {@code event} that caused scroll progress.
+ * @param deltaInPixels the amount of scroll progress, in pixels.
+ */
+ void onScrollProgress(MotionEvent event, int axis, int deltaInPixels);
+}
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 36954a9941c4..38e3526ac6b8 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -8414,6 +8414,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* for the matching <activity> entry in your application’s manifest or updating the title at
* runtime with{@link android.app.Activity#setTitle(CharSequence)}.
*
+ * <p>
+ * <b>Note:</b> Use
+ * {@link androidx.core.view.ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}
+ * for backwards-compatibility. </aside>
* @param accessibilityPaneTitle The pane's title. Setting to {@code null} indicates that this
* View is not a pane.
*
@@ -9329,8 +9333,12 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
final AccessibilityNodeInfo info = createAccessibilityNodeInfo();
structure.setChildCount(1);
final ViewStructure root = structure.newChild(0);
- populateVirtualStructure(root, provider, info, forAutofill);
- info.recycle();
+ if (info != null) {
+ populateVirtualStructure(root, provider, info, forAutofill);
+ info.recycle();
+ } else {
+ Log.w(AUTOFILL_LOG_TAG, "AccessibilityNodeInfo is null.");
+ }
}
}
@@ -13717,6 +13725,10 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
/**
* Returns whether the view should be treated as a focusable unit by screen reader
* accessibility tools.
+ * <p>
+ * <b>Note:</b> Use
+ * {@link androidx.core.view.ViewCompat#setScreenReaderFocusable(View, boolean)}
+ * for backwards-compatibility. </aside>
* @see #setScreenReaderFocusable(boolean)
*
* @return Whether the view should be treated as a focusable unit by screen reader.
@@ -13762,6 +13774,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Users of some accessibility services can choose to navigate between headings
* instead of between paragraphs, words, etc. Apps that provide headings on
* sections of text can help the text navigation experience.
+ * <p>
+ * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityHeading(View, boolean)}
+ * for backwards-compatibility. </aside>
*
* @param isHeading {@code true} if the view is a heading, {@code false} otherwise.
*
@@ -14622,6 +14637,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* <p>
* If the view's changes should interrupt ongoing speech and notify the user
* immediately, use {@link #ACCESSIBILITY_LIVE_REGION_ASSERTIVE}.
+ * <p>
+ * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#setAccessibilityLiveRegion(View, int)}
+ * for backwards-compatibility. </aside>
*
* @param mode The live region mode for this view, one of:
* <ul>
@@ -31419,6 +31437,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback,
* Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
* methods are called <i>after</i> host methods, which all properties to be
* modified without being overwritten by the host class.
+ * <aside class="note">
+ * <b>Note:</b> Use a {@link androidx.core.view.AccessibilityDelegateCompat}
+ * wrapper instead of this class for backwards-compatibility.
+ * </aside>
+ *
*/
public static class AccessibilityDelegate {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 20b1fed02502..d110ecb15c44 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1899,9 +1899,10 @@ public final class ViewRootImpl implements ViewParent,
&& !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean compatScaleChanged = mTmpFrames.compatScale != compatScale;
+ final boolean dragResizingChanged = mPendingDragResizing != dragResizing;
if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
&& !displayChanged && !forceNextWindowRelayout
- && !compatScaleChanged) {
+ && !compatScaleChanged && !dragResizingChanged) {
return;
}
@@ -2425,7 +2426,7 @@ public final class ViewRootImpl implements ViewParent,
*
* @hide
*/
- void notifyRendererOfExpensiveFrame() {
+ public void notifyRendererOfExpensiveFrame() {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.notifyExpensiveFrame();
}
@@ -7016,6 +7017,10 @@ public final class ViewRootImpl implements ViewParent,
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
boolean handled = mHandwritingInitiator.onTouchEvent(event);
+ if (handled) {
+ // If handwriting is started, toolkit doesn't receive ACTION_UP.
+ mLastClickToolType = event.getToolType(event.getActionIndex());
+ }
mAttachInfo.mUnbufferedDispatchRequested = false;
mAttachInfo.mHandlingPointerEvent = true;
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 7596459b130b..2f04b0c695da 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -658,6 +658,12 @@ public abstract class Window {
void updateStatusBarColor(int color);
/**
+ * Update the status bar appearance.
+ */
+
+ void updateStatusBarAppearance(int appearance);
+
+ /**
* Update the navigation bar color to a forced one.
*/
void updateNavigationBarColor(int color);
@@ -1039,6 +1045,9 @@ public abstract class Window {
if (mDecorCallback != null) {
mDecorCallback.onSystemBarAppearanceChanged(appearance);
}
+ if (mWindowControllerCallback != null) {
+ mWindowControllerCallback.updateStatusBarAppearance(appearance);
+ }
}
/** @hide */
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index d40c032eb21c..d702367965a1 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -435,11 +435,15 @@ public interface WindowManager extends ViewManager {
int TRANSIT_KEYGUARD_GOING_AWAY = 7;
/**
* A window is appearing above a locked keyguard.
+ * @deprecated use {@link #TRANSIT_TO_FRONT} + {@link #TRANSIT_FLAG_KEYGUARD_OCCLUDING} for
+ * keyguard occluding with Shell transition.
* @hide
*/
int TRANSIT_KEYGUARD_OCCLUDE = 8;
/**
* A window is made invisible revealing a locked keyguard.
+ * @deprecated use {@link #TRANSIT_TO_BACK} + {@link #TRANSIT_FLAG_KEYGUARD_UNOCCLUDING} for
+ * keyguard occluding with Shell transition.
* @hide
*/
int TRANSIT_KEYGUARD_UNOCCLUDE = 9;
@@ -562,6 +566,25 @@ public interface WindowManager extends ViewManager {
int TRANSIT_FLAG_INVISIBLE = (1 << 10); // 0x400
/**
+ * Transition flag: Indicates that keyguard will be showing (locked) with this transition,
+ * which is the opposite of {@link #TRANSIT_FLAG_KEYGUARD_GOING_AWAY}.
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_APPEARING = (1 << 11); // 0x800
+
+ /**
+ * Transition flag: Indicates that keyguard is becoming hidden by an app
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_OCCLUDING = (1 << 12); // 0x1000
+
+ /**
+ * Transition flag: Indicates that keyguard is being revealed after an app was occluding it.
+ * @hide
+ */
+ int TRANSIT_FLAG_KEYGUARD_UNOCCLUDING = (1 << 13); // 0x2000
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = { "TRANSIT_FLAG_" }, value = {
@@ -576,11 +599,28 @@ public interface WindowManager extends ViewManager {
TRANSIT_FLAG_KEYGUARD_GOING_AWAY,
TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT,
TRANSIT_FLAG_INVISIBLE,
+ TRANSIT_FLAG_KEYGUARD_APPEARING,
+ TRANSIT_FLAG_KEYGUARD_OCCLUDING,
+ TRANSIT_FLAG_KEYGUARD_UNOCCLUDING
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionFlags {}
/**
+ * Transit flags used to signal keyguard visibility is changing for animations.
+ *
+ * <p>These roughly correspond to CLOSE, OPEN, TO_BACK, and TO_FRONT on a hypothetical Keyguard
+ * container. Since Keyguard isn't a container we can't include it in changes and need to send
+ * this information in its own channel.
+ * @hide
+ */
+ int KEYGUARD_VISIBILITY_TRANSIT_FLAGS =
+ (TRANSIT_FLAG_KEYGUARD_GOING_AWAY
+ | TRANSIT_FLAG_KEYGUARD_APPEARING
+ | TRANSIT_FLAG_KEYGUARD_OCCLUDING
+ | TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
+
+ /**
* Remove content mode: Indicates remove content mode is currently not defined.
* @hide
*/
@@ -1344,15 +1384,28 @@ public interface WindowManager extends ViewManager {
"android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED";
/**
- * Request for keyboard shortcuts to be retrieved asynchronously.
+ * Request for app's keyboard shortcuts to be retrieved asynchronously.
*
* @param receiver The callback to be triggered when the result is ready.
+ * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+ * not triggered by a KeyEvent.
*
* @hide
*/
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
/**
+ * Request for ime's keyboard shortcuts to be retrieved asynchronously.
+ *
+ * @param receiver The callback to be triggered when the result is ready.
+ * @param deviceId The deviceId of KeyEvent by which this request is triggered, or -1 if it's
+ * not triggered by a KeyEvent.
+ *
+ * @hide
+ */
+ default void requestImeKeyboardShortcuts(KeyboardShortcutsReceiver receiver, int deviceId) {};
+
+ /**
* Return the touch region for the current IME window, or an empty region if there is none.
*
* @return The region of the IME that is accepting touch inputs, or null if there is no IME, no
@@ -3763,6 +3816,7 @@ public interface WindowManager extends ViewManager {
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @TestApi
public float preferredMinDisplayRefreshRate;
/**
@@ -3771,6 +3825,7 @@ public interface WindowManager extends ViewManager {
* This value is ignored if {@link #preferredDisplayModeId} is set.
* @hide
*/
+ @TestApi
public float preferredMaxDisplayRefreshRate;
/** Indicates whether this window wants the HDR conversion is disabled. */
diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java
index df3e0bb74292..b57163c4e435 100644
--- a/core/java/android/view/WindowManagerImpl.java
+++ b/core/java/android/view/WindowManagerImpl.java
@@ -215,14 +215,36 @@ public final class WindowManagerImpl implements WindowManager {
@Override
public void send(int resultCode, Bundle resultData) throws RemoteException {
List<KeyboardShortcutGroup> result =
- resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY, android.view.KeyboardShortcutGroup.class);
+ resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+ android.view.KeyboardShortcutGroup.class);
receiver.onKeyboardShortcutsReceived(result);
}
};
try {
WindowManagerGlobal.getWindowManagerService()
- .requestAppKeyboardShortcuts(resultReceiver, deviceId);
+ .requestAppKeyboardShortcuts(resultReceiver, deviceId);
} catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void requestImeKeyboardShortcuts(
+ final KeyboardShortcutsReceiver receiver, int deviceId) {
+ IResultReceiver resultReceiver = new IResultReceiver.Stub() {
+ @Override
+ public void send(int resultCode, Bundle resultData) throws RemoteException {
+ List<KeyboardShortcutGroup> result =
+ resultData.getParcelableArrayList(PARCEL_KEY_SHORTCUTS_ARRAY,
+ android.view.KeyboardShortcutGroup.class);
+ receiver.onKeyboardShortcutsReceived(result);
+ }
+ };
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .requestImeKeyboardShortcuts(resultReceiver, deviceId);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
}
}
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index b31aa288feac..7199f60e93b0 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -100,6 +100,9 @@ import java.util.Objects;
* <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
* developer guide.</p>
* </div>
+ * <aside class="note">
+ * <b>Note:</b> Use a {@link androidx.core.view.accessibility.AccessibilityNodeInfoCompat}
+ * wrapper instead of this class for backwards-compatibility. </aside>
*
* @see android.accessibilityservice.AccessibilityService
* @see AccessibilityEvent
@@ -1571,6 +1574,10 @@ public class AccessibilityNodeInfo implements Parcelable {
* describes the action.
* </p>
* <p>
+ * Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
+ * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
+ * view.
+ * <p>
* <strong>Note:</strong> Cannot be called from an
* {@link android.accessibilityservice.AccessibilityService}.
* This class is made immutable before being delivered to an AccessibilityService.
@@ -5149,12 +5156,17 @@ public class AccessibilityNodeInfo implements Parcelable {
* {@link View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo)} and are performed
* within {@link View#performAccessibilityAction(int, Bundle)}.
* </p>
- * <p class="note">
- * <strong>Note:</strong> Views which support these actions should invoke
+ * <aside class="note">
+ * <b>Note:</b> Views which support these actions should invoke
* {@link View#setImportantForAccessibility(int)} with
* {@link View#IMPORTANT_FOR_ACCESSIBILITY_YES} to ensure an {@link AccessibilityService}
* can discover the set of supported actions.
* </p>
+ * <aside class="note">
+ * <b>Note:</b> Use {@link androidx.core.view.ViewCompat#addAccessibilityAction(View,
+ * AccessibilityNodeInfoCompat.AccessibilityActionCompat)} to register an action directly on the
+ * view.
+ * </p>
*/
public static final class AccessibilityAction implements Parcelable {
diff --git a/core/java/android/view/autofill/AutofillFeatureFlags.java b/core/java/android/view/autofill/AutofillFeatureFlags.java
index 1960d6546c9a..106a77b80627 100644
--- a/core/java/android/view/autofill/AutofillFeatureFlags.java
+++ b/core/java/android/view/autofill/AutofillFeatureFlags.java
@@ -292,6 +292,9 @@ public class AutofillFeatureFlags {
private static final String DEFAULT_AFAA_NON_AUTOFILLABLE_IME_ACTIONS = "3,4";
private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_AUTOFILL_ON_ALL_VIEW_TYPES = true;
private static final boolean DEFAULT_AFAA_SHOULD_ENABLE_MULTILINE_FILTER = true;
+ private static final boolean
+ DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE = true;
+ // END AUTOFILL FOR ALL APPS DEFAULTS
private AutofillFeatureFlags() {};
@@ -447,7 +450,8 @@ public class AutofillFeatureFlags {
public static boolean shouldIncludeAllViewsAutofillTypeNotNoneInAssistStructrue() {
return DeviceConfig.getBoolean(
DeviceConfig.NAMESPACE_AUTOFILL,
- DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE, false);
+ DEVICE_CONFIG_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE,
+ DEFAULT_AFAA_SHOULD_INCLUDE_ALL_AUTOFILL_TYPE_NOT_NONE_VIEWS_IN_ASSIST_STRUCTURE);
}
/**
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index c37e311209df..c9afdc0ad074 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -52,6 +52,7 @@ import android.view.contentcapture.ContentCaptureSession.FlushReason;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
import com.android.internal.util.SyncResultReceiver;
import java.io.PrintWriter;
@@ -446,6 +447,9 @@ public final class ContentCaptureManager {
@Nullable // set on-demand by addDumpable()
private Dumper mDumpable;
+ // Created here in order to live across activity and session changes
+ @Nullable private final RingBuffer<ContentCaptureEvent> mContentProtectionEventBuffer;
+
/** @hide */
public interface ContentCaptureClient {
/**
@@ -456,12 +460,15 @@ public final class ContentCaptureManager {
}
/** @hide */
- static class StrippedContext {
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public static class StrippedContext {
@NonNull final String mPackageName;
@NonNull final String mContext;
final @UserIdInt int mUserId;
- private StrippedContext(@NonNull Context context) {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public StrippedContext(@NonNull Context context) {
mPackageName = context.getPackageName();
mContext = context.toString();
mUserId = context.getUserId();
@@ -502,6 +509,16 @@ public final class ContentCaptureManager {
mHandler = Handler.createAsync(Looper.getMainLooper());
mDataShareAdapterResourceManager = new LocalDataShareAdapterResourceManager();
+
+ if (mOptions.contentProtectionOptions.enableReceiver
+ && mOptions.contentProtectionOptions.bufferSize > 0) {
+ mContentProtectionEventBuffer =
+ new RingBuffer(
+ ContentCaptureEvent.class,
+ mOptions.contentProtectionOptions.bufferSize);
+ } else {
+ mContentProtectionEventBuffer = null;
+ }
}
/**
@@ -870,6 +887,13 @@ public final class ContentCaptureManager {
activity.addDumpable(mDumpable);
}
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ @Nullable
+ public RingBuffer<ContentCaptureEvent> getContentProtectionEventBuffer() {
+ return mContentProtectionEventBuffer;
+ }
+
// NOTE: ContentCaptureManager cannot implement it directly as it would be exposed as public API
private final class Dumper implements Dumpable {
@Override
diff --git a/core/java/android/view/contentcapture/IContentCaptureManager.aidl b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
index a64111069c9b..14879977d2a5 100644
--- a/core/java/android/view/contentcapture/IContentCaptureManager.aidl
+++ b/core/java/android/view/contentcapture/IContentCaptureManager.aidl
@@ -17,6 +17,7 @@
package android.view.contentcapture;
import android.content.ComponentName;
+import android.content.pm.ParceledListSlice;
import android.view.contentcapture.ContentCaptureContext;
import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.DataRemovalRequest;
@@ -108,4 +109,9 @@ oneway interface IContentCaptureManager {
*/
void registerContentCaptureOptionsCallback(String packageName,
in IContentCaptureOptionsCallback callback);
+
+ /**
+ * Notifies the system server that a login was detected.
+ */
+ void onLoginDetected(in ParceledListSlice<ContentCaptureEvent> events);
}
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
new file mode 100644
index 000000000000..e49df21692c5
--- /dev/null
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UiThread;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.text.InputType;
+import android.util.Log;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
+ *
+ * @hide
+ */
+public class ContentProtectionEventProcessor {
+
+ private static final String TAG = "ContentProtectionEventProcessor";
+
+ private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
+ Collections.unmodifiableList(
+ Arrays.asList(
+ InputType.TYPE_NUMBER_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
+ InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
+
+ private static final List<String> PASSWORD_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("password", "pass word", "code", "pin", "credential"));
+
+ private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
+ Collections.unmodifiableList(
+ Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
+
+ private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
+
+ private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
+
+ private static final Set<Integer> EVENT_TYPES_TO_STORE =
+ Collections.unmodifiableSet(
+ new HashSet<>(
+ Arrays.asList(
+ ContentCaptureEvent.TYPE_VIEW_APPEARED,
+ ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+ ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+
+ @NonNull private final RingBuffer<ContentCaptureEvent> mEventBuffer;
+
+ @NonNull private final Handler mHandler;
+
+ @NonNull private final IContentCaptureManager mContentCaptureManager;
+
+ @NonNull private final String mPackageName;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mPasswordFieldDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ public boolean mSuspiciousTextDetected = false;
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ public Instant mLastFlushTime;
+
+ public ContentProtectionEventProcessor(
+ @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
+ @NonNull Handler handler,
+ @NonNull IContentCaptureManager contentCaptureManager,
+ @NonNull String packageName) {
+ mEventBuffer = eventBuffer;
+ mHandler = handler;
+ mContentCaptureManager = contentCaptureManager;
+ mPackageName = packageName;
+ }
+
+ /** Main entry point for {@link ContentCaptureEvent} processing. */
+ @UiThread
+ public void processEvent(@NonNull ContentCaptureEvent event) {
+ if (EVENT_TYPES_TO_STORE.contains(event.getType())) {
+ storeEvent(event);
+ }
+ if (event.getType() == ContentCaptureEvent.TYPE_VIEW_APPEARED) {
+ processViewAppearedEvent(event);
+ }
+ }
+
+ @UiThread
+ private void storeEvent(@NonNull ContentCaptureEvent event) {
+ // Ensure receiver gets the package name which might not be set
+ ViewNode viewNode = (event.getViewNode() != null) ? event.getViewNode() : new ViewNode();
+ viewNode.setTextIdEntry(mPackageName);
+ event.setViewNode(viewNode);
+ mEventBuffer.append(event);
+ }
+
+ @UiThread
+ private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
+ mPasswordFieldDetected |= isPasswordField(event);
+ mSuspiciousTextDetected |= isSuspiciousText(event);
+ if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+ loginDetected();
+ }
+ }
+
+ @UiThread
+ private void loginDetected() {
+ if (mLastFlushTime == null
+ || Instant.now().isAfter(mLastFlushTime.plus(MIN_DURATION_BETWEEN_FLUSHING))) {
+ flush();
+ }
+ mPasswordFieldDetected = false;
+ mSuspiciousTextDetected = false;
+ }
+
+ @UiThread
+ private void flush() {
+ mLastFlushTime = Instant.now();
+
+ // Note the thread annotations, do not move clearEvents to mHandler
+ ParceledListSlice<ContentCaptureEvent> events = clearEvents();
+ mHandler.post(() -> handlerOnLoginDetected(events));
+ }
+
+ @UiThread
+ @NonNull
+ private ParceledListSlice<ContentCaptureEvent> clearEvents() {
+ List<ContentCaptureEvent> events = Arrays.asList(mEventBuffer.toArray());
+ mEventBuffer.clear();
+ return new ParceledListSlice<>(events);
+ }
+
+ private void handlerOnLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+ try {
+ mContentCaptureManager.onLoginDetected(events);
+ } catch (Exception ex) {
+ Log.e(TAG, "Failed to flush events for: " + mPackageName, ex);
+ }
+ }
+
+ private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
+ return isPasswordField(event.getViewNode());
+ }
+
+ private boolean isPasswordField(@Nullable ViewNode viewNode) {
+ if (viewNode == null) {
+ return false;
+ }
+ return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
+ }
+
+ private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
+ if (!isAndroidViewNode(viewNode)) {
+ return false;
+ }
+ int inputType = viewNode.getInputType();
+ return PASSWORD_FIELD_INPUT_TYPES.stream()
+ .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
+ }
+
+ private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
+ if (viewNode.getClassName() != null) {
+ return false;
+ }
+ return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
+ }
+
+ private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
+ String className = viewNode.getClassName();
+ return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
+ }
+
+ private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
+ return isSuspiciousText(ContentProtectionUtils.getEventText(event))
+ || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
+ }
+
+ private boolean isSuspiciousText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ if (isPasswordText(text)) {
+ return true;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
+ .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
+ }
+
+ private boolean isPasswordText(@Nullable String text) {
+ if (text == null) {
+ return false;
+ }
+ String lowerCaseText = text.toLowerCase();
+ return PASSWORD_TEXTS.stream()
+ .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
+ }
+}
diff --git a/core/java/android/view/textclassifier/TextClassification.java b/core/java/android/view/textclassifier/TextClassification.java
index 8b04d35734ec..63bf5622fbb8 100644
--- a/core/java/android/view/textclassifier/TextClassification.java
+++ b/core/java/android/view/textclassifier/TextClassification.java
@@ -21,6 +21,7 @@ import android.annotation.IntDef;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.app.RemoteAction;
import android.content.Context;
@@ -301,7 +302,8 @@ public final class TextClassification implements Parcelable {
Objects.requireNonNull(intent);
return v -> {
try {
- intent.send();
+ intent.send(ActivityOptions.makeBasic().setPendingIntentBackgroundActivityStartMode(
+ ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED).toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(LOG_TAG, "Error sending PendingIntent", e);
}
diff --git a/core/java/android/webkit/OWNERS b/core/java/android/webkit/OWNERS
index b33da57c42e3..e7fd7a5d1096 100644
--- a/core/java/android/webkit/OWNERS
+++ b/core/java/android/webkit/OWNERS
@@ -1,4 +1,3 @@
-changwan@google.com
+# Bug component: 76427
ntfschr@google.com
-tobiasjs@google.com
torne@google.com
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 31cfed5376a6..8af3ac66f1a7 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -232,6 +232,7 @@ public class RemoteViews implements Parcelable, Filter {
private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
+ private static final int SET_REMOTE_ADAPTER_TAG = 33;
/** @hide **/
@IntDef(prefix = "MARGIN_", value = {
@@ -960,6 +961,11 @@ public class RemoteViews implements Parcelable, Filter {
return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
}
+ @Override
+ public String getUniqueKey() {
+ return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+ }
+
int viewTypeCount;
ArrayList<RemoteViews> list;
}
@@ -1082,6 +1088,11 @@ public class RemoteViews implements Parcelable, Filter {
public int getActionTag() {
return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
}
+
+ @Override
+ public String getUniqueKey() {
+ return (SET_REMOTE_ADAPTER_TAG + "_" + viewId);
+ }
}
private class SetRemoteViewsAdapterIntent extends Action {
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index ee6ac1297b63..65984f55ded2 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -155,8 +155,7 @@ public class Toast {
* Construct an empty Toast object. You must call {@link #setView} before you
* can call {@link #show}.
*
- * @param context The context to use. Usually your {@link android.app.Application}
- * or {@link android.app.Activity} object.
+ * @param context The context to use. Usually your {@link android.app.Activity} object.
*/
public Toast(Context context) {
this(context, null);
@@ -482,8 +481,7 @@ public class Toast {
/**
* Make a standard toast that just contains text.
*
- * @param context The context to use. Usually your {@link android.app.Application}
- * or {@link android.app.Activity} object.
+ * @param context The context to use. Usually your {@link android.app.Activity} object.
* @param text The text to show. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
@@ -539,8 +537,7 @@ public class Toast {
/**
* Make a standard toast that just contains text from a resource.
*
- * @param context The context to use. Usually your {@link android.app.Application}
- * or {@link android.app.Activity} object.
+ * @param context The context to use. Usually your {@link android.app.Activity} object.
* @param resId The resource id of the string resource to use. Can be formatted text.
* @param duration How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG}
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8c05130bf5fe..59238b40e0c8 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -99,9 +99,6 @@ public final class TransitionInfo implements Parcelable {
/** The container is the display. */
public static final int FLAG_IS_DISPLAY = 1 << 5;
- /** The container can show on top of lock screen. */
- public static final int FLAG_OCCLUDES_KEYGUARD = 1 << 6;
-
/**
* Only for IS_DISPLAY containers. Is set if the display has system alert windows. This is
* used to prevent seamless rotation.
@@ -175,7 +172,6 @@ public final class TransitionInfo implements Parcelable {
FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT,
FLAG_IS_VOICE_INTERACTION,
FLAG_IS_DISPLAY,
- FLAG_OCCLUDES_KEYGUARD,
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
FLAG_IS_INPUT_METHOD,
FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY,
@@ -424,8 +420,8 @@ public final class TransitionInfo implements Parcelable {
case TRANSIT_NONE: return "NONE";
case TRANSIT_OPEN: return "OPEN";
case TRANSIT_CLOSE: return "CLOSE";
- case TRANSIT_TO_FRONT: return "SHOW";
- case TRANSIT_TO_BACK: return "HIDE";
+ case TRANSIT_TO_FRONT: return "TO_FRONT";
+ case TRANSIT_TO_BACK: return "TO_BACK";
case TRANSIT_CHANGE: return "CHANGE";
default: return "<unknown:" + mode + ">";
}
@@ -457,9 +453,6 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_IS_DISPLAY) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("IS_DISPLAY");
}
- if ((flags & FLAG_OCCLUDES_KEYGUARD) != 0) {
- sb.append(sb.length() == 0 ? "" : "|").append("OCCLUDES_KEYGUARD");
- }
if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
sb.append(sb.length() == 0 ? "" : "|").append("DISPLAY_HAS_ALERT_WINDOWS");
}
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index 421d9983d6b7..632208cdb97c 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -163,19 +163,25 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
// Re-populate the top callback to WM if the removed callback was previously the top one.
if (previousTopCallback == callback) {
// We should call onBackCancelled() when an active callback is removed from dispatcher.
- if (mProgressAnimator.isBackAnimationInProgress()
- && callback instanceof OnBackAnimationCallback) {
- // The ProgressAnimator will handle the new topCallback, so we don't want to call
- // onBackCancelled() on it. We call immediately the callback instead.
- OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
- animatedCallback.onBackCancelled();
- Log.d(TAG, "The callback was removed while a back animation was in progress, "
- + "an onBackCancelled() was dispatched.");
- }
+ sendCancelledIfInProgress(callback);
setTopOnBackInvokedCallback(getTopCallback());
}
}
+ private void sendCancelledIfInProgress(@NonNull OnBackInvokedCallback callback) {
+ boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
+ if (isInProgress && callback instanceof OnBackAnimationCallback) {
+ OnBackAnimationCallback animatedCallback = (OnBackAnimationCallback) callback;
+ animatedCallback.onBackCancelled();
+ if (DEBUG) {
+ Log.d(TAG, "sendCancelIfRunning: callback canceled");
+ }
+ } else {
+ Log.w(TAG, "sendCancelIfRunning: isInProgress=" + isInProgress
+ + "callback=" + callback);
+ }
+ }
+
@Override
public void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) {
registerOnBackInvokedCallbackUnchecked(callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM);
@@ -188,9 +194,20 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
mImeDispatcher = null;
}
if (!mAllCallbacks.isEmpty()) {
+ OnBackInvokedCallback topCallback = getTopCallback();
+ if (topCallback != null) {
+ sendCancelledIfInProgress(topCallback);
+ } else {
+ // Should not be possible
+ Log.e(TAG, "There is no topCallback, even if mAllCallbacks is not empty");
+ }
// Clear binder references in WM.
setTopOnBackInvokedCallback(null);
}
+
+ // We should also stop running animations since all callbacks have been removed.
+ // note: mSpring.skipToEnd(), in ProgressAnimator.reset(), requires the main handler.
+ Handler.getMain().post(mProgressAnimator::reset);
mAllCallbacks.clear();
mOnBackInvokedCallbacks.clear();
}
@@ -342,12 +359,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
@Override
public void onBackInvoked() throws RemoteException {
Handler.getMain().post(() -> {
+ boolean isInProgress = mProgressAnimator.isBackAnimationInProgress();
mProgressAnimator.reset();
final OnBackInvokedCallback callback = mCallbackRef.get();
if (callback == null) {
Log.d(TAG, "Trying to call onBackInvoked() on a null callback reference.");
return;
}
+ if (callback instanceof OnBackAnimationCallback && !isInProgress) {
+ Log.w(TAG, "ProgressAnimator was not in progress, skip onBackInvoked().");
+ return;
+ }
callback.onBackInvoked();
});
}
diff --git a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
index 3d95dd341cc0..c9e76009136a 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiSystemPropertiesFlags.java
@@ -81,6 +81,10 @@ public class SystemUiSystemPropertiesFlags {
/** Gating the logging of DND state change events. */
public static final Flag LOG_DND_STATE_EVENTS =
releasedFlag("persist.sysui.notification.log_dnd_state_events");
+
+ /** Gating the holding of WakeLocks until NLSes are told about a new notification. */
+ public static final Flag WAKE_LOCK_FOR_POSTING_NOTIFICATION =
+ devFlag("persist.sysui.notification.wake_lock_for_posting_notification");
}
//// == End of flags. Everything below this line is the implementation. == ////
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 0f41229dd0c1..5c1d91ff540e 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -278,7 +278,7 @@ int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data)
if (events & ALOOPER_EVENT_INPUT) {
JNIEnv* env = AndroidRuntime::getJNIEnv();
- status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
+ status_t status = consumeEvents(env, /*consumeBatches=*/false, -1, nullptr);
mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
}
@@ -398,7 +398,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.onFocusEvent,
jboolean(focusEvent->getHasFocus()));
- finishInputEvent(seq, true /* handled */);
+ finishInputEvent(seq, /*handled=*/true);
continue;
}
case InputEventType::CAPTURE: {
@@ -411,7 +411,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.onPointerCaptureEvent,
jboolean(captureEvent->getPointerCaptureEnabled()));
- finishInputEvent(seq, true /* handled */);
+ finishInputEvent(seq, /*handled=*/true);
continue;
}
case InputEventType::DRAG: {
@@ -423,7 +423,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onDragEvent,
jboolean(dragEvent->isExiting()), dragEvent->getX(),
dragEvent->getY());
- finishInputEvent(seq, true /* handled */);
+ finishInputEvent(seq, /*handled=*/true);
continue;
}
case InputEventType::TOUCH_MODE: {
@@ -436,7 +436,7 @@ status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
env->CallVoidMethod(receiverObj.get(),
gInputEventReceiverClassInfo.onTouchModeChanged,
jboolean(touchModeEvent->isInTouchMode()));
- finishInputEvent(seq, true /* handled */);
+ finishInputEvent(seq, /*handled=*/true);
continue;
}
@@ -571,8 +571,8 @@ static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong
sp<NativeInputEventReceiver> receiver =
reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
bool consumedBatch;
- status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos,
- &consumedBatch);
+ status_t status =
+ receiver->consumeEvents(env, /*consumeBatches=*/true, frameTimeNanos, &consumedBatch);
if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) {
std::string message =
android::base::StringPrintf("Failed to consume batched input event. status=%s(%d)",
diff --git a/core/jni/android_view_InputQueue.cpp b/core/jni/android_view_InputQueue.cpp
index 21db37e1ef7c..a0d081d2cd26 100644
--- a/core/jni/android_view_InputQueue.cpp
+++ b/core/jni/android_view_InputQueue.cpp
@@ -239,7 +239,7 @@ static jlong nativeSendMotionEvent(JNIEnv* env, jobject clazz, jlong ptr, jobjec
return -1;
}
MotionEvent* event = queue->createMotionEvent();
- event->copyFrom(originalEvent, true /* keepHistory */);
+ event->copyFrom(originalEvent, /*keepHistory=*/true);
queue->enqueueEvent(event);
return reinterpret_cast<jlong>(event);
}
diff --git a/core/jni/android_view_KeyCharacterMap.cpp b/core/jni/android_view_KeyCharacterMap.cpp
index 8fa03cfc5b6f..ddaeb5a4d272 100644
--- a/core/jni/android_view_KeyCharacterMap.cpp
+++ b/core/jni/android_view_KeyCharacterMap.cpp
@@ -76,7 +76,7 @@ jobject android_view_KeyCharacterMap_create(JNIEnv* env, int32_t deviceId,
reinterpret_cast<jlong>(nativeMap));
}
-static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, jobject /* clazz */, jint deviceId) {
+static jobject nativeObtainEmptyKeyCharacterMap(JNIEnv* env, /*clazz=*/jobject, jint deviceId) {
return android_view_KeyCharacterMap_create(env, deviceId, nullptr);
}
@@ -202,7 +202,7 @@ static jobjectArray nativeGetEvents(JNIEnv *env, jobject clazz, jlong ptr,
jcharArray charsArray) {
NativeKeyCharacterMap* map = reinterpret_cast<NativeKeyCharacterMap*>(ptr);
if (!map || !map->getMap()) {
- return env->NewObjectArray(0 /* size */, gKeyEventClassInfo.clazz, NULL);
+ return env->NewObjectArray(/*size=*/0, gKeyEventClassInfo.clazz, NULL);
}
jchar* chars = env->GetCharArrayElements(charsArray, NULL);
if (!chars) {
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 791d46332868..9017d58913b3 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -149,7 +149,12 @@ copyFileIfChanged(JNIEnv *env, void* arg, ZipFileRO* zipFile, ZipEntryRO zipEntr
return INSTALL_FAILED_INVALID_APK;
}
- if (!extractNativeLibs && (!debuggable || strcmp(fileName, "wrap.sh") != 0)) {
+ // Always extract wrap.sh for debuggable, even if extractNativeLibs=false. This makes it
+ // easier to use wrap.sh because it only works when it is extracted, see
+ // frameworks/base/services/core/java/com/android/server/am/ProcessList.java.
+ bool forceExtractCurrentFile = debuggable && strcmp(fileName, "wrap.sh") == 0;
+
+ if (!extractNativeLibs && !forceExtractCurrentFile) {
// check if library is uncompressed and page-aligned
if (method != ZipFileRO::kCompressStored) {
ALOGE("Library '%s' is compressed - will not be able to open it directly from apk.\n",
diff --git a/core/res/res/layout/alert_dialog_button_bar_leanback.xml b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
index ea94af662dcf..466811f6d116 100644
--- a/core/res/res/layout/alert_dialog_button_bar_leanback.xml
+++ b/core/res/res/layout/alert_dialog_button_bar_leanback.xml
@@ -21,13 +21,13 @@
android:layout_height="wrap_content"
android:scrollbarAlwaysDrawVerticalTrack="true"
android:scrollIndicators="top|bottom"
- android:fillViewport="true"
- style="?attr/buttonBarStyle">
+ android:fillViewport="true">
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutDirection="locale"
android:orientation="horizontal"
+ style="?attr/buttonBarStyle"
android:gravity="start">
<Button
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 6d8d368ab352..06ba4da2360f 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1679,16 +1679,56 @@
in the config_autoBrightnessLevels array. This array should have size one greater
than the size of the config_autoBrightnessLevels array.
The brightness values must be between 0 and 255 and be non-decreasing.
+
This must be overridden in platform specific overlays -->
<integer-array name="config_autoBrightnessButtonBacklightValues">
</integer-array>
+ <!-- Smoothing constant for Ambient keyboard backlight change. It should contain value
+ in the range (0.0, 1.0] that will be used to calculate smoothed lux values using
+ simple exponential smoothing. This value indicated how quickly we transition to
+ the lux values provided by the Ambient light sensor.
+ Simple formula for newLuxValue = (1-constant)*currLuxValue + constant*rawLuxValue, where
+ rawLuxValue is the value provided by the Ambient light sensor. (e.g. value of 1.0 means we
+ immediately start using the value provided by the Ambient light sensor)
+ This must be overridden in platform specific overlays -->
+ <item name="config_autoKeyboardBrightnessSmoothingConstant" format="float" type="dimen">
+ 1.0
+ </item>
+
<!-- Array of output values for keyboard backlight corresponding to the lux values
- in the config_autoBrightnessLevels array. This array should have size one greater
- than the size of the config_autoBrightnessLevels array.
+ in the config_autoKeyboardBacklight(Increase/Decrease)LuxThreshold arrays.
The brightness values must be between 0 and 255 and be non-decreasing.
- This must be overridden in platform specific overlays -->
- <integer-array name="config_autoBrightnessKeyboardBacklightValues">
+
+ This can be overridden in platform specific overlays -->
+ <integer-array name="config_autoKeyboardBacklightBrightnessValues">
+ <item>102</item>
+ <item>153</item>
+ <item>0</item>
+ </integer-array>
+
+ <!-- Array of threshold values for keyboard backlight corresponding to the values
+ in the config_autoKeyboardBacklightBrightnessValues array.
+ These lux values indicate when to move to a lower keyboard backlight value,
+ as defined in config_autoKeyboardBacklightBrightnessValues array.
+
+ This can be overridden in platform specific overlays -->
+ <integer-array name="config_autoKeyboardBacklightDecreaseLuxThreshold">
+ <item>-1</item>
+ <item>40</item>
+ <item>150</item>
+ </integer-array>
+
+ <!-- Array of threshold values for keyboard backlight corresponding to the values
+ in the config_autoKeyboardBacklightBrightnessValues array.
+ These lux values indicate when to move to a higher keyboard backlight value,
+ as defined in config_autoKeyboardBacklightBrightnessValues array.
+
+ This can be overridden in platform specific overlays -->
+ <integer-array name="config_autoKeyboardBacklightIncreaseLuxThreshold">
+ <item>55</item>
+ <item>200</item>
+ <item>-1</item>
</integer-array>
<!-- An array describing the screen's backlight values corresponding to the brightness
@@ -2766,6 +2806,11 @@
measured in dips per second. Setting this to -1dp disables rotary encoder fling. -->
<dimen name="config_viewMaxRotaryEncoderFlingVelocity">-1dp</dimen>
+ <!-- Tick intervals in dp for rotary encoder scrolls on {@link MotionEvent#AXIS_SCROLL}
+ generated by {@link InputDevice#SOURCE_ROTARY_ENCODER} devices. A valid tick interval value
+ is a positive value. Setting this to 0dp disables scroll tick. -->
+ <dimen name="config_rotaryEncoderAxisScrollTickInterval">21dp</dimen>
+
<!-- Amount of time in ms the user needs to press the relevant key to bring up the
global actions dialog -->
<integer name="config_globalActionsKeyTimeout">500</integer>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index b7e85f6526ee..16836fb0f634 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -522,6 +522,7 @@
<java-symbol type="dimen" name="config_viewConfigurationHandwritingSlop" />
<java-symbol type="dimen" name="config_viewConfigurationHoverSlop" />
<java-symbol type="dimen" name="config_ambiguousGestureMultiplier" />
+ <java-symbol type="dimen" name="config_autoKeyboardBrightnessSmoothingConstant" />
<java-symbol type="dimen" name="config_viewMinFlingVelocity" />
<java-symbol type="dimen" name="config_viewMaxFlingVelocity" />
<java-symbol type="dimen" name="config_viewMinRotaryEncoderFlingVelocity" />
@@ -1893,11 +1894,13 @@
<java-symbol type="anim" name="dream_activity_open_enter" />
<java-symbol type="anim" name="dream_activity_close_exit" />
<java-symbol type="array" name="config_autoBrightnessButtonBacklightValues" />
- <java-symbol type="array" name="config_autoBrightnessKeyboardBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues" />
<java-symbol type="array" name="config_autoBrightnessLcdBacklightValues_doze" />
<java-symbol type="array" name="config_autoBrightnessLevels" />
<java-symbol type="array" name="config_autoBrightnessLevelsIdle" />
+ <java-symbol type="array" name="config_autoKeyboardBacklightBrightnessValues" />
+ <java-symbol type="array" name="config_autoKeyboardBacklightDecreaseLuxThreshold" />
+ <java-symbol type="array" name="config_autoKeyboardBacklightIncreaseLuxThreshold" />
<java-symbol type="array" name="config_ambientThresholdLevels" />
<java-symbol type="array" name="config_ambientBrighteningThresholds" />
<java-symbol type="array" name="config_ambientDarkeningThresholds" />
@@ -2032,6 +2035,7 @@
<java-symbol type="integer" name="config_notificationsBatteryMediumARGB" />
<java-symbol type="integer" name="config_notificationsBatteryNearlyFullLevel" />
<java-symbol type="integer" name="config_notificationServiceArchiveSize" />
+ <java-symbol type="dimen" name="config_rotaryEncoderAxisScrollTickInterval" />
<java-symbol type="integer" name="config_previousVibrationsDumpLimit" />
<java-symbol type="integer" name="config_defaultVibrationAmplitude" />
<java-symbol type="dimen" name="config_hapticChannelMaxVibrationAmplitude" />
diff --git a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
index a7538701807a..0525443ecf82 100644
--- a/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorSetActivityTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import android.util.Property;
import android.view.View;
import androidx.test.annotation.UiThreadTest;
@@ -576,6 +577,42 @@ public class AnimatorSetActivityTest {
});
}
+ @Test
+ public void testInitializeWithoutReadingValues() throws Throwable {
+ // Some consumers crash while reading values before the animator starts
+ Property<int[], Integer> property = new Property<>(Integer.class, "firstValue") {
+ @Override
+ public Integer get(int[] target) {
+ throw new IllegalStateException("Shouldn't be called");
+ }
+
+ @Override
+ public void set(int[] target, Integer value) {
+ target[0] = value;
+ }
+ };
+
+ int[] target1 = new int[1];
+ int[] target2 = new int[1];
+ int[] target3 = new int[1];
+ ObjectAnimator animator1 = ObjectAnimator.ofInt(target1, property, 0, 100);
+ ObjectAnimator animator2 = ObjectAnimator.ofInt(target2, property, 0, 100);
+ ObjectAnimator animator3 = ObjectAnimator.ofInt(target3, property, 0, 100);
+ AnimatorSet set = new AnimatorSet();
+ set.playSequentially(animator1, animator2, animator3);
+
+ mActivityRule.runOnUiThread(() -> {
+ set.setCurrentPlayTime(900);
+ assertEquals(100, target1[0]);
+ assertEquals(100, target2[0]);
+ assertEquals(100, target3[0]);
+ set.setCurrentPlayTime(0);
+ assertEquals(0, target1[0]);
+ assertEquals(0, target2[0]);
+ assertEquals(0, target3[0]);
+ });
+ }
+
/**
* Check that the animator list contains exactly the given animators and nothing else.
*/
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 81eb213ce78a..5ac99db3aea5 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -128,6 +128,7 @@ public class ActivityManagerTest extends AndroidTestCase {
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -152,6 +153,7 @@ public class ActivityManagerTest extends AndroidTestCase {
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -167,6 +169,7 @@ public class ActivityManagerTest extends AndroidTestCase {
0x2222222, // colorBackground
0x3333332, // statusBarColor
0x4444442, // navigationBarColor
+ 0, // statusBarAppearance
true, // ensureStatusBarContrastWhenTransparent
true, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_RESIZEABLE, // resizeMode
@@ -197,6 +200,7 @@ public class ActivityManagerTest extends AndroidTestCase {
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -219,6 +223,7 @@ public class ActivityManagerTest extends AndroidTestCase {
0x222222, // colorBackground
0x333333, // statusBarColor
0x444444, // navigationBarColor
+ 0, // statusBarAppearance
false, // ensureStatusBarContrastWhenTransparent
false, // ensureNavigationBarContrastWhenTransparent
RESIZE_MODE_UNRESIZEABLE, // resizeMode
@@ -250,6 +255,7 @@ public class ActivityManagerTest extends AndroidTestCase {
assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+ assertEquals(td1.getStatusBarAppearance(), td2.getStatusBarAppearance());
assertEquals(td1.getResizeMode(), td2.getResizeMode());
assertEquals(td1.getMinWidth(), td2.getMinWidth());
assertEquals(td1.getMinHeight(), td2.getMinHeight());
diff --git a/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
new file mode 100644
index 000000000000..6bdb07d70212
--- /dev/null
+++ b/core/tests/coretests/src/android/view/HapticScrollFeedbackProviderTest.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.HapticFeedbackConstants.SCROLL_ITEM_FOCUS;
+import static android.view.HapticFeedbackConstants.SCROLL_LIMIT;
+import static android.view.HapticFeedbackConstants.SCROLL_TICK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public final class HapticScrollFeedbackProviderTest {
+ private static final int INPUT_DEVICE_1 = 1;
+ private static final int INPUT_DEVICE_2 = 2;
+
+ private static final int TICK_INTERVAL_PIXELS = 100;
+
+ private TestView mView;
+ private long mCurrentTimeMillis = 1000; // arbitrary starting time value
+
+ private HapticScrollFeedbackProvider mProvider;
+
+ @Before
+ public void setUp() {
+ mView = new TestView(InstrumentationRegistry.getContext());
+ mProvider = new HapticScrollFeedbackProvider(mView, TICK_INTERVAL_PIXELS);
+ }
+
+ @Test
+ public void testSnapToItem() {
+ mProvider.onSnapToItem(createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_ITEM_FOCUS);
+ }
+
+ @Test
+ public void testScrollLimit_start() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+ }
+
+ @Test
+ public void testScrollLimit_stop() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+ }
+
+ @Test
+ public void testScrollProgress_zeroTickInterval() {
+ mProvider =
+ new HapticScrollFeedbackProvider(
+ mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 0);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 10);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 30);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 30);
+
+ assertNoFeedback(mView);
+ }
+
+ @Test
+ public void testScrollProgress_progressEqualsOrExceedsPositiveThreshold() {
+ mProvider =
+ new HapticScrollFeedbackProvider(
+ mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 20);
+
+ assertNoFeedback(mView);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 40);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+ }
+
+ @Test
+ public void testScrollProgress_progressEqualsOrExceedsNegativeThreshold() {
+ mProvider =
+ new HapticScrollFeedbackProvider(
+ mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -20);
+
+ assertNoFeedback(mView);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -80);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -70);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -40);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+ }
+
+ @Test
+ public void testScrollProgress_positiveAndNegativeProgresses() {
+ mProvider =
+ new HapticScrollFeedbackProvider(
+ mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 20);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -90);
+
+ assertNoFeedback(mView);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 10);
+
+ assertNoFeedback(mView);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ -50);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 40);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 50);
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 60);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 2);
+ }
+
+ @Test
+ public void testScrollProgress_singleProgressExceedsThreshold() {
+ mProvider =
+ new HapticScrollFeedbackProvider(
+ mView, /* rotaryEncoderAxisScrollTickIntervalPixels= */ 100);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(),
+ MotionEvent.AXIS_SCROLL,
+ /* deltaInPixels= */ 1000);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_TICK, 1);
+ }
+
+ @Test
+ public void testScrollLimit_startAndEndLimit_playsOnlyOneFeedback() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+ }
+
+ @Test
+ public void testScrollLimit_doubleStartLimit_playsOnlyOneFeedback() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ true);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+ }
+
+ @Test
+ public void testScrollLimit_doubleEndLimit_playsOnlyOneFeedback() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT);
+ }
+
+ @Test
+ public void testScrollLimit_enabledWithProgress() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ mProvider.onScrollProgress(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+ }
+
+ @Test
+ public void testScrollLimit_enabledWithSnap() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ mProvider.onSnapToItem(createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+ }
+
+ @Test
+ public void testScrollLimit_enabledWithDissimilarSnap() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ mProvider.onSnapToItem(createTouchMoveEvent(), MotionEvent.AXIS_X);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertFeedbackCount(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+ }
+
+ @Test
+ public void testScrollLimit_enabledWithDissimilarProgress() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ mProvider.onScrollProgress(
+ createTouchMoveEvent(), MotionEvent.AXIS_SCROLL, /* deltaInPixels= */ 80);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 2);
+ }
+
+ @Test
+ public void testScrollLimit_enabledWithDissimilarLimit() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ mProvider.onScrollLimit(createTouchMoveEvent(), MotionEvent.AXIS_SCROLL, false);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(), MotionEvent.AXIS_SCROLL, /* isStart= */ false);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 3);
+ }
+
+ @Test
+ public void testScrollLimit_enabledWithMotionFromDifferentDeviceId() {
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(INPUT_DEVICE_1),
+ MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(INPUT_DEVICE_2),
+ MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+ mProvider.onScrollLimit(
+ createRotaryEncoderScrollEvent(INPUT_DEVICE_1),
+ MotionEvent.AXIS_SCROLL,
+ /* isStart= */ false);
+
+ assertOnlyFeedback(mView, HapticFeedbackConstants.SCROLL_LIMIT, 3);
+ }
+
+ private void assertNoFeedback(TestView view) {
+ for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+ assertFeedbackCount(view, feedback, 0);
+ }
+ }
+
+ private void assertOnlyFeedback(TestView view, int expectedFeedback) {
+ assertOnlyFeedback(view, expectedFeedback, /* expectedCount= */ 1);
+ }
+
+ private void assertOnlyFeedback(TestView view, int expectedFeedback, int expectedCount) {
+ for (int feedback : new int[] {SCROLL_ITEM_FOCUS, SCROLL_LIMIT, SCROLL_TICK}) {
+ assertFeedbackCount(view, feedback, (feedback == expectedFeedback) ? expectedCount : 0);
+ }
+ }
+
+ private void assertFeedbackCount(TestView view, int feedback, int expectedCount) {
+ int count = view.mFeedbackCount.getOrDefault(feedback, 0);
+ assertThat(count).isEqualTo(expectedCount);
+ }
+
+ private MotionEvent createTouchMoveEvent() {
+ long downTime = mCurrentTimeMillis;
+ long eventTime = mCurrentTimeMillis + 2; // arbitrary increment from the down time.
+ ++mCurrentTimeMillis;
+ return MotionEvent.obtain(
+ downTime , eventTime, MotionEvent.ACTION_MOVE, /* x= */ 3, /* y= */ 5, 0);
+ }
+
+ private MotionEvent createRotaryEncoderScrollEvent() {
+ return createRotaryEncoderScrollEvent(INPUT_DEVICE_1);
+ }
+
+ private MotionEvent createRotaryEncoderScrollEvent(int deviceId) {
+ MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
+ props.id = 0;
+
+ MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
+ coords.setAxisValue(MotionEvent.AXIS_SCROLL, 20);
+
+ return MotionEvent.obtain(0 /* downTime */,
+ ++mCurrentTimeMillis,
+ MotionEvent.ACTION_SCROLL,
+ /* pointerCount= */ 1,
+ new MotionEvent.PointerProperties[] {props},
+ new MotionEvent.PointerCoords[] {coords},
+ /* metaState= */ 0,
+ /* buttonState= */ 0,
+ /* xPrecision= */ 0,
+ /* yPrecision= */ 0,
+ deviceId,
+ /* edgeFlags= */ 0,
+ InputDevice.SOURCE_ROTARY_ENCODER,
+ /* flags= */ 0);
+ }
+
+ private static class TestView extends View {
+ final Map<Integer, Integer> mFeedbackCount = new HashMap<>();
+
+ TestView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public boolean performHapticFeedback(int feedback) {
+ if (!mFeedbackCount.containsKey(feedback)) {
+ mFeedbackCount.put(feedback, 0);
+ }
+ mFeedbackCount.put(feedback, mFeedbackCount.get(feedback) + 1);
+ return true;
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
index 17ed4c478350..101f7c21fa19 100644
--- a/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/ContentCaptureManagerTest.java
@@ -15,14 +15,17 @@
*/
package android.view.contentcapture;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_SESSION_STARTED;
+
import static com.google.common.truth.Truth.assertThat;
-import static org.mockito.Mockito.mock;
import static org.testng.Assert.assertThrows;
import android.content.ContentCaptureOptions;
import android.content.Context;
+import com.android.internal.util.RingBuffer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
@@ -37,9 +40,15 @@ import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class ContentCaptureManagerTest {
+ private static final int BUFFER_SIZE = 100;
+
+ private static final ContentCaptureOptions EMPTY_OPTIONS = new ContentCaptureOptions(null);
+
@Mock
private Context mMockContext;
+ @Mock private IContentCaptureManager mMockContentCaptureManager;
+
@Test
public void testConstructor_invalidParametersThrowsException() {
assertThrows(NullPointerException.class,
@@ -48,11 +57,65 @@ public class ContentCaptureManagerTest {
}
@Test
+ public void testConstructor_contentProtection_default_bufferNotCreated() {
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_disabled_bufferNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ false, BUFFER_SIZE));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_invalidBufferSize_bufferNotCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, /* bufferSize= */ 0));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+
+ assertThat(manager.getContentProtectionEventBuffer()).isNull();
+ }
+
+ @Test
+ public void testConstructor_contentProtection_enabled_bufferCreated() {
+ ContentCaptureOptions options =
+ createOptions(
+ new ContentCaptureOptions.ContentProtectionOptions(
+ /* enableReceiver= */ true, BUFFER_SIZE));
+
+ ContentCaptureManager manager =
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, options);
+ RingBuffer<ContentCaptureEvent> buffer = manager.getContentProtectionEventBuffer();
+
+ assertThat(buffer).isNotNull();
+ ContentCaptureEvent[] expected = new ContentCaptureEvent[BUFFER_SIZE];
+ int offset = 3;
+ for (int i = 0; i < BUFFER_SIZE + offset; i++) {
+ ContentCaptureEvent event = new ContentCaptureEvent(i, TYPE_SESSION_STARTED);
+ buffer.append(event);
+ expected[(i + BUFFER_SIZE - offset) % BUFFER_SIZE] = event;
+ }
+ assertThat(buffer.toArray()).isEqualTo(expected);
+ }
+
+ @Test
public void testRemoveData_invalidParametersThrowsException() {
- final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
- final ContentCaptureOptions options = new ContentCaptureOptions(null);
final ContentCaptureManager manager =
- new ContentCaptureManager(mMockContext, mockService, options);
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
assertThrows(NullPointerException.class, () -> manager.removeData(null));
}
@@ -60,10 +123,8 @@ public class ContentCaptureManagerTest {
@Test
@SuppressWarnings("GuardedBy")
public void testFlushViewTreeAppearingEventDisabled_setAndGet() {
- final IContentCaptureManager mockService = mock(IContentCaptureManager.class);
- final ContentCaptureOptions options = new ContentCaptureOptions(null);
final ContentCaptureManager manager =
- new ContentCaptureManager(mMockContext, mockService, options);
+ new ContentCaptureManager(mMockContext, mMockContentCaptureManager, EMPTY_OPTIONS);
assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
manager.setFlushViewTreeAppearingEventDisabled(true);
@@ -71,4 +132,18 @@ public class ContentCaptureManagerTest {
manager.setFlushViewTreeAppearingEventDisabled(false);
assertThat(manager.getFlushViewTreeAppearingEventDisabled()).isFalse();
}
+
+ private ContentCaptureOptions createOptions(
+ ContentCaptureOptions.ContentProtectionOptions contentProtectionOptions) {
+ return new ContentCaptureOptions(
+ /* loggingLevel= */ 0,
+ /* maxBufferSize= */ 0,
+ /* idleFlushingFrequencyMs= */ 0,
+ /* textChangeFlushingFrequencyMs= */ 0,
+ /* logHistorySize= */ 0,
+ /* disableFlushForViewTreeAppearing= */ false,
+ /* enableReceiver= */ true,
+ contentProtectionOptions,
+ /* whitelistedComponents= */ null);
+ }
}
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
new file mode 100644
index 000000000000..4adadf11903e
--- /dev/null
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -0,0 +1,516 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.contentprotection;
+
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_APPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_DISAPPEARED;
+import static android.view.contentcapture.ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.Looper;
+import android.text.InputType;
+import android.view.View;
+import android.view.contentcapture.ContentCaptureEvent;
+import android.view.contentcapture.IContentCaptureManager;
+import android.view.contentcapture.ViewNode;
+import android.view.contentcapture.ViewNode.ViewStructureImpl;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.util.RingBuffer;
+
+import com.google.common.collect.ImmutableSet;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Test for {@link ContentProtectionEventProcessor}.
+ *
+ * <p>Run with: {@code atest
+ * FrameworksCoreTests:android.view.contentprotection.ContentProtectionEventProcessorTest}
+ */
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ContentProtectionEventProcessorTest {
+
+ private static final String PACKAGE_NAME = "com.test.package.name";
+
+ private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+
+ private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+
+ private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+
+ private static final String SAFE_TEXT = "SAFE TEXT";
+
+ private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
+
+ private static final ContentCaptureEvent[] BUFFERED_EVENTS =
+ new ContentCaptureEvent[] {PROCESS_EVENT};
+
+ private static final Set<Integer> EVENT_TYPES_TO_STORE =
+ ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
+
+ @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
+
+ @Mock private RingBuffer<ContentCaptureEvent> mMockEventBuffer;
+
+ @Mock private IContentCaptureManager mMockContentCaptureManager;
+
+ private final Context mContext = ApplicationProvider.getApplicationContext();
+
+ private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+
+ @Before
+ public void setup() {
+ mContentProtectionEventProcessor =
+ new ContentProtectionEventProcessor(
+ mMockEventBuffer,
+ new Handler(Looper.getMainLooper()),
+ mMockContentCaptureManager,
+ PACKAGE_NAME);
+ }
+
+ @Test
+ public void processEvent_buffer_storesOnlySubsetOfEventTypes() {
+ List<ContentCaptureEvent> expectedEvents = new ArrayList<>();
+ for (int type = -100; type <= 100; type++) {
+ ContentCaptureEvent event = createEvent(type);
+ if (EVENT_TYPES_TO_STORE.contains(type)) {
+ expectedEvents.add(event);
+ }
+
+ mContentProtectionEventProcessor.processEvent(event);
+ }
+
+ assertThat(expectedEvents).hasSize(EVENT_TYPES_TO_STORE.size());
+ expectedEvents.forEach((expectedEvent) -> verify(mMockEventBuffer).append(expectedEvent));
+ verifyNoMoreInteractions(mMockEventBuffer);
+ }
+
+ @Test
+ public void processEvent_buffer_setsTextIdEntry_withoutExistingViewNode() {
+ ContentCaptureEvent event = createStoreEvent();
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(event.getViewNode()).isNotNull();
+ assertThat(event.getViewNode().getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+ verify(mMockEventBuffer).append(event);
+ }
+
+ @Test
+ public void processEvent_buffer_setsTextIdEntry_withExistingViewNode() {
+ ViewNode viewNode = new ViewNode();
+ viewNode.setTextIdEntry(PACKAGE_NAME + "TO BE OVERWRITTEN");
+ ContentCaptureEvent event = createStoreEvent();
+ event.setViewNode(viewNode);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(event.getViewNode()).isSameInstanceAs(viewNode);
+ assertThat(viewNode.getTextIdEntry()).isEqualTo(PACKAGE_NAME);
+ verify(mMockEventBuffer).append(event);
+ }
+
+ @Test
+ public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+ for (int type = -100; type <= 100; type++) {
+ if (type == TYPE_VIEW_APPEARED) {
+ continue;
+ }
+
+ mContentProtectionEventProcessor.processEvent(createEvent(type));
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ }
+
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void processEvent_loginDetected() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ assertOnLoginDetected();
+ }
+
+ @Test
+ public void processEvent_loginDetected_passwordFieldNotDetected() {
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void processEvent_loginDetected_suspiciousTextNotDetected() {
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void processEvent_loginDetected_withoutViewNode() {
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ assertOnLoginDetected();
+ }
+
+ @Test
+ public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
+ when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
+
+ mContentProtectionEventProcessor.mPasswordFieldDetected = true;
+ mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+ mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, times(2)).clear();
+ verify(mMockEventBuffer, times(2)).toArray();
+ assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
+ }
+
+ @Test
+ public void isPasswordField_android() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isPasswordField_android_withoutClassName() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isPasswordField_android_wrongClassName() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ "wrong.prefix" + ANDROID_CLASS_NAME,
+ InputType.TYPE_TEXT_VARIATION_PASSWORD);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isPasswordField_android_wrongInputType() {
+ ContentCaptureEvent event =
+ createAndroidPasswordFieldEvent(
+ ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isPasswordField_webView() throws Exception {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
+ when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer).clear();
+ verify(mMockEventBuffer).toArray();
+ assertOnLoginDetected(event, /* times= */ 1);
+ }
+
+ @Test
+ public void isPasswordField_webView_withClassName() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isPasswordField_webView_withSafeViewNodeText() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(
+ /* className= */ null, /* eventText= */ null, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isPasswordField_webView_withEventText() {
+ ContentCaptureEvent event =
+ createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isSuspiciousText_withSafeText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isSuspiciousText_eventText_suspiciousText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isSuspiciousText_viewNodeText_suspiciousText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isSuspiciousText_eventText_passwordText() {
+ ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ @Test
+ public void isSuspiciousText_viewNodeText_passwordText() {
+ // Specify the class to differ from {@link isPasswordField_webView} test in this version
+ ContentCaptureEvent event =
+ createProcessEvent(
+ "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
+
+ mContentProtectionEventProcessor.processEvent(event);
+
+ assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+ verify(mMockEventBuffer, never()).clear();
+ verify(mMockEventBuffer, never()).toArray();
+ verifyZeroInteractions(mMockContentCaptureManager);
+ }
+
+ private static ContentCaptureEvent createEvent(int type) {
+ return new ContentCaptureEvent(/* sessionId= */ 123, type);
+ }
+
+ private static ContentCaptureEvent createStoreEvent() {
+ return createEvent(TYPE_VIEW_TEXT_CHANGED);
+ }
+
+ private static ContentCaptureEvent createProcessEvent() {
+ return createEvent(TYPE_VIEW_APPEARED);
+ }
+
+ private ContentCaptureEvent createProcessEvent(
+ @Nullable String className,
+ int inputType,
+ @Nullable String eventText,
+ @Nullable String viewNodeText) {
+ View view = new View(mContext);
+ ViewStructureImpl viewStructure = new ViewStructureImpl(view);
+ if (className != null) {
+ viewStructure.setClassName(className);
+ }
+ if (viewNodeText != null) {
+ viewStructure.setText(viewNodeText);
+ }
+ viewStructure.setInputType(inputType);
+
+ ContentCaptureEvent event = createProcessEvent();
+ event.setViewNode(viewStructure.getNode());
+ if (eventText != null) {
+ event.setText(eventText);
+ }
+
+ return event;
+ }
+
+ private ContentCaptureEvent createAndroidPasswordFieldEvent(
+ @Nullable String className, int inputType) {
+ return createProcessEvent(
+ className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+ }
+
+ private ContentCaptureEvent createWebViewPasswordFieldEvent(
+ @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
+ return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+ }
+
+ private ContentCaptureEvent createSuspiciousTextEvent(
+ @Nullable String eventText, @Nullable String viewNodeText) {
+ return createProcessEvent(
+ /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
+ }
+
+ private void assertOnLoginDetected() throws Exception {
+ assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
+ }
+
+ private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
+ ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
+ verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+
+ assertThat(captor.getValue()).isNotNull();
+ List<ContentCaptureEvent> actual = captor.getValue().getList();
+ assertThat(actual).isNotNull();
+ assertThat(actual).containsExactly(event);
+ }
+}
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index 2ef2d3a968e0..a6e74d0d6b94 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -340,4 +341,42 @@ public class WindowOnBackInvokedDispatcherTest {
verify(mCallback1).onBackCancelled();
verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), isNull());
}
+
+ @Test
+ public void onBackInvoked_calledAfterOnBackStarted() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+
+ callbackInfo.getCallback().onBackInvoked();
+
+ waitForIdle();
+ verify(mCallback1).onBackInvoked();
+ verify(mCallback1, never()).onBackCancelled();
+ }
+
+ @Test
+ public void onDetachFromWindow_cancelCallbackAndIgnoreOnBackInvoked() throws RemoteException {
+ mDispatcher.registerOnBackInvokedCallback(PRIORITY_DEFAULT, mCallback1);
+
+ OnBackInvokedCallbackInfo callbackInfo = assertSetCallbackInfo();
+
+ callbackInfo.getCallback().onBackStarted(mBackEvent);
+
+ waitForIdle();
+ verify(mCallback1).onBackStarted(any(BackEvent.class));
+
+ // This should trigger mCallback1.onBackCancelled()
+ mDispatcher.detachFromWindow();
+ // This should be ignored by mCallback1
+ callbackInfo.getCallback().onBackInvoked();
+
+ waitForIdle();
+ verify(mCallback1, never()).onBackInvoked();
+ verify(mCallback1).onBackCancelled();
+ }
}
diff --git a/data/etc/com.android.networkstack.xml b/data/etc/com.android.networkstack.xml
index 06fec1cdab1e..003ca53b03b0 100644
--- a/data/etc/com.android.networkstack.xml
+++ b/data/etc/com.android.networkstack.xml
@@ -25,6 +25,7 @@
<permission name="android.permission.LOCAL_MAC_ADDRESS"/>
<permission name="android.permission.MANAGE_SUBSCRIPTION_PLANS"/>
<permission name="android.permission.MANAGE_USB"/>
+ <permission name="android.permission.MANAGE_USERS"/>
<permission name="android.permission.NETWORK_BYPASS_PRIVATE_DNS"/>
<permission name="android.permission.PACKET_KEEPALIVE_OFFLOAD"/>
<permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index e4defcfa359f..136d1a25255b 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -3379,6 +3379,12 @@
"group": "WM_DEBUG_REMOTE_ANIMATIONS",
"at": "com\/android\/server\/wm\/RemoteAnimationController.java"
},
+ "975028389": {
+ "message": "unable to call receiver for empty keyboard shortcuts",
+ "level": "ERROR",
+ "group": "WM_ERROR",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"975275467": {
"message": "Set animatingExit: reason=remove\/isAnimating win=%s",
"level": "VERBOSE",
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index 39338e5baddb..146774189490 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -2152,7 +2152,7 @@ public class BubbleController implements ConfigurationChangeListener,
pw.println(" suppressing: " + key);
}
- pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
+ pw.println("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 12d51f54a09c..47d58afb6aba 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -25,6 +25,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -86,13 +87,14 @@ public class TvWMShellModule {
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
return new TvSplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTDAOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, mainExecutor, mainHandler,
- systemWindows);
+ transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor,
+ mainHandler, systemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 5a9c25f7a710..7cc2b9e64ec8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -46,6 +46,7 @@ import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.DockStateReader;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -70,6 +71,8 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformComponents;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
+import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
+import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -77,8 +80,6 @@ import com.android.wm.shell.pip.PipMediaController;
import com.android.wm.shell.pip.PipSurfaceTransactionHelper;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PipTouchHandler;
-import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
-import com.android.wm.shell.keyguard.KeyguardTransitions;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.recents.RecentsTransitionHandler;
@@ -275,6 +276,13 @@ public abstract class WMShellBaseModule {
return new WindowManagerShellWrapper(mainExecutor);
}
+ @WMSingleton
+ @Provides
+ static LaunchAdjacentController provideLaunchAdjacentController(
+ SyncTransactionQueue syncQueue) {
+ return new LaunchAdjacentController(syncQueue);
+ }
+
//
// Back animation
//
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index 2dbccaceeddc..e2010c220bc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -41,6 +41,7 @@ import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.FloatingContentCoordinator;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -334,11 +335,12 @@ public abstract class WMShellModule {
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor) {
return new SplitScreenController(context, shellInit, shellCommandHandler, shellController,
shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, displayController,
displayImeController, displayInsetsController, dragAndDropController, transitions,
- transactionPool, iconProvider, recentTasks, mainExecutor);
+ transactionPool, iconProvider, recentTasks, launchAdjacentController, mainExecutor);
}
//
@@ -679,6 +681,7 @@ public abstract class WMShellModule {
static DesktopTasksController provideDesktopTasksController(
Context context,
ShellInit shellInit,
+ ShellCommandHandler shellCommandHandler,
ShellController shellController,
DisplayController displayController,
ShellTaskOrganizer shellTaskOrganizer,
@@ -688,12 +691,13 @@ public abstract class WMShellModule {
EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
@DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
+ LaunchAdjacentController launchAdjacentController,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new DesktopTasksController(context, shellInit, shellController, displayController,
- shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer, transitions,
- enterDesktopTransitionHandler, exitDesktopTransitionHandler,
- desktopModeTaskRepository, mainExecutor);
+ return new DesktopTasksController(context, shellInit, shellCommandHandler, shellController,
+ displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
+ transitions, enterDesktopTransitionHandler, exitDesktopTransitionHandler,
+ desktopModeTaskRepository, launchAdjacentController, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index 402bb96dc0c0..a490fb8289d0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -25,6 +25,7 @@ import androidx.core.util.keyIterator
import androidx.core.util.valueIterator
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
import com.android.wm.shell.util.KtProtoLog
+import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -339,6 +340,25 @@ class DesktopModeTaskRepository {
return displayData[displayId]?.stashed ?: false
}
+ internal fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopModeTaskRepository")
+ dumpDisplayData(pw, innerPrefix)
+ pw.println("${innerPrefix}freeformTasksInZOrder=${freeformTasksInZOrder.toDumpString()}")
+ pw.println("${innerPrefix}activeTasksListeners=${activeTasksListeners.size}")
+ pw.println("${innerPrefix}visibleTasksListeners=${visibleTasksListeners.size}")
+ }
+
+ private fun dumpDisplayData(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ displayData.forEach { displayId, data ->
+ pw.println("${prefix}Display $displayId:")
+ pw.println("${innerPrefix}activeTasks=${data.activeTasks.toDumpString()}")
+ pw.println("${innerPrefix}visibleTasks=${data.visibleTasks.toDumpString()}")
+ pw.println("${innerPrefix}stashed=${data.stashed}")
+ }
+ }
+
/**
* Defines interface for classes that can listen to changes for active tasks in desktop mode.
*/
@@ -367,3 +387,7 @@ class DesktopModeTaskRepository {
fun onStashedChanged(displayId: Int, stashed: Boolean) {}
}
}
+
+private fun <T> Iterable<T>.toDumpString(): String {
+ return joinToString(separator = ", ", prefix = "[", postfix = "]")
+} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index 37cbbcd1853a..821fc13d7653 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
@@ -46,6 +46,7 @@ import com.android.wm.shell.ShellTaskOrganizer
import com.android.wm.shell.common.DisplayController
import com.android.wm.shell.common.ExecutorUtils
import com.android.wm.shell.common.ExternalInterfaceBinder
+import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.RemoteCallable
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SingleInstanceRemoteListener
@@ -54,11 +55,14 @@ import com.android.wm.shell.common.annotations.ExternalThread
import com.android.wm.shell.common.annotations.ShellMainThread
import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE
+import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.sysui.ShellSharedConstants
import com.android.wm.shell.transition.Transitions
import com.android.wm.shell.util.KtProtoLog
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration
+import java.io.PrintWriter
import java.util.concurrent.Executor
import java.util.function.Consumer
@@ -66,6 +70,7 @@ import java.util.function.Consumer
class DesktopTasksController(
private val context: Context,
shellInit: ShellInit,
+ private val shellCommandHandler: ShellCommandHandler,
private val shellController: ShellController,
private val displayController: DisplayController,
private val shellTaskOrganizer: ShellTaskOrganizer,
@@ -75,6 +80,7 @@ class DesktopTasksController(
private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
private val desktopModeTaskRepository: DesktopModeTaskRepository,
+ private val launchAdjacentController: LaunchAdjacentController,
@ShellMainThread private val mainExecutor: ShellExecutor
) : RemoteCallable<DesktopTasksController>, Transitions.TransitionHandler {
@@ -85,6 +91,11 @@ class DesktopTasksController(
visualIndicator?.releaseVisualIndicator(t)
visualIndicator = null
}
+ private val taskVisibilityListener = object : VisibleTasksListener {
+ override fun onVisibilityChanged(displayId: Int, hasVisibleFreeformTasks: Boolean) {
+ launchAdjacentController.launchAdjacentEnabled = !hasVisibleFreeformTasks
+ }
+ }
init {
desktopMode = DesktopModeImpl()
@@ -95,12 +106,14 @@ class DesktopTasksController(
private fun onInit() {
KtProtoLog.d(WM_SHELL_DESKTOP_MODE, "Initialize DesktopTasksController")
+ shellCommandHandler.addDumpCallback(this::dump, this)
shellController.addExternalInterface(
ShellSharedConstants.KEY_EXTRA_SHELL_DESKTOP_MODE,
{ createExternalInterface() },
this
)
transitions.addHandler(this)
+ desktopModeTaskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
}
/** Show all tasks, that are part of the desktop, on top of launcher */
@@ -602,14 +615,17 @@ class DesktopTasksController(
* @param taskInfo the task being dragged.
* @param position position of surface when drag ends.
* @param y the Y position of the motion event.
+ * @param windowDecor the window decoration for the task being dragged
*/
fun onDragPositioningEnd(
taskInfo: RunningTaskInfo,
position: Point,
- y: Float
+ y: Float,
+ windowDecor: DesktopModeWindowDecoration
) {
val statusBarHeight = getStatusBarHeight(taskInfo)
if (y <= statusBarHeight && taskInfo.windowingMode == WINDOWING_MODE_FREEFORM) {
+ windowDecor.incrementRelayoutBlock()
moveToFullscreenWithAnimation(taskInfo, position)
}
}
@@ -710,6 +726,12 @@ class DesktopTasksController(
desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
}
+ private fun dump(pw: PrintWriter, prefix: String) {
+ val innerPrefix = "$prefix "
+ pw.println("${prefix}DesktopTasksController")
+ desktopModeTaskRepository.dump(pw, innerPrefix)
+ }
+
/** The interface for calls from outside the shell, within the host process. */
@ExternalThread
private inner class DesktopModeImpl : DesktopMode {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
index 513638eeb960..cef7e1666331 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/keyguard/KeyguardTransitionHandler.java
@@ -17,31 +17,25 @@
package com.android.wm.shell.keyguard;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
-import static android.view.WindowManager.TRANSIT_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_SLEEP;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
+import static android.view.WindowManager.TRANSIT_SLEEP;
import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
-import static com.android.wm.shell.util.TransitionUtil.isClosingType;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.os.RemoteException;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.Log;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.IRemoteTransitionFinishedCallback;
import android.window.TransitionInfo;
@@ -56,8 +50,6 @@ import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
-import java.util.Map;
-
/**
* The handler for Keyguard enter/exit and occlude/unocclude animations.
*
@@ -70,7 +62,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
private final Handler mMainHandler;
private final ShellExecutor mMainExecutor;
- private final Map<IBinder, IRemoteTransition> mStartedTransitions = new ArrayMap<>();
+ private final ArrayMap<IBinder, StartedTransition> mStartedTransitions = new ArrayMap<>();
/**
* Local IRemoteTransition implementations registered by the keyguard service.
@@ -81,6 +73,18 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
private IRemoteTransition mOccludeByDreamTransition = null;
private IRemoteTransition mUnoccludeTransition = null;
+ private final class StartedTransition {
+ final TransitionInfo mInfo;
+ final SurfaceControl.Transaction mFinishT;
+ final IRemoteTransition mPlayer;
+
+ public StartedTransition(TransitionInfo info,
+ SurfaceControl.Transaction finishT, IRemoteTransition player) {
+ mInfo = info;
+ mFinishT = finishT;
+ mPlayer = player;
+ }
+ }
public KeyguardTransitionHandler(
@NonNull ShellInit shellInit,
@NonNull Transitions transitions,
@@ -105,10 +109,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
}
public static boolean handles(TransitionInfo info) {
- return (info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0
- || (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0
- || info.getType() == TRANSIT_KEYGUARD_OCCLUDE
- || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE;
+ return (info.getFlags() & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0;
}
@Override
@@ -120,39 +121,14 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
return false;
}
- boolean hasOpeningOcclude = false;
- boolean hasClosingOcclude = false;
- boolean hasOpeningDream = false;
- boolean hasClosingApp = false;
-
- // Check for occluding/dream/closing apps
- for (int i = info.getChanges().size() - 1; i >= 0; i--) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if ((change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0) {
- continue;
- } else if (isOpeningType(change.getMode())) {
- hasOpeningOcclude |= change.hasFlags(FLAG_OCCLUDES_KEYGUARD);
- hasOpeningDream |= (change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM);
- } else if (isClosingType(change.getMode())) {
- hasClosingOcclude |= change.hasFlags(FLAG_OCCLUDES_KEYGUARD);
- hasClosingApp = true;
- }
- }
-
// Choose a transition applicable for the changes and keyguard state.
if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
return startAnimation(mExitTransition,
"going-away",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- if (hasOpeningOcclude || info.getType() == TRANSIT_KEYGUARD_OCCLUDE) {
- if (hasClosingOcclude) {
- // Transitions between apps on top of the keyguard can use the default handler.
- // WM sends a final occlude status update after the transition is finished.
- return false;
- }
- if (hasOpeningDream) {
+ if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_OCCLUDING) != 0) {
+ if (hasOpeningDream(info)) {
return startAnimation(mOccludeByDreamTransition,
"occlude-by-dream",
transition, info, startTransaction, finishTransaction, finishCallback);
@@ -161,12 +137,12 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
"occlude",
transition, info, startTransaction, finishTransaction, finishCallback);
}
- } else if (hasClosingApp || info.getType() == TRANSIT_KEYGUARD_UNOCCLUDE) {
+ } else if ((info.getFlags() & TRANSIT_FLAG_KEYGUARD_UNOCCLUDING) != 0) {
return startAnimation(mUnoccludeTransition,
"unocclude",
transition, info, startTransaction, finishTransaction, finishCallback);
} else {
- Log.w(TAG, "Failed to play: " + info);
+ Log.i(TAG, "Refused to play keyguard transition: " + info);
return false;
}
}
@@ -194,7 +170,8 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
});
}
});
- mStartedTransitions.put(transition, remoteHandler);
+ mStartedTransitions.put(transition,
+ new StartedTransition(info, finishTransaction, remoteHandler));
} catch (RemoteException e) {
Log.wtf(TAG, "RemoteException thrown from local IRemoteTransition", e);
return false;
@@ -207,20 +184,35 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
public void mergeAnimation(@NonNull IBinder nextTransition, @NonNull TransitionInfo nextInfo,
@NonNull SurfaceControl.Transaction nextT, @NonNull IBinder currentTransition,
@NonNull TransitionFinishCallback nextFinishCallback) {
- final IRemoteTransition playing = mStartedTransitions.get(currentTransition);
-
+ final StartedTransition playing = mStartedTransitions.get(currentTransition);
if (playing == null) {
ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"unknown keyguard transition %s", currentTransition);
return;
}
-
- if (nextInfo.getType() == TRANSIT_SLEEP) {
+ if ((nextInfo.getFlags() & WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING) != 0
+ && (playing.mInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
+ // Keyguard unlocking has been canceled. Merge the unlock and re-lock transitions to
+ // avoid a flicker where we flash one frame with the screen fully unlocked.
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "canceling keyguard exit transition %s", currentTransition);
+ playing.mFinishT.merge(nextT);
+ try {
+ playing.mPlayer.mergeAnimation(nextTransition, nextInfo, nextT, currentTransition,
+ new FakeFinishCallback());
+ } catch (RemoteException e) {
+ // There is no good reason for this to happen because the player is a local object
+ // implementing an AIDL interface.
+ Log.wtf(TAG, "RemoteException thrown from KeyguardService transition", e);
+ }
+ nextFinishCallback.onTransitionFinished(null, null);
+ } else if (nextInfo.getType() == TRANSIT_SLEEP) {
// An empty SLEEP transition comes in as a signal to abort transitions whenever a sleep
// token is held. In cases where keyguard is showing, we are running the animation for
// the device sleeping/waking, so it's best to ignore this and keep playing anyway.
return;
- } else {
+ } else if (handles(nextInfo)) {
+ // In all other cases, fast-forward to let the next queued transition start playing.
finishAnimationImmediately(currentTransition, playing);
}
}
@@ -228,7 +220,7 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
@Override
public void onTransitionConsumed(IBinder transition, boolean aborted,
SurfaceControl.Transaction finishTransaction) {
- final IRemoteTransition playing = mStartedTransitions.remove(transition);
+ final StartedTransition playing = mStartedTransitions.remove(transition);
if (playing != null) {
finishAnimationImmediately(transition, playing);
}
@@ -241,13 +233,26 @@ public class KeyguardTransitionHandler implements Transitions.TransitionHandler
return null;
}
- private void finishAnimationImmediately(IBinder transition, IRemoteTransition playing) {
+ private static boolean hasOpeningDream(@NonNull TransitionInfo info) {
+ for (int i = info.getChanges().size() - 1; i >= 0; i--) {
+ final TransitionInfo.Change change = info.getChanges().get(i);
+ if (isOpeningType(change.getMode())
+ && change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_DREAM) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void finishAnimationImmediately(IBinder transition, StartedTransition playing) {
final IBinder fakeTransition = new Binder();
final TransitionInfo fakeInfo = new TransitionInfo(TRANSIT_SLEEP, 0x0);
final SurfaceControl.Transaction fakeT = new SurfaceControl.Transaction();
final FakeFinishCallback fakeFinishCb = new FakeFinishCallback();
try {
- playing.mergeAnimation(fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
+ playing.mPlayer.mergeAnimation(
+ fakeTransition, fakeInfo, fakeT, transition, fakeFinishCb);
} catch (RemoteException e) {
// There is no good reason for this to happen because the player is a local object
// implementing an AIDL interface.
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 281cae5e4ffa..abe2db094a5c 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
@@ -70,6 +70,7 @@ public class PipResizeGestureHandler {
private final PipBoundsAlgorithm mPipBoundsAlgorithm;
private final PipMotionHelper mMotionHelper;
private final PipBoundsState mPipBoundsState;
+ private final PipTouchState mPipTouchState;
private final PipTaskOrganizer mPipTaskOrganizer;
private final PhonePipMenuController mPhonePipMenuController;
private final PipDismissTargetHandler mPipDismissTargetHandler;
@@ -104,7 +105,6 @@ public class PipResizeGestureHandler {
private boolean mAllowGesture;
private boolean mIsAttached;
private boolean mIsEnabled;
- private boolean mEnableTouch;
private boolean mEnablePinchResize;
private boolean mEnableDragCornerResize;
private boolean mIsSysUiStateValid;
@@ -122,7 +122,8 @@ public class PipResizeGestureHandler {
public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm,
PipBoundsState pipBoundsState, PipMotionHelper motionHelper,
- PipTaskOrganizer pipTaskOrganizer, PipDismissTargetHandler pipDismissTargetHandler,
+ PipTouchState pipTouchState, PipTaskOrganizer pipTaskOrganizer,
+ PipDismissTargetHandler pipDismissTargetHandler,
Function<Rect, Rect> movementBoundsSupplier, Runnable updateMovementBoundsRunnable,
PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController,
ShellExecutor mainExecutor) {
@@ -132,6 +133,7 @@ public class PipResizeGestureHandler {
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipBoundsState = pipBoundsState;
mMotionHelper = motionHelper;
+ mPipTouchState = pipTouchState;
mPipTaskOrganizer = pipTaskOrganizer;
mPipDismissTargetHandler = pipDismissTargetHandler;
mMovementBoundsSupplier = movementBoundsSupplier;
@@ -139,7 +141,6 @@ public class PipResizeGestureHandler {
mPhonePipMenuController = menuActivityController;
mPipUiEventLogger = pipUiEventLogger;
mPinchResizingAlgorithm = new PipPinchResizingAlgorithm();
- mEnableTouch = true;
mUpdateResizeBoundsCallback = (rect) -> {
mUserResizeBounds.set(rect);
@@ -250,8 +251,8 @@ public class PipResizeGestureHandler {
return;
}
- if (!mEnableTouch) {
- // No need to handle anything if touches are not enabled for resizing.
+ if (!mPipTouchState.getAllowInputEvents()) {
+ // No need to handle anything if touches are not enabled
return;
}
@@ -588,13 +589,13 @@ public class PipResizeGestureHandler {
mLastResizeBounds, movementBounds);
mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction);
- // disable the resizing until the final bounds are updated
- mEnableTouch = false;
+ // disable any touch events beyond resizing too
+ mPipTouchState.setAllowInputEvents(false);
mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds,
PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> {
- // reset the pinch resizing to its default state
- mEnableTouch = true;
+ // enable touch events
+ mPipTouchState.setAllowInputEvents(true);
});
} else {
mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds,
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 8f6cee76b68a..500094335258 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
@@ -199,11 +199,6 @@ public class PipTouchHandler {
mMotionHelper = pipMotionHelper;
mPipDismissTargetHandler = new PipDismissTargetHandler(context, pipUiEventLogger,
mMotionHelper, mainExecutor);
- mPipResizeGestureHandler =
- new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
- mMotionHelper, pipTaskOrganizer, mPipDismissTargetHandler,
- this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
- menuController, mainExecutor);
mTouchState = new PipTouchState(ViewConfiguration.get(context),
() -> {
if (mPipBoundsState.isStashed()) {
@@ -220,6 +215,11 @@ public class PipTouchHandler {
},
menuController::hideMenu,
mainExecutor);
+ mPipResizeGestureHandler =
+ new PipResizeGestureHandler(context, pipBoundsAlgorithm, pipBoundsState,
+ mMotionHelper, mTouchState, pipTaskOrganizer, mPipDismissTargetHandler,
+ this::getMovementBounds, this::updateMovementBounds, pipUiEventLogger,
+ menuController, mainExecutor);
mConnection = new PipAccessibilityInteractionConnection(mContext, pipBoundsState,
mMotionHelper, pipTaskOrganizer, mPipBoundsAlgorithm.getSnapAlgorithm(),
this::onAccessibilityShowMenu, this::updateMovementBounds,
@@ -556,6 +556,11 @@ public class PipTouchHandler {
return true;
}
+ // do not process input event if not allowed
+ if (!mTouchState.getAllowInputEvents()) {
+ return true;
+ }
+
MotionEvent ev = (MotionEvent) inputEvent;
if (!mPipBoundsState.isStashed() && mPipResizeGestureHandler.willStartResizeGesture(ev)) {
// Initialize the touch state for the gesture, but immediately reset to invalidate the
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
index d7d69f27f9f8..7f62c629c7f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchState.java
@@ -37,7 +37,7 @@ public class PipTouchState {
private static final boolean DEBUG = false;
@VisibleForTesting
- public static final long DOUBLE_TAP_TIMEOUT = 200;
+ public static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout();
static final long HOVER_EXIT_TIMEOUT = 50;
private final ShellExecutor mMainExecutor;
@@ -55,6 +55,9 @@ public class PipTouchState {
private final PointF mLastDelta = new PointF();
private final PointF mVelocity = new PointF();
private boolean mAllowTouches = true;
+
+ // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing
+ private boolean mAllowInputEvents = true;
private boolean mIsUserInteracting = false;
// Set to true only if the multiple taps occur within the double tap timeout
private boolean mIsDoubleTap = false;
@@ -77,6 +80,20 @@ public class PipTouchState {
}
/**
+ * @return true if input processing is enabled for PiP in general.
+ */
+ public boolean getAllowInputEvents() {
+ return mAllowInputEvents;
+ }
+
+ /**
+ * @param allowInputEvents true to enable input processing for PiP in general.
+ */
+ public void setAllowInputEvents(boolean allowInputEvents) {
+ mAllowInputEvents = allowInputEvents;
+ }
+
+ /**
* Resets this state.
*/
public void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index ea33a1f1b56d..d0ea611dc403 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -76,6 +76,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ExternalInterfaceBinder;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.RemoteCallable;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SingleInstanceRemoteListener;
@@ -170,6 +171,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final LaunchAdjacentController mLaunchAdjacentController;
private final SplitScreenShellCommandHandler mSplitScreenShellCommandHandler;
private final String[] mAppsSupportMultiInstances;
@@ -196,6 +198,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
ShellExecutor mainExecutor) {
mShellCommandHandler = shellCommandHandler;
mShellController = shellController;
@@ -212,6 +215,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
// TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
// override for this controller from the base module
@@ -241,6 +245,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
TransactionPool transactionPool,
IconProvider iconProvider,
RecentTasksController recentTasks,
+ LaunchAdjacentController launchAdjacentController,
ShellExecutor mainExecutor,
StageCoordinator stageCoordinator) {
mShellCommandHandler = shellCommandHandler;
@@ -258,6 +263,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
mRecentTasksOptional = Optional.of(recentTasks);
+ mLaunchAdjacentController = launchAdjacentController;
mStageCoordinator = stageCoordinator;
mSplitScreenShellCommandHandler = new SplitScreenShellCommandHandler(this);
shellInit.addInitCallback(this::onInit, this);
@@ -296,7 +302,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool,
- mIconProvider, mMainExecutor, mRecentTasksOptional);
+ mIconProvider, mMainExecutor, mRecentTasksOptional, mLaunchAdjacentController);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index b2526ee97a21..8497f9f156f0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -123,6 +123,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ScreenshotUtils;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
@@ -196,6 +197,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// if user is opening another task(s).
private final ArrayList<Integer> mPausingTasks = new ArrayList<>();
private final Optional<RecentTasksController> mRecentTasks;
+ private final LaunchAdjacentController mLaunchAdjacentController;
private final Rect mTempRect1 = new Rect();
private final Rect mTempRect2 = new Rect();
@@ -273,7 +275,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool,
IconProvider iconProvider, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -281,6 +284,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger = new SplitscreenEventLogger();
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
@@ -327,7 +331,8 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks) {
+ Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -344,6 +349,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger = new SplitscreenEventLogger();
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout();
transitions.addHandler(this);
@@ -579,6 +585,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks.get().removeSplitPair(taskId1);
}
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
wct.startTask(taskId1, options1);
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
return;
@@ -600,6 +607,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
wct.sendPendingIntent(pendingIntent, fillInIntent, options1);
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
return;
@@ -620,6 +628,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (taskId == INVALID_TASK_ID) {
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
wct.startShortcut(mContext.getPackageName(), shortcutInfo, options1);
mSplitTransitions.startFullscreenTransition(wct, remoteTransition);
return;
@@ -678,6 +687,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
if (pendingIntent2 == null) {
options1 = options1 != null ? options1 : new Bundle();
+ addActivityOptions(options1, null);
if (shortcutInfo1 != null) {
wct.startShortcut(mContext.getPackageName(), shortcutInfo1, options1);
} else {
@@ -1743,7 +1753,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.reparent(mSideStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
// Make the stages adjacent to each other so they occlude what's behind them.
wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
- wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
setRootForceTranslucent(true, wct);
mSplitLayout.getInvisibleBounds(mTempRect1);
wct.setBounds(mSideStage.mRootTaskInfo.token, mTempRect1);
@@ -1751,6 +1760,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSyncQueue.runInSync(t -> {
t.setPosition(mSideStage.mRootLeash, mTempRect1.left, mTempRect1.top);
});
+ mLaunchAdjacentController.setLaunchAdjacentRoot(mSideStage.mRootTaskInfo.token);
}
/** Callback when split roots have child task appeared under it, this is a little different from
@@ -1780,9 +1790,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onRootTaskVanished() {
final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mRootTaskInfo != null) {
- wct.clearLaunchAdjacentFlagRoot(mRootTaskInfo.token);
- }
+ mLaunchAdjacentController.clearLaunchAdjacentRoot();
applyExitSplitScreen(null /* childrenToTop */, wct, EXIT_REASON_ROOT_TASK_VANISHED);
mDisplayInsetsController.removeInsetsChangedListener(mDisplayId, mSplitLayout);
}
@@ -2435,7 +2443,13 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
continue;
}
final StageTaskListener stage = getStageOfTask(taskInfo);
- if (stage == null) continue;
+ if (stage == null) {
+ if (change.getParent() == null && !isClosingType(change.getMode())
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+ record.mContainShowFullscreenChange = true;
+ }
+ continue;
+ }
if (isOpeningType(change.getMode())) {
if (!stage.containsTask(taskInfo.taskId)) {
Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
@@ -2450,22 +2464,25 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
}
- // If the size of dismissStages == 1, one of the task is closed without prepare pending
- // transition, which could happen if all activities were finished after finish top
- // activity in a task, so the trigger task is null when handleRequest.
- // Note if the size of dismissStages == 2, it's starting a new task, so don't handle it.
final ArraySet<StageTaskListener> dismissStages = record.getShouldDismissedStage();
if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0
|| dismissStages.size() == 1) {
+ // If the size of dismissStages == 1, one of the task is closed without prepare
+ // pending transition, which could happen if all activities were finished after
+ // finish top activity in a task, so the trigger task is null when handleRequest.
+ // Note if the size of dismissStages == 2, it's starting a new task,
+ // so don't handle it.
Log.e(TAG, "Somehow removed the last task in a stage outside of a proper "
+ "transition.");
final WindowContainerTransaction wct = new WindowContainerTransaction();
final int dismissTop = (dismissStages.size() == 1
&& getStageType(dismissStages.valueAt(0)) == STAGE_TYPE_MAIN)
|| mMainStage.getChildCount() == 0 ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
- prepareExitSplitScreen(dismissTop, wct);
+ // If there is a fullscreen opening change, we should not bring stage to top.
+ prepareExitSplitScreen(record.mContainShowFullscreenChange
+ ? STAGE_TYPE_UNDEFINED : dismissTop, wct);
mSplitTransitions.startDismissTransition(wct, this, dismissTop,
- EXIT_REASON_UNKNOWN);
+ EXIT_REASON_APP_FINISHED);
// This can happen in some pathological cases. For example:
// 1. main has 2 tasks [Task A (Single-task), Task B], side has one task [Task C]
// 2. Task B closes itself and starts Task A in LAUNCH_ADJACENT at the same time
@@ -2473,7 +2490,6 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// TODO(b/184679596): Find a way to either include task-org information in
// the transition, or synchronize task-org callbacks.
}
-
// Use normal animations.
return false;
} else if (mMixedHandler != null && TransitionUtil.hasDisplayChange(info)) {
@@ -2490,6 +2506,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
static class StageChangeRecord {
+ boolean mContainShowFullscreenChange = false;
static class StageChange {
final StageTaskListener mStageTaskListener;
final IntArray mAddedTaskId = new IntArray();
@@ -2622,6 +2639,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
Log.w(TAG, "Launched a task in split, but didn't receive any task in transition.");
mSplitTransitions.mPendingEnter.cancel((cancelWct, cancelT)
-> prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, cancelWct));
+ mSplitUnsupportedToast.show();
return true;
}
} else {
@@ -2632,6 +2650,7 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
(sideChild != null ? STAGE_TYPE_SIDE : STAGE_TYPE_UNDEFINED);
mSplitTransitions.mPendingEnter.cancel(
(cancelWct, cancelT) -> prepareExitSplitScreen(dismissTop, cancelWct));
+ mSplitUnsupportedToast.show();
return true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index 27d520d81c41..f05f32442cd6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -27,6 +27,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -58,6 +59,7 @@ public class TvSplitScreenController extends SplitScreenController {
private final TransactionPool mTransactionPool;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
+ private final LaunchAdjacentController mLaunchAdjacentController;
private final Handler mMainHandler;
private final SystemWindows mSystemWindows;
@@ -77,13 +79,14 @@ public class TvSplitScreenController extends SplitScreenController {
TransactionPool transactionPool,
IconProvider iconProvider,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
ShellExecutor mainExecutor,
Handler mainHandler,
SystemWindows systemWindows) {
super(context, shellInit, shellCommandHandler, shellController, shellTaskOrganizer,
syncQueue, rootTDAOrganizer, displayController, displayImeController,
displayInsetsController, dragAndDropController, transitions, transactionPool,
- iconProvider, recentTasks, mainExecutor);
+ iconProvider, recentTasks, launchAdjacentController, mainExecutor);
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
@@ -96,6 +99,7 @@ public class TvSplitScreenController extends SplitScreenController {
mTransactionPool = transactionPool;
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
+ mLaunchAdjacentController = launchAdjacentController;
mMainHandler = mainHandler;
mSystemWindows = systemWindows;
@@ -111,7 +115,7 @@ public class TvSplitScreenController extends SplitScreenController {
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool,
mIconProvider, mMainExecutor, mMainHandler,
- mRecentTasksOptional, mSystemWindows);
+ mRecentTasksOptional, mLaunchAdjacentController, mSystemWindows);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 4d563fbb7f04..0b11922ce113 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -24,6 +24,7 @@ import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
@@ -51,10 +52,11 @@ public class TvStageCoordinator extends StageCoordinator
IconProvider iconProvider, ShellExecutor mainExecutor,
Handler mainHandler,
Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController,
SystemWindows systemWindows) {
super(context, displayId, syncQueue, taskOrganizer, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- mainExecutor, recentTasks);
+ mainExecutor, recentTasks, launchAdjacentController);
mTvSplitMenuController = new TvSplitMenuController(context, this,
systemWindows, mainHandler);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 5ee5324f8758..f58f24be4984 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -31,6 +31,7 @@ import static com.android.wm.shell.util.TransitionUtil.isOpeningType;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.IBinder;
+import android.util.Log;
import android.util.Pair;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -589,11 +590,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- boolean consumed = mKeyguardHandler.startAnimation(
- mixed.mTransition, info, startTransaction, finishTransaction, finishCallback);
- if (!consumed) {
+ final Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ mixed.mInFlightSubAnimations--;
+ if (mixed.mInFlightSubAnimations == 0) {
+ mActiveTransitions.remove(mixed);
+ finishCallback.onTransitionFinished(wct, wctCB);
+ }
+ };
+ if (!mKeyguardHandler.startAnimation(
+ mixed.mTransition, info, startTransaction, finishTransaction, finishCB)) {
return false;
}
+ mixed.mInFlightSubAnimations++;
// Sync pip state.
if (mPipHandler != null) {
// We don't know when to apply `startTransaction` so use a separate transaction here.
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 ce8d792ef302..de20c2d90066 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
@@ -29,6 +29,7 @@ import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.fixScale;
import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -554,7 +555,10 @@ public class Transitions implements RemoteCallable<Transitions>,
layer = -zSplitLine - i;
}
} else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
- if (isOpening) {
+ if (isOpening
+ // This is for when an activity launches while a different transition is
+ // collecting.
+ || change.hasFlags(FLAG_MOVED_TO_TOP)) {
// put on top
layer = zSplitLine + numChanges - i;
} else {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index bb0eba6a0fc7..c504f57216f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -173,6 +173,17 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
mTransition = null;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull TransitionFinishCallback finishCallback) {
+ if (info.getType() == TRANSIT_CHANGE) {
+ // Apply changes happening during the unfold animation immediately
+ t.apply();
+ finishCallback.onTransitionFinished(null, null);
+ }
+ }
+
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 2fd34d9dc699..f0f06a6f07dd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -193,7 +193,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
@NonNull TransitionInfo info,
@NonNull TransitionInfo.Change change) {
if (change.getMode() == WindowManager.TRANSIT_CHANGE
- && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE)) {
+ && (info.getType() == Transitions.TRANSIT_ENTER_DESKTOP_MODE
+ || info.getType() == Transitions.TRANSIT_CANCEL_ENTERING_DESKTOP_MODE
+ || info.getType() == Transitions.TRANSIT_EXIT_DESKTOP_MODE)) {
mWindowDecorByTaskId.get(change.getTaskInfo().taskId)
.addTransitionPausingRelayout(transition);
}
@@ -411,7 +413,7 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
mDragPositioningCallback.onDragPositioningEnd(
e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
mDesktopTasksController.ifPresent(c -> c.onDragPositioningEnd(taskInfo,
- position, e.getRawY()));
+ position, e.getRawY(), mWindowDecorByTaskId.get(mTaskId)));
final boolean wasDragging = mIsDragging;
mIsDragging = false;
return wasDragging;
@@ -577,9 +579,9 @@ public class DesktopModeWindowDecorViewModel implements WindowDecorViewModel {
return;
} else if (mDragToDesktopAnimationStarted) {
Point position = new Point((int) ev.getX(), (int) ev.getY());
+ relevantDecor.incrementRelayoutBlock();
mDesktopTasksController.ifPresent(
- c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo,
- position));
+ c -> c.cancelMoveToFreeform(relevantDecor.mTaskInfo, position));
mDragToDesktopAnimationStarted = false;
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 514ea52cb8ae..76c80f7bbe85 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -4,7 +4,7 @@ import android.app.ActivityManager.RunningTaskInfo
import android.content.Context
import android.graphics.Color
import android.view.View
-
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
/**
* Encapsulates the root [View] of a window decoration and its children to facilitate looking up
* children (via findViewById) and updating to the latest data from [RunningTaskInfo].
@@ -23,6 +23,10 @@ internal abstract class DesktopModeWindowDecorationViewHolder(rootView: View) {
* with the caption background color.
*/
protected fun shouldUseLightCaptionColors(taskInfo: RunningTaskInfo): Boolean {
- return Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ return if (Color.alpha(taskInfo.taskDescription.statusBarColor) != 0) {
+ Color.valueOf(taskInfo.taskDescription.statusBarColor).luminance() < 0.5
+ } else {
+ taskInfo.taskDescription.statusBarAppearance and APPEARANCE_LIGHT_STATUS_BARS == 0
+ }
}
}
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 a4ac261d1946..ef7bedf49a92 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
@@ -16,7 +16,6 @@
package com.android.wm.shell.flicker.pip
-import android.platform.test.annotations.FlakyTest
import android.platform.test.annotations.Presubmit
import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
@@ -55,20 +54,24 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class AutoEnterPipOnGoToHomeTest(flicker: FlickerTest) : EnterPipViaAppUiButtonTest(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- pipApp.launchViaIntent(wmHelper)
- pipApp.enableAutoEnterForPipActivity()
- }
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- transitions { tapl.goHome() }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { tapl.goHome() }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableAutoEnterForPipActivity()
}
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
+ }
+ }
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index 98fc91b334cf..afcc1729ed16 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -54,40 +54,38 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ClosePipBySwipingDownTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
- val pipCenterX = pipRegion.centerX()
- val pipCenterY = pipRegion.centerY()
- val displayCenterX = device.displayWidth / 2
- val barComponent =
- if (flicker.scenario.isTablet) {
- ComponentNameMatcher.TASK_BAR
- } else {
- ComponentNameMatcher.NAV_BAR
- }
- val barLayerHeight =
- wmHelper.currentState.layerState
- .getLayerWithBuffer(barComponent)
- ?.visibleRegion
- ?.height
- ?: error("Couldn't find Nav or Task bar layer")
- // The dismiss button doesn't appear at the complete bottom of the screen,
- // it appears above the hot seat but `hotseatBarSize` is not available outside
- // the platform
- val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
- device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
- // Wait until the other app is no longer visible
- wmHelper
- .StateSyncBuilder()
- .withPipGone()
- .withWindowSurfaceDisappeared(pipApp)
- .withAppTransitionIdle()
- .waitForAndVerify()
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions {
+ val pipRegion = wmHelper.getWindowRegion(pipApp).bounds
+ val pipCenterX = pipRegion.centerX()
+ val pipCenterY = pipRegion.centerY()
+ val displayCenterX = device.displayWidth / 2
+ val barComponent =
+ if (flicker.scenario.isTablet) {
+ ComponentNameMatcher.TASK_BAR
+ } else {
+ ComponentNameMatcher.NAV_BAR
+ }
+ val barLayerHeight =
+ wmHelper.currentState.layerState
+ .getLayerWithBuffer(barComponent)
+ ?.visibleRegion
+ ?.height
+ ?: error("Couldn't find Nav or Task bar layer")
+ // The dismiss button doesn't appear at the complete bottom of the screen,
+ // it appears above the hot seat but `hotseatBarSize` is not available outside
+ // the platform
+ val displayY = (device.displayHeight * 0.9).toInt() - barLayerHeight
+ device.swipe(pipCenterX, pipCenterY, displayCenterX, displayY, 50)
+ // Wait until the other app is no longer visible
+ wmHelper
+ .StateSyncBuilder()
+ .withPipGone()
+ .withWindowSurfaceDisappeared(pipApp)
+ .withAppTransitionIdle()
+ .waitForAndVerify()
}
+ }
/** Checks that the focus doesn't change between windows during the transition */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
index 2417c45bf9a0..e52b71e602f9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
@@ -28,11 +28,10 @@ import org.junit.runners.Parameterized
/** Base class for exiting pip (closing pip window) without returning to the app */
abstract class ClosePipTransition(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup { this.setRotation(flicker.scenario.startRotation) }
- teardown { this.setRotation(Rotation.ROTATION_0) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup { this.setRotation(flicker.scenario.startRotation) }
+ teardown { this.setRotation(Rotation.ROTATION_0) }
+ }
/**
* Checks that [pipApp] window is pinned and visible at the start and then becomes unpinned and
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index d16583271e8c..86fe583c94e6 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -54,12 +54,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ClosePipWithDismissButtonTest(flicker: FlickerTest) : ClosePipTransition(flicker) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions { pipApp.closePipWindow(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.closePipWindow(wmHelper) }
+ }
/**
* Checks that the focus changes between the pip menu window and the launcher when clicking the
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index f52e877ec2b1..01d67cc35a14 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -45,20 +45,24 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- pipApp.launchViaIntent(wmHelper)
- pipApp.enableEnterPipOnUserLeaveHint()
- }
- teardown {
- // close gracefully so that onActivityUnpinned() can be called before force exit
- pipApp.closePipWindow(wmHelper)
- pipApp.exit(wmHelper)
- }
- transitions { tapl.goHome() }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { tapl.goHome() }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableEnterPipOnUserLeaveHint()
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
}
+ }
@Presubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index 4b4613704a16..5480144ba1ce 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -73,39 +73,39 @@ open class EnterPipToOtherOrientation(flicker: FlickerTest) : PipTransition(flic
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- // Launch a portrait only app on the fullscreen stack
- testApp.launchViaIntent(
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ teardown {
+ testApp.exit(wmHelper)
+ }
+ transitions {
+ // Enter PiP, and assert that the PiP is within bounds now that the device is back
+ // in portrait
+ broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
+ // during rotation the status bar becomes invisible and reappears at the end
+ wmHelper
+ .StateSyncBuilder()
+ .withPipShown()
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ // Launch a portrait only app on the fullscreen stack
+ testApp.launchViaIntent(
wmHelper,
stringExtras = mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_PORTRAIT.toString())
- )
- // Launch the PiP activity fixed as landscape
- pipApp.launchViaIntent(
+ )
+ // Launch the PiP activity fixed as landscape, but don't enter PiP
+ pipApp.launchViaIntent(
wmHelper,
stringExtras =
- mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
- )
- }
- teardown {
- pipApp.exit(wmHelper)
- testApp.exit(wmHelper)
- }
- transitions {
- // Enter PiP, and assert that the PiP is within bounds now that the device is back
- // in portrait
- broadcastActionTrigger.doAction(ACTION_ENTER_PIP)
- // during rotation the status bar becomes invisible and reappears at the end
- wmHelper
- .StateSyncBuilder()
- .withPipShown()
- .withNavOrTaskBarVisible()
- .withStatusBarVisible()
- .waitForAndVerify()
- }
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
}
+ }
/**
* This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
index bfd57786e615..95121def07bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
@@ -26,12 +26,11 @@ import org.junit.Test
import org.junit.runners.Parameterized
abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) {
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup { pipApp.launchViaIntent(wmHelper) }
- teardown { pipApp.exit(wmHelper) }
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntent(wmHelper)
}
+ }
/** Checks [pipApp] window remains visible throughout the animation */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index f1925d8c9d85..95725b64a48a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -51,11 +51,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class EnterPipViaAppUiButtonTest(flicker: FlickerTest) : EnterPipTransition(flicker) {
-
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions { pipApp.clickEnterPipButton(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.clickEnterPipButton(wmHelper) }
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index 3e0e37dfc997..0b3d16a8087d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -53,19 +53,16 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExitPipToAppViaExpandButtonTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
-
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.expandPipWindowToApp(wmHelper)
- // Wait until the other app is no longer visible
- wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.expandPipWindowToApp(wmHelper)
+ // Wait until the other app is no longer visible
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 603f99541a12..bb2d40becdc9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -52,19 +52,16 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExitPipToAppViaIntentTest(flicker: FlickerTest) : ExitPipToAppTransition(flicker) {
-
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- // launch an app behind the pip one
- testApp.launchViaIntent(wmHelper)
- }
- transitions {
- // This will bring PipApp to fullscreen
- pipApp.exitPipToFullScreenViaIntent(wmHelper)
- // Wait until the other app is no longer visible
- wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ // launch an app behind the pip one
+ testApp.launchViaIntent(wmHelper)
+ }
+ transitions {
+ // This will bring PipApp to fullscreen
+ pipApp.exitPipToFullScreenViaIntent(wmHelper)
+ // Wait until the other app is no longer visible
+ wmHelper.StateSyncBuilder().withWindowSurfaceDisappeared(testApp).waitForAndVerify()
}
+ }
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index bbb1c6c2ac63..fd16b6ea6ada 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -56,8 +56,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExpandPipOnDoubleClickTest(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.doubleClickPipWindow(wmHelper) } }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.doubleClickPipWindow(wmHelper) }
+ }
/**
* Checks that the pip app window remains inside the display bounds throughout the whole
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index d860e00fbfff..253aa4cae5c7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -35,8 +35,9 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class ExpandPipOnPinchOpenTest(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) } }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.pinchOpenPipWindow(wmHelper, 0.25f, 30) }
+ }
/** Checks that the visible region area of [pipApp] always increases during the animation. */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index d8d57d219933..094060f86691 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -56,15 +56,10 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class MovePipDownOnShelfHeightChange(flicker: FlickerTest) : MovePipShelfHeightTransition(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- teardown {
- tapl.pressHome()
- testApp.exit(wmHelper)
- }
- transitions { testApp.launchViaIntent(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ teardown { testApp.exit(wmHelper) }
+ transitions { testApp.launchViaIntent(wmHelper) }
+ }
/** Checks that the visible region of [pipApp] window always moves down during the animation. */
@Presubmit @Test fun pipWindowMovesDown() = pipWindowMoves(Direction.DOWN)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index 6b061bbb1565..ff51c27bf116 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -41,23 +41,21 @@ import org.junit.runners.Parameterized
open class MovePipOnImeVisibilityChangeTest(flicker: FlickerTest) : PipTransition(flicker) {
private val imeApp = ImeAppHelper(instrumentation)
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- imeApp.launchViaIntent(wmHelper)
- setRotation(flicker.scenario.startRotation)
- }
- teardown { imeApp.exit(wmHelper) }
- transitions {
- // open the soft keyboard
- imeApp.openIME(wmHelper)
- createTag(TAG_IME_VISIBLE)
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ imeApp.launchViaIntent(wmHelper)
+ setRotation(flicker.scenario.startRotation)
+ }
+ teardown { imeApp.exit(wmHelper) }
+ transitions {
+ // open the soft keyboard
+ imeApp.openIME(wmHelper)
+ createTag(TAG_IME_VISIBLE)
- // then close it again
- imeApp.closeIME(wmHelper)
- }
+ // then close it again
+ imeApp.closeIME(wmHelper)
}
+ }
/** Ensure the pip window remains visible throughout any keyboard interactions */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index ae3f87967658..27b061b67a85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -57,14 +57,12 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class MovePipUpOnShelfHeightChangeTest(flicker: FlickerTest) :
MovePipShelfHeightTransition(flicker) {
- /** Defines the transition used to run the test */
- override val transition: FlickerBuilder.() -> Unit
- get() =
- buildTransition() {
- setup { testApp.launchViaIntent(wmHelper) }
- transitions { tapl.pressHome() }
- teardown { testApp.exit(wmHelper) }
- }
+ override val thisTransition: FlickerBuilder.() -> Unit =
+ {
+ setup { testApp.launchViaIntent(wmHelper) }
+ transitions { tapl.pressHome() }
+ teardown { testApp.exit(wmHelper) }
+ }
/** Checks that the visible region of [pipApp] window always moves up during the animation. */
@Presubmit @Test fun pipWindowMovesUp() = pipWindowMoves(Direction.UP)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 4e2a4e700698..9f81ba8eee87 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -22,7 +22,6 @@ import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
import android.tools.device.flicker.legacy.FlickerBuilder
import android.tools.device.flicker.legacy.FlickerTest
import android.tools.device.flicker.legacy.FlickerTestFactory
-import android.tools.device.flicker.rules.RemoveAllTasksButHomeRule
import com.android.server.wm.flicker.testapp.ActivityOptions
import org.junit.FixMethodOrder
import org.junit.Test
@@ -37,28 +36,31 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class PipDragTest(flicker: FlickerTest) : PipTransition(flicker) {
private var isDraggedLeft: Boolean = true
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
- setup {
- tapl.setEnableRotation(true)
- // Launch the PIP activity and wait for it to enter PiP mode
- RemoveAllTasksButHomeRule.removeAllTasksButHome()
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
+ }
- // determine the direction of dragging to test for
- isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
- }
- teardown {
- // release the primary pointer after dragging without release
- pipApp.releasePipAfterDragging()
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ val stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true")
+ setup {
+ tapl.setEnableRotation(true)
+ pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- pipApp.exit(wmHelper)
- tapl.setEnableRotation(false)
- }
- transitions { pipApp.dragPipWindowAwayFromEdgeWithoutRelease(wmHelper, 50) }
+ // determine the direction of dragging to test for
+ isDraggedLeft = pipApp.isCloserToRightEdge(wmHelper)
+ }
+ }
+
+ override val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ // release the primary pointer after dragging without release
+ pipApp.releasePipAfterDragging()
+
+ pipApp.exit(wmHelper)
+ tapl.setEnableRotation(false)
}
+ }
@Postsubmit
@Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 8eb41b4ac694..60bf5ffdc7af 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -37,8 +37,9 @@ import org.junit.runners.Parameterized
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@FlakyTest(bugId = 270677470)
class PipPinchInTest(flicker: FlickerTest) : PipTransition(flicker) {
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition { transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) } }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions { pipApp.pinchInPipWindow(wmHelper, 0.4f, 30) }
+ }
/** Checks that the visible region area of [pipApp] always decreases during the animation. */
@Postsubmit
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
index eb1245b9ab86..17a178f78de3 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
@@ -56,27 +56,37 @@ abstract class PipTransition(flicker: FlickerTest) : BaseTest(flicker) {
}
}
- /**
- * Gets a configuration that handles basic setup and teardown of pip tests and that launches the
- * Pip app for test
- *
- * @param stringExtras Arguments to pass to the PIP launch intent
- * @param extraSpec Additional segment of flicker specification
- */
- @JvmOverloads
- protected open fun buildTransition(
- stringExtras: Map<String, String> = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"),
- extraSpec: FlickerBuilder.() -> Unit = {}
- ): FlickerBuilder.() -> Unit {
- return {
- setup {
- setRotation(Rotation.ROTATION_0)
- removeAllTasksButHome()
- pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = stringExtras)
- }
- teardown { pipApp.exit(wmHelper) }
+ /** Defines the transition used to run the test */
+ protected open val thisTransition: FlickerBuilder.() -> Unit = {}
- extraSpec(this)
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ defaultSetup(this)
+ defaultEnterPip(this)
+ thisTransition(this)
+ defaultTeardown(this)
+ }
+
+ /** Defines the default setup steps required by the test */
+ protected open val defaultSetup: FlickerBuilder.() -> Unit = {
+ setup {
+ setRotation(Rotation.ROTATION_0)
+ removeAllTasksButHome()
+ }
+ }
+
+ /** Defines the default method of entering PiP */
+ protected open val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ pipApp.launchViaIntentAndWaitForPip(wmHelper,
+ stringExtras = mapOf(ActivityOptions.Pip.EXTRA_ENTER_PIP to "true"))
+ }
+ }
+
+ /** Defines the default teardown required to clean up after the test */
+ protected open val defaultTeardown: FlickerBuilder.() -> Unit = {
+ teardown {
+ pipApp.exit(wmHelper)
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index 3850c1f6c89a..c618e5a24fdf 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -50,41 +50,41 @@ open class SetRequestedOrientationWhilePinned(flicker: FlickerTest) : PipTransit
private val startingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_0)
private val endingBounds = WindowUtils.getDisplayBounds(Rotation.ROTATION_90)
- /** {@inheritDoc} */
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- // Launch the PiP activity fixed as landscape.
- pipApp.launchViaIntent(
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ transitions {
+ // Launch the activity back into fullscreen and ensure that it is now in landscape
+ pipApp.launchViaIntent(wmHelper)
+ // System bar may fade out during fixed rotation.
+ wmHelper
+ .StateSyncBuilder()
+ .withFullScreenApp(pipApp)
+ .withRotation(Rotation.ROTATION_90)
+ .withNavOrTaskBarVisible()
+ .withStatusBarVisible()
+ .waitForAndVerify()
+ }
+ }
+
+ override val defaultEnterPip: FlickerBuilder.() -> Unit = {
+ setup {
+ // Launch the PiP activity fixed as landscape.
+ pipApp.launchViaIntent(
wmHelper,
stringExtras =
- mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
- )
- // Enter PiP.
- broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
- // System bar may fade out during fixed rotation.
- wmHelper
+ mapOf(EXTRA_FIXED_ORIENTATION to ORIENTATION_LANDSCAPE.toString())
+ )
+ // Enter PiP.
+ broadcastActionTrigger.doAction(ActivityOptions.Pip.ACTION_ENTER_PIP)
+ // System bar may fade out during fixed rotation.
+ wmHelper
.StateSyncBuilder()
.withPipShown()
.withRotation(Rotation.ROTATION_0)
.withNavOrTaskBarVisible()
.withStatusBarVisible()
.waitForAndVerify()
- }
- teardown { pipApp.exit(wmHelper) }
- transitions {
- // Launch the activity back into fullscreen and ensure that it is now in landscape
- pipApp.launchViaIntent(wmHelper)
- // System bar may fade out during fixed rotation.
- wmHelper
- .StateSyncBuilder()
- .withFullScreenApp(pipApp)
- .withRotation(Rotation.ROTATION_90)
- .withNavOrTaskBarVisible()
- .withStatusBarVisible()
- .waitForAndVerify()
- }
}
+ }
/**
* This test is not compatible with Tablets. When using [Activity.setRequestedOrientation] to
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index 703784dd8c67..43d6c8f26126 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -63,14 +63,13 @@ open class ShowPipAndRotateDisplay(flicker: FlickerTest) : PipTransition(flicker
private val screenBoundsStart = WindowUtils.getDisplayBounds(flicker.scenario.startRotation)
private val screenBoundsEnd = WindowUtils.getDisplayBounds(flicker.scenario.endRotation)
- override val transition: FlickerBuilder.() -> Unit
- get() = buildTransition {
- setup {
- testApp.launchViaIntent(wmHelper)
- setRotation(flicker.scenario.startRotation)
- }
- transitions { setRotation(flicker.scenario.endRotation) }
+ override val thisTransition: FlickerBuilder.() -> Unit = {
+ setup {
+ testApp.launchViaIntent(wmHelper)
+ setRotation(flicker.scenario.startRotation)
}
+ transitions { setRotation(flicker.scenario.endRotation) }
+ }
/** Checks that [testApp] layer is within [screenBoundsStart] at the start of the transition */
@Presubmit
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
index 666ef5515f21..dba21b8dd3f3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt
@@ -46,11 +46,13 @@ import com.android.wm.shell.ShellTestCase
import com.android.wm.shell.TestRunningTaskInfoBuilder
import com.android.wm.shell.TestShellExecutor
import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.LaunchAdjacentController
import com.android.wm.shell.common.ShellExecutor
import com.android.wm.shell.common.SyncTransactionQueue
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createHomeTask
+import com.android.wm.shell.sysui.ShellCommandHandler
import com.android.wm.shell.sysui.ShellController
import com.android.wm.shell.sysui.ShellInit
import com.android.wm.shell.transition.Transitions
@@ -78,6 +80,7 @@ import org.mockito.Mockito.`when` as whenever
class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var testExecutor: ShellExecutor
+ @Mock lateinit var shellCommandHandler: ShellCommandHandler
@Mock lateinit var shellController: ShellController
@Mock lateinit var displayController: DisplayController
@Mock lateinit var shellTaskOrganizer: ShellTaskOrganizer
@@ -86,12 +89,14 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Mock lateinit var transitions: Transitions
@Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
@Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+ @Mock lateinit var launchAdjacentController: LaunchAdjacentController
private lateinit var mockitoSession: StaticMockitoSession
private lateinit var controller: DesktopTasksController
private lateinit var shellInit: ShellInit
private lateinit var desktopModeTaskRepository: DesktopModeTaskRepository
+ private val shellExecutor = TestShellExecutor()
// Mock running tasks are registered here so we can get the list from mock shell task organizer
private val runningTasks = mutableListOf<RunningTaskInfo>()
@@ -115,6 +120,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
return DesktopTasksController(
context,
shellInit,
+ shellCommandHandler,
shellController,
displayController,
shellTaskOrganizer,
@@ -124,7 +130,8 @@ class DesktopTasksControllerTest : ShellTestCase() {
enterDesktopTransitionHandler,
exitDesktopTransitionHandler,
desktopModeTaskRepository,
- TestShellExecutor()
+ launchAdjacentController,
+ shellExecutor
)
}
@@ -348,7 +355,7 @@ class DesktopTasksControllerTest : ShellTestCase() {
@Test
fun moveTaskToFront_postsWctWithReorderOp() {
val task1 = setUpFreeformTask()
- val task2 = setUpFreeformTask()
+ setUpFreeformTask()
controller.moveTaskToFront(task1)
@@ -611,6 +618,27 @@ class DesktopTasksControllerTest : ShellTestCase() {
assertThat(desktopModeTaskRepository.isStashed(SECOND_DISPLAY)).isTrue()
}
+ @Test
+ fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
+ val task = setUpFreeformTask()
+ clearInvocations(launchAdjacentController)
+
+ markTaskVisible(task)
+ shellExecutor.flushAll()
+ verify(launchAdjacentController).launchAdjacentEnabled = false
+ }
+
+ @Test
+ fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() {
+ val task = setUpFreeformTask()
+ markTaskVisible(task)
+ clearInvocations(launchAdjacentController)
+
+ markTaskHidden(task)
+ shellExecutor.flushAll()
+ verify(launchAdjacentController).launchAdjacentEnabled = true
+ }
+
private fun setUpFreeformTask(displayId: Int = DEFAULT_DISPLAY): RunningTaskInfo {
val task = createFreeformTask(displayId)
whenever(shellTaskOrganizer.getRunningTaskInfo(task.taskId)).thenReturn(task)
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 ada3455fae18..1dfdbf6514ba 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
@@ -29,6 +29,7 @@ import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.ViewConfiguration;
import androidx.test.filters.SmallTest;
@@ -90,6 +91,8 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
private PipDisplayLayoutState mPipDisplayLayoutState;
+ private PipTouchState mPipTouchState;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -104,8 +107,12 @@ public class PipResizeGestureHandlerTest extends ShellTestCase {
final PipMotionHelper motionHelper = new PipMotionHelper(mContext, mPipBoundsState,
mPipTaskOrganizer, mPhonePipMenuController, pipSnapAlgorithm,
mMockPipTransitionController, mFloatingContentCoordinator);
+
+ mPipTouchState = new PipTouchState(ViewConfiguration.get(mContext),
+ () -> {}, () -> {}, mMainExecutor);
mPipResizeGestureHandler = new PipResizeGestureHandler(mContext, pipBoundsAlgorithm,
- mPipBoundsState, motionHelper, mPipTaskOrganizer, mPipDismissTargetHandler,
+ mPipBoundsState, motionHelper, mPipTouchState, mPipTaskOrganizer,
+ mPipDismissTargetHandler,
(Rect bounds) -> new Rect(), () -> {}, mPipUiEventLogger, mPhonePipMenuController,
mMainExecutor) {
@Override
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index fb17d8799bda..981035087e24 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -61,6 +61,7 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -102,6 +103,7 @@ public class SplitScreenControllerTests extends ShellTestCase {
@Mock IconProvider mIconProvider;
@Mock StageCoordinator mStageCoordinator;
@Mock RecentTasksController mRecentTasks;
+ @Mock LaunchAdjacentController mLaunchAdjacentController;
@Captor ArgumentCaptor<Intent> mIntentCaptor;
private ShellController mShellController;
@@ -117,7 +119,8 @@ public class SplitScreenControllerTests extends ShellTestCase {
mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue,
mRootTDAOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mDragAndDropController, mTransitions, mTransactionPool,
- mIconProvider, mRecentTasks, mMainExecutor, mStageCoordinator));
+ mIconProvider, mRecentTasks, mLaunchAdjacentController, mMainExecutor,
+ mStageCoordinator));
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 4e446c684d86..ff6f59d8973c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -31,6 +31,7 @@ import com.android.wm.shell.TestRunningTaskInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -75,11 +76,12 @@ public class SplitTestUtils {
DisplayController displayController, DisplayImeController imeController,
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
- ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks) {
+ ShellExecutor mainExecutor, Optional<RecentTasksController> recentTasks,
+ LaunchAdjacentController launchAdjacentController) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
- transitions, transactionPool, mainExecutor, recentTasks);
+ transitions, transactionPool, mainExecutor, recentTasks,
+ launchAdjacentController);
// Prepare root task for testing.
mRootTask = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 3b05651f884b..0095f65b410a 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -70,6 +70,7 @@ import com.android.wm.shell.TransitionInfoBuilder;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -101,6 +102,7 @@ public class SplitTransitionTests extends ShellTestCase {
@Mock private SurfaceSession mSurfaceSession;
@Mock private IconProvider mIconProvider;
@Mock private ShellExecutor mMainExecutor;
+ @Mock private LaunchAdjacentController mLaunchAdjacentController;
private SplitLayout mSplitLayout;
private MainStage mMainStage;
private SideStage mSideStage;
@@ -130,7 +132,8 @@ public class SplitTransitionTests extends ShellTestCase {
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
- mTransactionPool, mMainExecutor, Optional.empty());
+ mTransactionPool, mMainExecutor, Optional.empty(),
+ mLaunchAdjacentController);
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 66b6c62f1dd6..91ae1ef8603e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -65,6 +65,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.LaunchAdjacentController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
@@ -107,6 +108,8 @@ public class StageCoordinatorTests extends ShellTestCase {
private DisplayInsetsController mDisplayInsetsController;
@Mock
private TransactionPool mTransactionPool;
+ @Mock
+ private LaunchAdjacentController mLaunchAdjacentController;
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
@@ -130,7 +133,7 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool,
- mMainExecutor, Optional.empty()));
+ mMainExecutor, Optional.empty(), mLaunchAdjacentController));
mDividerLeash = new SurfaceControl.Builder(mSurfaceSession).setName("fakeDivider").build();
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
diff --git a/native/webview/OWNERS b/native/webview/OWNERS
index 580bb0fe8273..7e27dc8831a6 100644
--- a/native/webview/OWNERS
+++ b/native/webview/OWNERS
@@ -1,4 +1,4 @@
+# Bug component: 76427
boliu@google.com
-changwan@google.com
-tobiasjs@google.com
+ntfschr@google.com
torne@google.com
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 539857951ab1..7a6fad4b6d51 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -20,7 +20,7 @@
<string name="app_label">Companion Device Manager</string>
<!-- Title of the device association confirmation dialog. -->
- <string name="confirmation_title">Allow &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
+ <string name="confirmation_title">Allow the app &lt;strong&gt;<xliff:g id="app_name" example="Android Wear">%1$s</xliff:g>&lt;/strong&gt; to access &lt;strong&gt;<xliff:g id="device_name" example="ASUS ZenWatch 2">%2$s</xliff:g>&lt;/strong&gt;?</string>
<!-- ================= DEVICE_PROFILE_WATCH and null profile ================= -->
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index cf962d1d94aa..bce86c477e77 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -118,8 +118,10 @@ class CredentialSelectorViewModel(
if (entry != null && entry.pendingIntent != null) {
Log.d(Constants.LOG_TAG, "Launching provider activity")
uiState = uiState.copy(providerActivityState = ProviderActivityState.PENDING)
+ val entryIntent = entry.fillInIntent
+ entryIntent?.putExtra(Constants.IS_AUTO_SELECTED_KEY, uiState.isAutoSelectFlow)
val intentSenderRequest = IntentSenderRequest.Builder(entry.pendingIntent)
- .setFillInIntent(entry.fillInIntent).build()
+ .setFillInIntent(entryIntent).build()
try {
launcher.launch(intentSenderRequest)
} catch (e: Exception) {
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
index c6dc5945d886..51ca5971cec4 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/common/Constants.kt
@@ -21,5 +21,6 @@ class Constants {
const val LOG_TAG = "CredentialSelector"
const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
"androidx.credentials.BUNDLE_KEY_IS_AUTO_SELECT_ALLOWED"
+ const val IS_AUTO_SELECTED_KEY = "IS_AUTO_SELECTED"
}
-} \ No newline at end of file
+}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 738354f78e30..e1eb36ac276c 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -16,11 +16,10 @@
package com.android.dynsystem;
-import static android.os.AsyncTask.Status.FINISHED;
-import static android.os.AsyncTask.Status.PENDING;
import static android.os.AsyncTask.Status.RUNNING;
import static android.os.image.DynamicSystemClient.ACTION_HIDE_NOTIFICATION;
import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_IF_IN_USE;
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_KEYGUARD_DISMISSED;
import static android.os.image.DynamicSystemClient.ACTION_START_INSTALL;
import static android.os.image.DynamicSystemClient.CAUSE_ERROR_EXCEPTION;
import static android.os.image.DynamicSystemClient.CAUSE_ERROR_INVALID_URL;
@@ -234,6 +233,8 @@ public class DynamicSystemInstallationService extends Service
executeNotifyIfInUseCommand();
} else if (ACTION_HIDE_NOTIFICATION.equals(action)) {
executeHideNotificationCommand();
+ } else if (ACTION_NOTIFY_KEYGUARD_DISMISSED.equals(action)) {
+ executeNotifyKeyguardDismissed();
}
return Service.START_NOT_STICKY;
@@ -477,6 +478,10 @@ public class DynamicSystemInstallationService extends Service
}
}
+ private void executeNotifyKeyguardDismissed() {
+ postStatus(STATUS_NOT_STARTED, CAUSE_INSTALL_CANCELLED, null);
+ }
+
private void resetTaskAndStop() {
resetTaskAndStop(/* removeNotification= */ false);
}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
index b52272961e4b..7401691cd59e 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/VerificationActivity.java
@@ -16,6 +16,7 @@
package com.android.dynsystem;
+import static android.os.image.DynamicSystemClient.ACTION_NOTIFY_KEYGUARD_DISMISSED;
import static android.os.image.DynamicSystemClient.KEY_KEYGUARD_USE_DEFAULT_STRINGS;
import android.app.Activity;
@@ -83,11 +84,19 @@ public class VerificationActivity extends Activity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
startInstallationService();
+ } else {
+ notifyKeyguardDismissed();
}
finish();
}
+ private void notifyKeyguardDismissed() {
+ Intent intent = new Intent(this, DynamicSystemInstallationService.class);
+ intent.setAction(ACTION_NOTIFY_KEYGUARD_DISMISSED);
+ startServiceAsUser(intent, UserHandle.SYSTEM);
+ }
+
private void startInstallationService() {
// retrieve data from calling intent
Intent callingIntent = getIntent();
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index a2118fac4231..c52fde6364a0 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -38,7 +38,11 @@
<!-- Message for updating an existing app [CHAR LIMIT=NONE] -->
<string name="install_confirm_question_update">Do you want to update this app?</string>
<!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
- <string name="install_confirm_question_update_owner_reminder">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
+ <string name="install_confirm_question_update_owner_reminder" product="tablet">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your tablet. App functionality may change.</string>
+ <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+ <string name="install_confirm_question_update_owner_reminder" product="tv">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your TV. App functionality may change.</string>
+ <!-- Message for updating an existing app with update owner reminder [CHAR LIMIT=NONE] -->
+ <string name="install_confirm_question_update_owner_reminder" product="default">Update this app from <xliff:g id="new_update_owner">%1$s</xliff:g>?\n\nThis app normally receives updates from <xliff:g id="existing_update_owner">%2$s</xliff:g>. By updating from a different source, you may receive future updates from any source on your phone. App functionality may change.</string>
<!-- [CHAR LIMIT=100] -->
<string name="install_failed">App not installed.</string>
<!-- Reason displayed when installation fails because the package was blocked
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
index 7b17cbdd3a1e..19d74b33e034 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/DeleteStagedFileOnResult.java
@@ -16,6 +16,8 @@
package com.android.packageinstaller;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+
import static com.android.packageinstaller.PackageInstallerActivity.EXTRA_STAGED_SESSION_ID;
import android.app.Activity;
@@ -45,6 +47,9 @@ public class DeleteStagedFileOnResult extends Activity {
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
setResult(resultCode, data);
finish();
+ if (data != null && data.hasCategory(CATEGORY_LAUNCHER)) {
+ startActivity(data);
+ }
}
@Override
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
index 73c03a57cade..ff991d2f7ee3 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/InstallSuccess.java
@@ -17,7 +17,6 @@
package com.android.packageinstaller;
import android.app.Activity;
-import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
@@ -123,11 +122,7 @@ public class InstallSuccess extends AlertActivity {
Button launchButton = mAlert.getButton(DialogInterface.BUTTON_POSITIVE);
if (enabled) {
launchButton.setOnClickListener(view -> {
- try {
- startActivity(mLaunchIntent);
- } catch (ActivityNotFoundException | SecurityException e) {
- Log.e(LOG_TAG, "Could not start activity", e);
- }
+ setResult(Activity.RESULT_OK, mLaunchIntent);
finish();
});
} else {
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
index d1541569bc55..e071c111d617 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/PackageInstallerActivity.java
@@ -16,8 +16,9 @@
*/
package com.android.packageinstaller;
+import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import android.Manifest;
@@ -789,7 +790,8 @@ public class PackageInstallerActivity extends AlertActivity {
}
new Handler(Looper.getMainLooper()).postDelayed(() -> {
if (!isDestroyed()) {
- startActivity(getIntent().addFlags(FLAG_ACTIVITY_REORDER_TO_FRONT));
+ startActivity(getIntent().addFlags(
+ FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP));
}
}, 500);
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 750c156f95bd..c244ca0505ed 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -69,9 +69,6 @@ android_library {
"src/**/*.java",
"src/**/*.kt",
],
-
- min_sdk_version: "30",
-
}
// NOTE: Keep this module in sync with ./common.mk
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 89bfa0eb646b..030b70a75a8b 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -31,6 +31,8 @@ import com.android.settingslib.spaprivileged.template.common.UserProfilePager
/**
* The full screen template for an App List page.
*
+ * @param noMoreOptions default false. If true, then do not display more options action button,
+ * including the "Show System" / "Hide System" action.
* @param header the description header appears before all the applications.
*/
@Composable
@@ -38,6 +40,7 @@ fun <T : AppRecord> AppListPage(
title: String,
listModel: AppListModel<T>,
showInstantApps: Boolean = false,
+ noMoreOptions: Boolean = false,
matchAnyUserForAdmin: Boolean = false,
primaryUserOnly: Boolean = false,
noItemMessage: String? = null,
@@ -49,9 +52,11 @@ fun <T : AppRecord> AppListPage(
SearchScaffold(
title = title,
actions = {
- MoreOptionsAction {
- ShowSystemAction(showSystem.value) { showSystem.value = it }
- moreOptions()
+ if (!noMoreOptions) {
+ MoreOptionsAction {
+ ShowSystemAction(showSystem.value) { showSystem.value = it }
+ moreOptions()
+ }
}
},
) { bottomPadding, searchQuery ->
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
index 06003c0cb8f9..f6f48891030a 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListPageTest.kt
@@ -63,9 +63,7 @@ class AppListPageTest {
fun canShowSystem() {
val inputState by setContent()
- composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
- ).performClick()
+ onMoreOptions().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
val state = inputState!!.state
@@ -75,20 +73,32 @@ class AppListPageTest {
@Test
fun afterShowSystem_displayHideSystem() {
setContent()
- composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
- ).performClick()
+ onMoreOptions().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.menu_show_system)).performClick()
- composeTestRule.onNodeWithContentDescription(
- context.getString(R.string.abc_action_menu_overflow_description)
- ).performClick()
+ onMoreOptions().performClick()
composeTestRule.onNodeWithText(context.getString(R.string.menu_hide_system))
.assertIsDisplayed()
}
+ @Test
+ fun noMoreOptions_notDisplayMoreOptions() {
+ setContent(noMoreOptions = true)
+
+ onMoreOptions().assertDoesNotExist()
+ }
+
+ @Test
+ fun noMoreOptions_showSystemIsFalse() {
+ val inputState by setContent(noMoreOptions = true)
+
+ val state = inputState!!.state
+ assertThat(state.showSystem.value).isFalse()
+ }
+
private fun setContent(
+ noMoreOptions: Boolean = false,
header: @Composable () -> Unit = {},
): State<AppListInput<TestAppRecord>?> {
val appListState = mutableStateOf<AppListInput<TestAppRecord>?>(null)
@@ -96,6 +106,7 @@ class AppListPageTest {
AppListPage(
title = TITLE,
listModel = TestAppListModel(),
+ noMoreOptions = noMoreOptions,
header = header,
appList = { appListState.value = this },
)
@@ -103,6 +114,11 @@ class AppListPageTest {
return appListState
}
+ private fun onMoreOptions() =
+ composeTestRule.onNodeWithContentDescription(
+ context.getString(R.string.abc_action_menu_overflow_description)
+ )
+
private companion object {
const val TITLE = "Title"
}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index dac7f8d15388..9f884b277b5b 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -241,12 +241,12 @@
<!-- Bluetooth settings. Similar to bluetooth_profile_a2dp_high_quality, but used when the device supports high quality audio but we don't know which codec that will be used. -->
<string name="bluetooth_profile_a2dp_high_quality_unknown_codec">HD audio</string>
- <!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing Aid profile. -->
- <string name="bluetooth_profile_hearing_aid">Hearing Aids</string>
+ <!-- Bluetooth settings. The user-visible string that is used whenever referring to the Hearing aid profile. -->
+ <string name="bluetooth_profile_hearing_aid">Hearing aids</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the LE audio profile. -->
<string name="bluetooth_profile_le_audio">LE Audio</string>
- <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference when Hearing Aid is connected. -->
- <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to Hearing Aids</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the Hearing aid checkbox preference when hearing aid is connected. -->
+ <string name="bluetooth_hearing_aid_profile_summary_connected">Connected to hearing aids</string>
<!-- Bluetooth settings. Connection options screen. The summary for the LE audio checkbox preference when LE audio is connected. -->
<string name="bluetooth_le_audio_profile_summary_connected">Connected to LE audio</string>
@@ -287,8 +287,8 @@
for the HID checkbox preference that describes how checking it
will set the HID profile as preferred. -->
<string name="bluetooth_hid_profile_summary_use_for">Use for input</string>
- <!-- Bluetooth settings. Connection options screen. The summary for the Hearing Aid checkbox preference that describes how checking it will set the Hearing Aid profile as preferred. -->
- <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for Hearing Aids</string>
+ <!-- Bluetooth settings. Connection options screen. The summary for the Hearing aid checkbox preference that describes how checking it will set the hearing aid related profile as preferred. -->
+ <string name="bluetooth_hearing_aid_profile_summary_use_for">Use for hearing aids</string>
<!-- Bluetooth settings. Connection options screen. The summary for the LE_AUDIO checkbox preference that describes how checking it will set the LE_AUDIO profile as preferred. -->
<string name="bluetooth_le_audio_profile_summary_use_for">Use for LE_AUDIO</string>
@@ -827,6 +827,11 @@
<!-- UI debug setting: show touches location summary [CHAR LIMIT=50] -->
<string name="show_touches_summary">Show visual feedback for taps</string>
+ <!-- UI debug setting: show key presses? [CHAR LIMIT=25] -->
+ <string name="show_key_presses">Show key presses</string>
+ <!-- UI debug setting: show physical key presses summary [CHAR LIMIT=50] -->
+ <string name="show_key_presses_summary">Show visual feedback for physical key presses</string>
+
<!-- UI debug setting: show where surface updates happen? [CHAR LIMIT=25] -->
<string name="show_screen_updates">Show surface updates</string>
<!-- UI debug setting: show surface updates summary [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/search/Android.bp b/packages/SettingsLib/search/Android.bp
index cfff519705f2..918d696fa481 100644
--- a/packages/SettingsLib/search/Android.bp
+++ b/packages/SettingsLib/search/Android.bp
@@ -7,8 +7,18 @@ package {
default_applicable_licenses: ["frameworks_base_license"],
}
+java_library {
+ name: "SettingsLib-search-interface",
+ visibility: ["//visibility:private"],
+ srcs: ["interface-src/**/*.java"],
+ host_supported: true,
+}
+
android_library {
name: "SettingsLib-search",
+ static_libs: [
+ "SettingsLib-search-interface",
+ ],
srcs: ["src/**/*.java"],
sdk_version: "system_current",
@@ -19,12 +29,10 @@ java_plugin {
name: "SettingsLib-annotation-processor",
processor_class: "com.android.settingslib.search.IndexableProcessor",
static_libs: [
+ "SettingsLib-search-interface",
"javapoet",
],
- srcs: [
- "processor-src/**/*.java",
- "src/com/android/settingslib/search/SearchIndexable.java",
- ],
+ srcs: ["processor-src/**/*.java"],
java_resource_dirs: ["resources"],
}
diff --git a/packages/SettingsLib/search/common.mk b/packages/SettingsLib/search/common.mk
deleted file mode 100644
index 05226db5cb91..000000000000
--- a/packages/SettingsLib/search/common.mk
+++ /dev/null
@@ -1,10 +0,0 @@
-# Include this file to generate SearchIndexableResourcesImpl
-
-LOCAL_ANNOTATION_PROCESSORS += \
- SettingsLib-annotation-processor
-
-LOCAL_ANNOTATION_PROCESSOR_CLASSES += \
- com.android.settingslib.search.IndexableProcessor
-
-LOCAL_STATIC_JAVA_LIBRARIES += \
- SettingsLib-search
diff --git a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
index 638fa3e98138..174f33709b02 100644
--- a/packages/SettingsLib/search/src/com/android/settingslib/search/SearchIndexable.java
+++ b/packages/SettingsLib/search/interface-src/com/android/settingslib/search/SearchIndexable.java
@@ -27,7 +27,7 @@ public @interface SearchIndexable {
/**
* Bitfield for the form factors this class should be considered indexable for.
* Default is {@link #ALL}.
- *
+ * <p>
* TODO: actually use this value somehow
*/
int forTarget() default ALL;
@@ -35,27 +35,27 @@ public @interface SearchIndexable {
/**
* Indicates that the class should be considered indexable for Mobile.
*/
- int MOBILE = 1<<0;
+ int MOBILE = 1 << 0;
/**
* Indicates that the class should be considered indexable for TV.
*/
- int TV = 1<<1;
+ int TV = 1 << 1;
/**
* Indicates that the class should be considered indexable for Wear.
*/
- int WEAR = 1<<2;
+ int WEAR = 1 << 2;
/**
* Indicates that the class should be considered indexable for Auto.
*/
- int AUTO = 1<<3;
+ int AUTO = 1 << 3;
/**
* Indicates that the class should be considered indexable for ARC++.
*/
- int ARC = 1<<4;
+ int ARC = 1 << 4;
/**
* Indicates that the class should be considered indexable for all targets.
diff --git a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
index e92157e7c867..fa43915deb6a 100644
--- a/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
+++ b/packages/SettingsLib/search/processor-src/com/android/settingslib/search/IndexableProcessor.java
@@ -34,6 +34,7 @@ import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedOptions;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
@@ -48,10 +49,11 @@ import javax.tools.Diagnostic.Kind;
* subclasses.
*/
@SupportedSourceVersion(SourceVersion.RELEASE_17)
+@SupportedOptions(IndexableProcessor.PACKAGE_KEY)
@SupportedAnnotationTypes({"com.android.settingslib.search.SearchIndexable"})
public class IndexableProcessor extends AbstractProcessor {
- private static final String PACKAGE = "com.android.settingslib.search";
+ private static final String SETTINGSLIB_SEARCH_PACKAGE = "com.android.settingslib.search";
private static final String CLASS_BASE = "SearchIndexableResourcesBase";
private static final String CLASS_MOBILE = "SearchIndexableResourcesMobile";
private static final String CLASS_TV = "SearchIndexableResourcesTv";
@@ -59,6 +61,9 @@ public class IndexableProcessor extends AbstractProcessor {
private static final String CLASS_AUTO = "SearchIndexableResourcesAuto";
private static final String CLASS_ARC = "SearchIndexableResourcesArc";
+ static final String PACKAGE_KEY = "com.android.settingslib.search.processor.package";
+
+ private String mPackage;
private Filer mFiler;
private Messager mMessager;
private boolean mRanOnce;
@@ -72,7 +77,8 @@ public class IndexableProcessor extends AbstractProcessor {
}
mRanOnce = true;
- final ClassName searchIndexableData = ClassName.get(PACKAGE, "SearchIndexableData");
+ final ClassName searchIndexableData =
+ ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableData");
final FieldSpec providers = FieldSpec.builder(
ParameterizedTypeName.get(
@@ -130,7 +136,7 @@ public class IndexableProcessor extends AbstractProcessor {
builder = arcConstructorBuilder;
}
builder.addCode(
- "$N(new SearchIndexableData($L.class, $L"
+ "$N(new com.android.settingslib.search.SearchIndexableData($L.class, $L"
+ ".SEARCH_INDEX_DATA_PROVIDER));\n",
addIndex, className, className);
} else {
@@ -150,50 +156,51 @@ public class IndexableProcessor extends AbstractProcessor {
final TypeSpec baseClass = TypeSpec.classBuilder(CLASS_BASE)
.addModifiers(Modifier.PUBLIC)
- .addSuperinterface(ClassName.get(PACKAGE, "SearchIndexableResources"))
+ .addSuperinterface(
+ ClassName.get(SETTINGSLIB_SEARCH_PACKAGE, "SearchIndexableResources"))
.addField(providers)
.addMethod(baseConstructorBuilder.build())
.addMethod(addIndex)
.addMethod(getProviderValues)
.build();
- final JavaFile searchIndexableResourcesBase = JavaFile.builder(PACKAGE, baseClass).build();
+ final JavaFile searchIndexableResourcesBase = JavaFile.builder(mPackage, baseClass).build();
- final JavaFile searchIndexableResourcesMobile = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesMobile = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_MOBILE)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(mobileConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesTv = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesTv = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_TV)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(tvConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesWear = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesWear = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_WEAR)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(wearConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesAuto = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesAuto = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_AUTO)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(autoConstructorBuilder.build())
.build())
.build();
- final JavaFile searchIndexableResourcesArc = JavaFile.builder(PACKAGE,
+ final JavaFile searchIndexableResourcesArc = JavaFile.builder(mPackage,
TypeSpec.classBuilder(CLASS_ARC)
.addModifiers(Modifier.PUBLIC)
- .superclass(ClassName.get(PACKAGE, baseClass.name))
+ .superclass(ClassName.get(mPackage, baseClass.name))
.addMethod(arcConstructorBuilder.build())
.build())
.build();
@@ -214,6 +221,8 @@ public class IndexableProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
+ mPackage = processingEnvironment.getOptions()
+ .getOrDefault(PACKAGE_KEY, SETTINGSLIB_SEARCH_PACKAGE);
mFiler = processingEnvironment.getFiler();
mMessager = processingEnvironment.getMessager();
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 8d4aa9a7b25e..c967b568042c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -688,8 +688,12 @@ public class Utils {
continue;
}
for (int complianceWarningType : complianceWarnings) {
- if (complianceWarningType != 0) {
- return true;
+ switch (complianceWarningType) {
+ case UsbPortStatus.COMPLIANCE_WARNING_OTHER:
+ case UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY:
+ return true;
+ default:
+ break;
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
index fe8988385453..6eb2f3834858 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java
@@ -484,8 +484,9 @@ public class ApplicationsState {
if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
synchronized (mEntriesMap) {
AppEntry entry = null;
- if (mEntriesMap.contains(userId)) {
- entry = mEntriesMap.get(userId).get(packageName);
+ HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ entry = userEntriesMap.get(packageName);
}
if (entry == null) {
ApplicationInfo info = getAppInfoLocked(packageName, userId);
@@ -735,8 +736,9 @@ public class ApplicationsState {
private AppEntry getEntryLocked(ApplicationInfo info) {
int userId = UserHandle.getUserId(info.uid);
AppEntry entry = null;
- if (mEntriesMap.contains(userId)) {
- entry = mEntriesMap.get(userId).get(info.packageName);
+ HashMap<String, AppEntry> userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ entry = userEntriesMap.get(info.packageName);
}
if (DEBUG) {
Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
@@ -752,8 +754,11 @@ public class ApplicationsState {
Log.i(TAG, "Creating AppEntry for " + info.packageName);
}
entry = new AppEntry(mContext, info, mCurId++);
- mEntriesMap.get(userId).put(info.packageName, entry);
- mAppEntries.add(entry);
+ userEntriesMap = mEntriesMap.get(userId);
+ if (userEntriesMap != null) {
+ userEntriesMap.put(info.packageName, entry);
+ mAppEntries.add(entry);
+ }
} else if (entry.info != info) {
entry.info = info;
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 8aacd4d6da54..0bd9384c3ef4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -40,6 +40,7 @@ import static android.media.MediaRoute2Info.TYPE_WIRED_HEADSET;
import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TargetApi;
import android.app.Notification;
@@ -56,7 +57,6 @@ import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.DoNotInline;
-import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import com.android.internal.annotations.VisibleForTesting;
@@ -70,29 +70,17 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import java.util.stream.Collectors;
-/**
- * InfoMediaManager provide interface to get InfoMediaDevice list.
- */
+/** InfoMediaManager provide interface to get InfoMediaDevice list. */
@RequiresApi(Build.VERSION_CODES.R)
-public class InfoMediaManager extends MediaManager {
+public abstract class InfoMediaManager extends MediaManager {
private static final String TAG = "InfoMediaManager";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- @VisibleForTesting
- final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
- @VisibleForTesting
- final Executor mExecutor = Executors.newSingleThreadExecutor();
- @VisibleForTesting
- MediaRouter2Manager mRouterManager;
- @VisibleForTesting
- String mPackageName;
+ protected String mPackageName;
private MediaDevice mCurrentConnectedDevice;
- private LocalBluetoothManager mBluetoothManager;
+ private final LocalBluetoothManager mBluetoothManager;
private final Map<String, RouteListingPreference.Item> mPreferenceItemMap =
new ConcurrentHashMap<>();
@@ -100,7 +88,6 @@ public class InfoMediaManager extends MediaManager {
LocalBluetoothManager localBluetoothManager) {
super(context, notification);
- mRouterManager = MediaRouter2Manager.getInstance(context);
mBluetoothManager = localBluetoothManager;
if (!TextUtils.isEmpty(packageName)) {
mPackageName = packageName;
@@ -110,25 +97,87 @@ public class InfoMediaManager extends MediaManager {
@Override
public void startScan() {
mMediaDevices.clear();
- mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
- mRouterManager.registerScanRequest();
+ startScanOnRouter();
+ updateRouteListingPreference();
+ refreshDevices();
+ }
+
+ private void updateRouteListingPreference() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
&& !TextUtils.isEmpty(mPackageName)) {
RouteListingPreference routeListingPreference =
- mRouterManager.getRouteListingPreference(mPackageName);
- if (routeListingPreference != null) {
- Api34Impl.onRouteListingPreferenceUpdated(null, routeListingPreference,
- mPreferenceItemMap);
- }
+ getRouteListingPreference();
+ Api34Impl.onRouteListingPreferenceUpdated(routeListingPreference,
+ mPreferenceItemMap);
}
- refreshDevices();
}
@Override
- public void stopScan() {
- mRouterManager.unregisterCallback(mMediaRouterCallback);
- mRouterManager.unregisterScanRequest();
- }
+ public abstract void stopScan();
+
+ protected abstract void startScanOnRouter();
+
+ /**
+ * Transfer MediaDevice for media without package name.
+ */
+ protected abstract boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device);
+
+ protected abstract void selectRoute(
+ @NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info);
+
+ protected abstract void deselectRoute(
+ @NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info);
+
+ protected abstract void releaseSession(@NonNull RoutingSessionInfo sessionInfo);
+
+ @NonNull
+ protected abstract List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info);
+
+ @NonNull
+ protected abstract List<MediaRoute2Info> getDeselectableRoutes(
+ @NonNull RoutingSessionInfo info);
+
+ @NonNull
+ protected abstract List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info);
+
+ protected abstract void setSessionVolume(@NonNull RoutingSessionInfo info, int volume);
+
+ @Nullable
+ protected abstract RouteListingPreference getRouteListingPreference();
+
+ /**
+ * Returns the list of currently active {@link RoutingSessionInfo routing sessions} known to the
+ * system.
+ */
+ @NonNull
+ protected abstract List<RoutingSessionInfo> getActiveRoutingSessions();
+
+ @NonNull
+ protected abstract List<RoutingSessionInfo> getRoutingSessionsForPackage();
+
+ @NonNull
+ protected abstract List<MediaRoute2Info> getAllRoutes();
+
+ @NonNull
+ protected abstract List<MediaRoute2Info> getAvailableRoutesFromRouter();
+
+ @NonNull
+ protected abstract List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName);
+
+ @NonNull
+ protected abstract ComplexMediaDevice createComplexMediaDevice(
+ MediaRoute2Info route, RouteListingPreference.Item routeListingPreferenceItem);
+
+ @NonNull
+ protected abstract InfoMediaDevice createInfoMediaDevice(
+ MediaRoute2Info route, RouteListingPreference.Item routeListingPreferenceItem);
+
+ @NonNull
+ protected abstract PhoneMediaDevice createPhoneMediaDevice(MediaRoute2Info route);
+
+ @NonNull
+ protected abstract BluetoothMediaDevice createBluetoothMediaDevice(
+ MediaRoute2Info route, CachedBluetoothDevice cachedDevice);
/**
* Get current device that played media.
@@ -139,18 +188,6 @@ public class InfoMediaManager extends MediaManager {
}
/**
- * Transfer MediaDevice for media without package name.
- */
- boolean connectDeviceWithoutPackageName(MediaDevice device) {
- final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
- if (info != null) {
- mRouterManager.transfer(info, device.mRouteInfo);
- return true;
- }
- return false;
- }
-
- /**
* Add a MediaDevice to let it play current media.
*
* @param device MediaDevice
@@ -169,26 +206,21 @@ public class InfoMediaManager extends MediaManager {
return false;
}
- mRouterManager.selectRoute(info, device.mRouteInfo);
+ selectRoute(device.mRouteInfo, info);
return true;
}
private RoutingSessionInfo getRoutingSessionInfo() {
- return getRoutingSessionInfo(mPackageName);
- }
-
- private RoutingSessionInfo getRoutingSessionInfo(String packageName) {
- final List<RoutingSessionInfo> sessionInfos =
- mRouterManager.getRoutingSessions(packageName);
+ final List<RoutingSessionInfo> sessionInfos = getRoutingSessionsForPackage();
- if (sessionInfos == null || sessionInfos.isEmpty()) {
+ if (sessionInfos.isEmpty()) {
return null;
}
return sessionInfos.get(sessionInfos.size() - 1);
}
boolean isRoutingSessionAvailableForVolumeControl() {
- List<RoutingSessionInfo> sessions = mRouterManager.getRoutingSessions(mPackageName);
+ List<RoutingSessionInfo> sessions = getRoutingSessionsForPackage();
for (RoutingSessionInfo session : sessions) {
if (!session.isSystemSession()
@@ -203,15 +235,17 @@ public class InfoMediaManager extends MediaManager {
boolean preferRouteListingOrdering() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
- && Api34Impl.preferRouteListingOrdering(mRouterManager, mPackageName);
+ && !TextUtils.isEmpty(mPackageName)
+ && Api34Impl.preferRouteListingOrdering(getRouteListingPreference());
}
@Nullable
ComponentName getLinkedItemComponentName() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.UPSIDE_DOWN_CAKE && TextUtils.isEmpty(
+ mPackageName)) {
return null;
}
- return Api34Impl.getLinkedItemComponentName(mRouterManager, mPackageName);
+ return Api34Impl.getLinkedItemComponentName(getRouteListingPreference());
}
/**
@@ -233,7 +267,7 @@ public class InfoMediaManager extends MediaManager {
return false;
}
- mRouterManager.deselectRoute(info, device.mRouteInfo);
+ deselectRoute(device.mRouteInfo, info);
return true;
}
@@ -252,11 +286,10 @@ public class InfoMediaManager extends MediaManager {
return false;
}
- mRouterManager.releaseSession(sessionInfo);
+ releaseSession(sessionInfo);
return true;
}
-
/**
* Returns the list of {@link MediaDevice media devices} that can be added to the current {@link
* RoutingSessionInfo routing session}.
@@ -276,9 +309,9 @@ public class InfoMediaManager extends MediaManager {
}
final List<MediaDevice> deviceList = new ArrayList<>();
- for (MediaRoute2Info route : mRouterManager.getSelectableRoutes(info)) {
- deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
- route, mPackageName, mPreferenceItemMap.get(route.getId())));
+ for (MediaRoute2Info route : getSelectableRoutes(info)) {
+ deviceList.add(
+ createInfoMediaDevice(route, mPreferenceItemMap.get(route.getId())));
}
return deviceList;
}
@@ -302,9 +335,9 @@ public class InfoMediaManager extends MediaManager {
}
final List<MediaDevice> deviceList = new ArrayList<>();
- for (MediaRoute2Info route : mRouterManager.getDeselectableRoutes(info)) {
- deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
- route, mPackageName, mPreferenceItemMap.get(route.getId())));
+ for (MediaRoute2Info route : getDeselectableRoutes(info)) {
+ deviceList.add(
+ createInfoMediaDevice(route, mPreferenceItemMap.get(route.getId())));
Log.d(TAG, route.getName() + " is deselectable for " + mPackageName);
}
return deviceList;
@@ -329,9 +362,9 @@ public class InfoMediaManager extends MediaManager {
}
final List<MediaDevice> deviceList = new ArrayList<>();
- for (MediaRoute2Info route : mRouterManager.getSelectedRoutes(info)) {
- deviceList.add(new InfoMediaDevice(mContext, mRouterManager,
- route, mPackageName, mPreferenceItemMap.get(route.getId())));
+ for (MediaRoute2Info route : getSelectedRoutes(info)) {
+ deviceList.add(
+ createInfoMediaDevice(route, mPreferenceItemMap.get(route.getId())));
}
return deviceList;
}
@@ -342,7 +375,7 @@ public class InfoMediaManager extends MediaManager {
return;
}
- mRouterManager.setSessionVolume(info, volume);
+ setSessionVolume(info, volume);
}
/**
@@ -364,7 +397,7 @@ public class InfoMediaManager extends MediaManager {
}
Log.d(TAG, "adjustSessionVolume() adjust volume: " + volume + ", with : " + mPackageName);
- mRouterManager.setSessionVolume(info, volume);
+ setSessionVolume(info, volume);
}
/**
@@ -431,7 +464,7 @@ public class InfoMediaManager extends MediaManager {
}
// Disable when there is no transferable route
- return mRouterManager.getTransferableRoutes(packageName).isEmpty();
+ return getTransferableRoutes(packageName).isEmpty();
}
@TargetApi(Build.VERSION_CODES.R)
@@ -454,7 +487,7 @@ public class InfoMediaManager extends MediaManager {
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
private void buildAllRoutes() {
- for (MediaRoute2Info route : mRouterManager.getAllRoutes()) {
+ for (MediaRoute2Info route : getAllRoutes()) {
if (DEBUG) {
Log.d(TAG, "buildAllRoutes() route : " + route.getName() + ", volume : "
+ route.getVolume() + ", type : " + route.getType());
@@ -465,22 +498,10 @@ public class InfoMediaManager extends MediaManager {
}
}
- /**
- * Returns the list of currently active {@link RoutingSessionInfo routing sessions} known to the
- * system.
- */
- @NonNull
- List<RoutingSessionInfo> getActiveRoutingSessions() {
- List<RoutingSessionInfo> infos = new ArrayList<>();
- infos.add(mRouterManager.getSystemRoutingSession(null));
- infos.addAll(mRouterManager.getRemoteSessions());
- return infos;
- }
-
// MediaRoute2Info.getType was made public on API 34, but exists since API 30.
@SuppressWarnings("NewApi")
private synchronized void buildAvailableRoutes() {
- for (MediaRoute2Info route : getAvailableRoutes(mPackageName)) {
+ for (MediaRoute2Info route : getAvailableRoutes()) {
if (DEBUG) {
Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : "
+ route.getVolume() + ", type : " + route.getType());
@@ -488,18 +509,17 @@ public class InfoMediaManager extends MediaManager {
addMediaDevice(route);
}
}
-
- private synchronized List<MediaRoute2Info> getAvailableRoutes(String packageName) {
+ private synchronized List<MediaRoute2Info> getAvailableRoutes() {
List<MediaRoute2Info> infos = new ArrayList<>();
- RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo(packageName);
+ RoutingSessionInfo routingSessionInfo = getRoutingSessionInfo();
List<MediaRoute2Info> selectedRouteInfos = new ArrayList<>();
if (routingSessionInfo != null) {
- selectedRouteInfos = mRouterManager.getSelectedRoutes(routingSessionInfo);
+ selectedRouteInfos = getSelectedRoutes(routingSessionInfo);
infos.addAll(selectedRouteInfos);
- infos.addAll(mRouterManager.getSelectableRoutes(routingSessionInfo));
+ infos.addAll(getSelectableRoutes(routingSessionInfo));
}
final List<MediaRoute2Info> transferableRoutes =
- mRouterManager.getTransferableRoutes(packageName);
+ getTransferableRoutes(mPackageName);
for (MediaRoute2Info transferableRoute : transferableRoutes) {
boolean alreadyAdded = false;
for (MediaRoute2Info mediaRoute2Info : infos) {
@@ -514,14 +534,13 @@ public class InfoMediaManager extends MediaManager {
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE
&& !TextUtils.isEmpty(mPackageName)) {
- RouteListingPreference routeListingPreference =
- mRouterManager.getRouteListingPreference(mPackageName);
+ RouteListingPreference routeListingPreference = getRouteListingPreference();
if (routeListingPreference != null) {
final List<RouteListingPreference.Item> preferenceRouteListing =
Api34Impl.composePreferenceRouteListing(
routeListingPreference);
infos = Api34Impl.arrangeRouteListByPreference(selectedRouteInfos,
- mRouterManager.getAvailableRoutes(packageName),
+ getAvailableRoutesFromRouter(),
preferenceRouteListing);
}
return Api34Impl.filterDuplicatedIds(infos);
@@ -547,8 +566,8 @@ public class InfoMediaManager extends MediaManager {
case TYPE_REMOTE_GAME_CONSOLE:
case TYPE_REMOTE_CAR:
case TYPE_REMOTE_SMARTWATCH:
- mediaDevice = new InfoMediaDevice(mContext, mRouterManager, route,
- mPackageName, mPreferenceItemMap.get(route.getId()));
+ mediaDevice =
+ createInfoMediaDevice(route, mPreferenceItemMap.get(route.getId()));
break;
case TYPE_BUILTIN_SPEAKER:
case TYPE_USB_DEVICE:
@@ -558,8 +577,7 @@ public class InfoMediaManager extends MediaManager {
case TYPE_HDMI:
case TYPE_WIRED_HEADSET:
case TYPE_WIRED_HEADPHONES:
- mediaDevice =
- new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
+ mediaDevice = createPhoneMediaDevice(route);
break;
case TYPE_HEARING_AID:
case TYPE_BLUETOOTH_A2DP:
@@ -569,13 +587,13 @@ public class InfoMediaManager extends MediaManager {
final CachedBluetoothDevice cachedDevice =
mBluetoothManager.getCachedDeviceManager().findDevice(device);
if (cachedDevice != null) {
- mediaDevice = new BluetoothMediaDevice(mContext, cachedDevice, mRouterManager,
- route, mPackageName);
+ mediaDevice = createBluetoothMediaDevice(route, cachedDevice);
}
break;
case TYPE_REMOTE_AUDIO_VIDEO_RECEIVER:
- mediaDevice = new ComplexMediaDevice(mContext, mRouterManager, route,
- mPackageName, mPreferenceItemMap.get(route.getId()));
+ mediaDevice =
+ createComplexMediaDevice(
+ route, mPreferenceItemMap.get(route.getId()));
default:
Log.w(TAG, "addMediaDevice() unknown device type : " + deviceType);
break;
@@ -654,16 +672,16 @@ public class InfoMediaManager extends MediaManager {
public void onRouteListingPreferenceUpdated(
String packageName,
RouteListingPreference routeListingPreference) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
- Api34Impl.onRouteListingPreferenceUpdated(packageName, routeListingPreference,
- mPreferenceItemMap);
+ if (TextUtils.equals(mPackageName, packageName)) {
+ Api34Impl.onRouteListingPreferenceUpdated(
+ routeListingPreference, mPreferenceItemMap);
refreshDevices();
}
}
}
@RequiresApi(34)
- private static class Api34Impl {
+ static class Api34Impl {
@DoNotInline
static List<RouteListingPreference.Item> composePreferenceRouteListing(
RouteListingPreference routeListingPreference) {
@@ -712,19 +730,13 @@ public class InfoMediaManager extends MediaManager {
if (sortedInfoList.size() != infolist.size()) {
infolist.removeAll(sortedInfoList);
sortedInfoList.addAll(infolist.stream().filter(
- MediaRoute2Info::isSystemRoute).collect(Collectors.toList()));
+ MediaRoute2Info::isSystemRoute).toList());
}
return sortedInfoList;
}
@DoNotInline
- static boolean preferRouteListingOrdering(MediaRouter2Manager mediaRouter2Manager,
- String packageName) {
- if (TextUtils.isEmpty(packageName)) {
- return false;
- }
- RouteListingPreference routeListingPreference =
- mediaRouter2Manager.getRouteListingPreference(packageName);
+ static boolean preferRouteListingOrdering(RouteListingPreference routeListingPreference) {
return routeListingPreference != null
&& !routeListingPreference.getUseSystemOrdering();
}
@@ -732,26 +744,19 @@ public class InfoMediaManager extends MediaManager {
@DoNotInline
@Nullable
static ComponentName getLinkedItemComponentName(
- MediaRouter2Manager mediaRouter2Manager, String packageName) {
- if (TextUtils.isEmpty(packageName)) {
- return null;
- }
- RouteListingPreference routeListingPreference =
- mediaRouter2Manager.getRouteListingPreference(packageName);
+ RouteListingPreference routeListingPreference) {
return routeListingPreference == null ? null
: routeListingPreference.getLinkedItemComponentName();
}
@DoNotInline
static void onRouteListingPreferenceUpdated(
- String packageName,
RouteListingPreference routeListingPreference,
Map<String, RouteListingPreference.Item> preferenceItemMap) {
preferenceItemMap.clear();
if (routeListingPreference != null) {
- routeListingPreference.getItems().forEach((item) -> {
- preferenceItemMap.put(item.getRouteId(), item);
- });
+ routeListingPreference.getItems().forEach((item) ->
+ preferenceItemMap.put(item.getRouteId(), item));
}
}
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index 2ce2636c2440..987f61649ba9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -137,7 +137,8 @@ public class LocalMediaManager implements BluetoothCallback {
}
mInfoMediaManager =
- new InfoMediaManager(context, packageName, notification, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(
+ context, packageName, notification, mLocalBluetoothManager);
}
/**
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
new file mode 100644
index 000000000000..4f081369f5bf
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -0,0 +1,187 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.media;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Notification;
+import android.content.Context;
+import android.media.MediaRoute2Info;
+import android.media.MediaRouter2Manager;
+import android.media.RouteListingPreference;
+import android.media.RoutingSessionInfo;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.LocalBluetoothManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+
+/**
+ * Template implementation of {@link InfoMediaManager} using {@link MediaRouter2Manager}.
+ */
+public class ManagerInfoMediaManager extends InfoMediaManager {
+
+ @VisibleForTesting
+ /* package */ final RouterManagerCallback mMediaRouterCallback = new RouterManagerCallback();
+ @VisibleForTesting
+ /* package */ MediaRouter2Manager mRouterManager;
+
+ private final Executor mExecutor = Executors.newSingleThreadExecutor();
+
+ public ManagerInfoMediaManager(
+ Context context,
+ String packageName,
+ Notification notification,
+ LocalBluetoothManager localBluetoothManager) {
+ super(context, packageName, notification, localBluetoothManager);
+
+ mRouterManager = MediaRouter2Manager.getInstance(context);
+ }
+
+ @Override
+ protected void startScanOnRouter() {
+ mRouterManager.registerCallback(mExecutor, mMediaRouterCallback);
+ mRouterManager.registerScanRequest();
+ }
+
+ @Override
+ public void stopScan() {
+ mRouterManager.unregisterCallback(mMediaRouterCallback);
+ mRouterManager.unregisterScanRequest();
+ }
+
+ @Override
+ protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
+ final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
+ if (info != null) {
+ mRouterManager.transfer(info, device.mRouteInfo);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ protected void selectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+ mRouterManager.selectRoute(info, route);
+ }
+
+ @Override
+ protected void deselectRoute(@NonNull MediaRoute2Info route, @NonNull RoutingSessionInfo info) {
+ mRouterManager.deselectRoute(info, route);
+ }
+
+ @Override
+ protected void releaseSession(@NonNull RoutingSessionInfo sessionInfo) {
+ mRouterManager.releaseSession(sessionInfo);
+ }
+
+ @Override
+ @NonNull
+ protected List<MediaRoute2Info> getSelectableRoutes(@NonNull RoutingSessionInfo info) {
+ return mRouterManager.getSelectableRoutes(info);
+ }
+
+ @Override
+ @NonNull
+ protected List<MediaRoute2Info> getDeselectableRoutes(@NonNull RoutingSessionInfo info) {
+ return mRouterManager.getDeselectableRoutes(info);
+ }
+
+ @Override
+ @NonNull
+ protected List<MediaRoute2Info> getSelectedRoutes(@NonNull RoutingSessionInfo info) {
+ return mRouterManager.getSelectedRoutes(info);
+ }
+
+ @Override
+ protected void setSessionVolume(@NonNull RoutingSessionInfo info, int volume) {
+ mRouterManager.setSessionVolume(info, volume);
+ }
+
+ @Override
+ @Nullable
+ protected RouteListingPreference getRouteListingPreference() {
+ return mRouterManager.getRouteListingPreference(mPackageName);
+ }
+
+ @Override
+ @NonNull
+ protected List<RoutingSessionInfo> getRoutingSessionsForPackage() {
+ return mRouterManager.getRoutingSessions(mPackageName);
+ }
+
+ @Override
+ @NonNull
+ protected List<RoutingSessionInfo> getActiveRoutingSessions() {
+ List<RoutingSessionInfo> infos = new ArrayList<>();
+ infos.add(mRouterManager.getSystemRoutingSession(null));
+ infos.addAll(mRouterManager.getRemoteSessions());
+ return infos;
+ }
+
+ @Override
+ @NonNull
+ protected List<MediaRoute2Info> getAllRoutes() {
+ return mRouterManager.getAllRoutes();
+ }
+
+ @Override
+ @NonNull
+ protected List<MediaRoute2Info> getAvailableRoutesFromRouter() {
+ return mRouterManager.getAvailableRoutes(mPackageName);
+ }
+
+ @Override
+ @NonNull
+ protected List<MediaRoute2Info> getTransferableRoutes(@NonNull String packageName) {
+ return mRouterManager.getTransferableRoutes(packageName);
+ }
+
+ @Override
+ @NonNull
+ protected ComplexMediaDevice createComplexMediaDevice(
+ MediaRoute2Info route, RouteListingPreference.Item routeListingPreferenceItem) {
+ return new ComplexMediaDevice(
+ mContext, mRouterManager, route, mPackageName, routeListingPreferenceItem);
+ }
+
+ @Override
+ @NonNull
+ protected InfoMediaDevice createInfoMediaDevice(
+ MediaRoute2Info route, RouteListingPreference.Item routeListingPreferenceItem) {
+ return new InfoMediaDevice(
+ mContext, mRouterManager, route, mPackageName, routeListingPreferenceItem);
+ }
+
+ @Override
+ @NonNull
+ protected PhoneMediaDevice createPhoneMediaDevice(MediaRoute2Info route) {
+ return new PhoneMediaDevice(mContext, mRouterManager, route, mPackageName);
+ }
+
+ @Override
+ @NonNull
+ protected BluetoothMediaDevice createBluetoothMediaDevice(
+ MediaRoute2Info route, CachedBluetoothDevice cachedDevice) {
+ return new BluetoothMediaDevice(
+ mContext, cachedDevice, mRouterManager, route, mPackageName);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index 0637e5d27f57..4a913c87bddf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -455,12 +455,24 @@ public class UtilsTest {
}
@Test
- public void containsIncompatibleChargers_returnTrue() {
- setupIncompatibleCharging();
+ public void containsIncompatibleChargers_complianeWarningOther_returnTrue() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
+ }
+
+ @Test
+ public void containsIncompatibleChargers_complianeWarningDebug_returnTrue() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_DEBUG_ACCESSORY);
assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isTrue();
}
@Test
+ public void containsIncompatibleChargers_unexpectedWarningType_returnFalse() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_BC_1_2);
+ assertThat(Utils.containsIncompatibleChargers(mContext, TAG)).isFalse();
+ }
+
+ @Test
public void containsIncompatibleChargers_emptyComplianceWarnings_returnFalse() {
setupIncompatibleCharging();
when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[1]);
@@ -494,12 +506,17 @@ public class UtilsTest {
}
private void setupIncompatibleCharging() {
+ setupIncompatibleCharging(UsbPortStatus.COMPLIANCE_WARNING_OTHER);
+ }
+
+ private void setupIncompatibleCharging(int complianceWarningType) {
final List<UsbPort> usbPorts = new ArrayList<>();
usbPorts.add(mUsbPort);
when(mUsbManager.getPorts()).thenReturn(usbPorts);
when(mUsbPort.getStatus()).thenReturn(mUsbPortStatus);
when(mUsbPort.supportsComplianceWarnings()).thenReturn(true);
when(mUsbPortStatus.isConnected()).thenReturn(true);
- when(mUsbPortStatus.getComplianceWarnings()).thenReturn(new int[]{1});
+ when(mUsbPortStatus.getComplianceWarnings())
+ .thenReturn(new int[]{complianceWarningType});
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
index 1d081d7214cc..34d8148f418f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/applications/ApplicationsStateRoboTest.java
@@ -804,7 +804,7 @@ public class ApplicationsStateRoboTest {
}
@Test
- public void getEntry_validUserId_shouldReturnEntry() {
+ public void getEntry_hasCache_shouldReturnCacheEntry() {
mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
addApp(PKG_1, /* id= */ 1);
@@ -813,10 +813,13 @@ public class ApplicationsStateRoboTest {
}
@Test
- public void getEntry_invalidUserId_shouldReturnNull() {
- mApplicationsState.mEntriesMap.put(/* userId= */ 0, new HashMap<>());
- addApp(PKG_1, /* id= */ 1);
+ public void getEntry_hasNoCache_shouldReturnEntry() {
+ mApplicationsState.mEntriesMap.clear();
+ ApplicationInfo appInfo = createApplicationInfo(PKG_1, /* uid= */ 0);
+ mApplicationsState.mApplications.add(appInfo);
+ mApplicationsState.mSystemModules.put(PKG_1, /* value= */ false);
- assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ -1)).isNull();
+ assertThat(mApplicationsState.getEntry(PKG_1, /* userId= */ 0).info.packageName)
+ .isEqualTo(PKG_1);
}
}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
index 4d45e55d41c8..ce1744dd415e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java
@@ -73,6 +73,7 @@ import java.util.Set;
public class InfoMediaManagerTest {
private static final String TEST_PACKAGE_NAME = "com.test.packagename";
+ private static final String TEST_PACKAGE_NAME_2 = "com.test.packagename2";
private static final String TEST_ID = "test_id";
private static final String TEST_ID_1 = "test_id_1";
private static final String TEST_ID_2 = "test_id_2";
@@ -95,7 +96,7 @@ public class InfoMediaManagerTest {
@Mock
private ComponentName mComponentName;
- private InfoMediaManager mInfoMediaManager;
+ private ManagerInfoMediaManager mInfoMediaManager;
private Context mContext;
private ShadowRouter2Manager mShadowRouter2Manager;
@@ -107,7 +108,8 @@ public class InfoMediaManagerTest {
doReturn(mMediaSessionManager).when(mContext).getSystemService(
Context.MEDIA_SESSION_SERVICE);
mInfoMediaManager =
- new InfoMediaManager(mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
+ new ManagerInfoMediaManager(
+ mContext, TEST_PACKAGE_NAME, null, mLocalBluetoothManager);
mShadowRouter2Manager = ShadowRouter2Manager.getShadow();
mInfoMediaManager.mRouterManager = MediaRouter2Manager.getInstance(mContext);
}
@@ -308,7 +310,54 @@ public class InfoMediaManagerTest {
}
@Test
- public void onRouteChanged_getAvailableRoutesWithPrefernceListExit_ordersRoutes() {
+ public void onRouteChanged_getAvailableRoutesWithPreferenceListExit_ordersRoutes() {
+ RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME);
+ setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo);
+
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+ when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+ setAvailableRoutesList(TEST_PACKAGE_NAME);
+
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
+ routeListingPreference);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
+ assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+ assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
+ assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
+ assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
+ }
+
+ @Test
+ public void onRouteChanged_preferenceListUpdateWithDifferentPkg_notOrdersRoutes() {
+ RouteListingPreference routeListingPreference = setUpPreferenceList(TEST_PACKAGE_NAME_2);
+ setUpSelectedRoutes(TEST_PACKAGE_NAME);
+
+ final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
+ final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
+ routingSessionInfos.add(sessionInfo);
+
+ when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
+ when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
+
+ setAvailableRoutesList(TEST_PACKAGE_NAME);
+ mInfoMediaManager.mRouterManager = mRouterManager;
+ mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME_2,
+ routeListingPreference);
+ mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
+
+ assertThat(mInfoMediaManager.mMediaDevices).hasSize(1);
+ assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
+ }
+
+ private RouteListingPreference setUpPreferenceList(String packageName) {
ReflectionHelpers.setStaticField(Build.VERSION.class, "SDK_INT",
Build.VERSION_CODES.UPSIDE_DOWN_CAKE);
final List<RouteListingPreference.Item> preferenceItemList = new ArrayList<>();
@@ -324,57 +373,40 @@ public class InfoMediaManagerTest {
RouteListingPreference routeListingPreference =
new RouteListingPreference.Builder().setItems(
preferenceItemList).setUseSystemOrdering(false).build();
- when(mRouterManager.getRouteListingPreference(TEST_PACKAGE_NAME))
+ when(mRouterManager.getRouteListingPreference(packageName))
.thenReturn(routeListingPreference);
+ return routeListingPreference;
+ }
+ private void setUpSelectedRoutes(String packageName) {
final List<MediaRoute2Info> selectedRoutes = new ArrayList<>();
final MediaRoute2Info info = mock(MediaRoute2Info.class);
when(info.getId()).thenReturn(TEST_ID);
- when(info.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(info.getClientPackageName()).thenReturn(packageName);
when(info.isSystemRoute()).thenReturn(true);
selectedRoutes.add(info);
when(mRouterManager.getSelectedRoutes(any())).thenReturn(selectedRoutes);
-
- final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>();
- final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class);
- routingSessionInfos.add(sessionInfo);
-
- when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos);
- when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID));
-
- setAvailableRoutesList();
-
- mInfoMediaManager.mRouterManager = mRouterManager;
- mInfoMediaManager.mMediaRouterCallback.onRouteListingPreferenceUpdated(TEST_PACKAGE_NAME,
- routeListingPreference);
- mInfoMediaManager.mMediaRouterCallback.onRoutesUpdated();
-
- assertThat(mInfoMediaManager.mMediaDevices).hasSize(3);
- assertThat(mInfoMediaManager.mMediaDevices.get(0).getId()).isEqualTo(TEST_ID);
- assertThat(mInfoMediaManager.mMediaDevices.get(1).getId()).isEqualTo(TEST_ID_4);
- assertThat(mInfoMediaManager.mMediaDevices.get(1).isSuggestedDevice()).isTrue();
- assertThat(mInfoMediaManager.mMediaDevices.get(2).getId()).isEqualTo(TEST_ID_3);
}
- private List<MediaRoute2Info> setAvailableRoutesList() {
+ private List<MediaRoute2Info> setAvailableRoutesList(String packageName) {
final List<MediaRoute2Info> availableRoutes = new ArrayList<>();
final MediaRoute2Info availableInfo1 = mock(MediaRoute2Info.class);
when(availableInfo1.getId()).thenReturn(TEST_ID_2);
- when(availableInfo1.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo1.getClientPackageName()).thenReturn(packageName);
when(availableInfo1.getType()).thenReturn(TYPE_REMOTE_TV);
availableRoutes.add(availableInfo1);
final MediaRoute2Info availableInfo2 = mock(MediaRoute2Info.class);
when(availableInfo2.getId()).thenReturn(TEST_ID_3);
- when(availableInfo2.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo2.getClientPackageName()).thenReturn(packageName);
availableRoutes.add(availableInfo2);
final MediaRoute2Info availableInfo3 = mock(MediaRoute2Info.class);
when(availableInfo3.getId()).thenReturn(TEST_ID_4);
- when(availableInfo3.getClientPackageName()).thenReturn(TEST_PACKAGE_NAME);
+ when(availableInfo3.getClientPackageName()).thenReturn(packageName);
availableRoutes.add(availableInfo3);
- when(mRouterManager.getAvailableRoutes(TEST_PACKAGE_NAME)).thenReturn(
+ when(mRouterManager.getAvailableRoutes(packageName)).thenReturn(
availableRoutes);
return availableRoutes;
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 476c820a79a7..b7b86ef18299 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -817,7 +817,8 @@
<service
android:name=".dreams.DreamOverlayService"
android:enabled="false"
- android:exported="true" />
+ android:exported="true"
+ android:singleUser="true" />
<activity android:name=".keyguard.WorkLockActivity"
android:label="@string/accessibility_desc_work_lock"
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 83e44b69812b..7e1bfb921ca9 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -18,6 +18,7 @@ package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
+import android.util.Log
import android.util.LruCache
import android.util.MathUtils
import android.util.MathUtils.abs
@@ -114,6 +115,9 @@ class FontInterpolator {
tmpInterpKey.set(start, end, progress)
val cachedFont = interpCache[tmpInterpKey]
if (cachedFont != null) {
+ if (DEBUG) {
+ Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey")
+ }
return cachedFont
}
@@ -159,6 +163,9 @@ class FontInterpolator {
val axesCachedFont = verFontCache[tmpVarFontKey]
if (axesCachedFont != null) {
interpCache.put(InterpKey(start, end, progress), axesCachedFont)
+ if (DEBUG) {
+ Log.d(LOG_TAG, "[$progress] Axis cache hit for $tmpVarFontKey")
+ }
return axesCachedFont
}
@@ -168,6 +175,9 @@ class FontInterpolator {
val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
interpCache.put(InterpKey(start, end, progress), newFont)
verFontCache.put(VarFontKey(start, newAxes), newFont)
+ if (DEBUG) {
+ Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey")
+ }
return newFont
}
@@ -233,6 +243,8 @@ class FontInterpolator {
(v.coerceIn(min, max) / step).toInt() * step
companion object {
+ private const val LOG_TAG = "FontInterpolator"
+ private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
// Returns true if given two font instance can be interpolated.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 00108940e6ec..16ddf0c36d9d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -26,6 +26,7 @@ import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.LruCache
+import kotlin.math.roundToInt
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -63,9 +64,9 @@ class TypefaceVariantCacheImpl(
return it
}
- return TypefaceVariantCache
- .createVariantTypeface(baseTypeface, fvar)
- .also { cache.put(fvar, it) }
+ return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also {
+ cache.put(fvar, it)
+ }
}
}
@@ -74,7 +75,6 @@ class TypefaceVariantCacheImpl(
*
* Currently this class can provide text style animation for text weight and text size. For example
* the simple view that draws text with animating text size is like as follows:
- *
* <pre> <code>
* ```
* class SimpleTextAnimation : View {
@@ -97,6 +97,7 @@ class TypefaceVariantCacheImpl(
*/
class TextAnimator(
layout: Layout,
+ numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps.
private val invalidateCallback: () -> Unit,
) {
var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface)
@@ -112,7 +113,8 @@ class TextAnimator(
ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
- textInterpolator.progress = it.animatedValue as Float
+ textInterpolator.progress =
+ calculateProgress(it.animatedValue as Float, numberOfAnimationSteps)
invalidateCallback()
}
addListener(
@@ -123,6 +125,17 @@ class TextAnimator(
)
}
+ private fun calculateProgress(animProgress: Float, numberOfAnimationSteps: Int?): Float {
+ if (numberOfAnimationSteps != null) {
+ // This clamps the progress to the nearest value of "numberOfAnimationSteps"
+ // discrete values between 0 and 1f.
+ return (animProgress * numberOfAnimationSteps).roundToInt() /
+ numberOfAnimationSteps.toFloat()
+ }
+
+ return animProgress
+ }
+
sealed class PositionedGlyph {
/** Mutable X coordinate of the glyph position relative from drawing offset. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
index 89871fa7d875..2cd587ffbc45 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseAnimationConfig.kt
@@ -56,7 +56,19 @@ data class TurbulenceNoiseAnimationConfig(
val easeOutDuration: Float = DEFAULT_EASING_DURATION_IN_MILLIS,
val pixelDensity: Float = 1f,
val blendMode: BlendMode = DEFAULT_BLEND_MODE,
- val onAnimationEnd: Runnable? = null
+ val onAnimationEnd: Runnable? = null,
+ /**
+ * Variants in noise. Higher number means more contrast; lower number means less contrast but
+ * make the noise dimmed. You may want to increase the [lumaMatteBlendFactor] to compensate.
+ * Expected range [0, 1].
+ */
+ val lumaMatteBlendFactor: Float = DEFAULT_LUMA_MATTE_BLEND_FACTOR,
+ /**
+ * Offset for the overall brightness in noise. Higher number makes the noise brighter. You may
+ * want to use this if you have made the noise softer using [lumaMatteBlendFactor]. Expected
+ * range [0, 1].
+ */
+ val lumaMatteOverallBrightness: Float = DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS
) {
companion object {
const val DEFAULT_MAX_DURATION_IN_MILLIS = 30_000f // Max 30 sec
@@ -66,6 +78,8 @@ data class TurbulenceNoiseAnimationConfig(
const val DEFAULT_NOISE_SPEED_Z = 0.3f
const val DEFAULT_OPACITY = 150 // full opacity is 255.
const val DEFAULT_COLOR = Color.WHITE
+ const val DEFAULT_LUMA_MATTE_BLEND_FACTOR = 1f
+ const val DEFAULT_LUMA_MATTE_OVERALL_BRIGHTNESS = 0f
const val DEFAULT_BACKGROUND_COLOR = Color.BLACK
val DEFAULT_BLEND_MODE = BlendMode.SRC_OVER
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
index d1ba7c4de35c..d3c57c91405a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt
@@ -37,6 +37,8 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uniform float in_opacity;
uniform float in_pixelDensity;
uniform float in_inverseLuma;
+ uniform half in_lumaMatteBlendFactor;
+ uniform half in_lumaMatteOverallBrightness;
layout(color) uniform vec4 in_color;
layout(color) uniform vec4 in_backgroundColor;
"""
@@ -48,18 +50,21 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity;
+ // Bring it to [0, 1] range.
+ float luma = (simplex3d(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+ luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
vec3 mask = maskLuminosity(in_color.rgb, luma);
vec3 color = in_backgroundColor.rgb + mask * 0.6;
- // Add dither with triangle distribution to avoid color banding. Ok to dither in the
+ // Add dither with triangle distribution to avoid color banding. Dither in the
// shader here as we are in gamma space.
float dither = triangleNoise(p * in_pixelDensity) / 255.;
// The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
// multiply rgb with a to get the correct result.
- color = (color + dither.rrr) * in_color.a;
- return vec4(color, in_color.a);
+ color = (color + dither.rrr) * in_opacity;
+ return vec4(color, in_opacity);
}
"""
@@ -70,12 +75,15 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
uv.x *= in_aspectRatio;
vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
- float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity;
+ // Bring it to [0, 1] range.
+ float luma = (simplex3d_fractal(noiseP) * in_inverseLuma) * 0.5 + 0.5;
+ luma = saturate(luma * in_lumaMatteBlendFactor + in_lumaMatteOverallBrightness)
+ * in_opacity;
vec3 mask = maskLuminosity(in_color.rgb, luma);
vec3 color = in_backgroundColor.rgb + mask * 0.6;
// Skip dithering.
- return vec4(color * in_color.a, in_color.a);
+ return vec4(color * in_opacity, in_opacity);
}
"""
@@ -125,6 +133,28 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
}
/**
+ * Sets blend and brightness factors of the luma matte.
+ *
+ * @param lumaMatteBlendFactor increases or decreases the amount of variance in noise. Setting
+ * this a lower number removes variations. I.e. the turbulence noise will look more blended.
+ * Expected input range is [0, 1]. more dimmed.
+ * @param lumaMatteOverallBrightness adds the overall brightness of the turbulence noise.
+ * Expected input range is [0, 1].
+ *
+ * Example usage: You may want to apply a small number to [lumaMatteBlendFactor], such as 0.2,
+ * which makes the noise look softer. However it makes the overall noise look dim, so you want
+ * offset something like 0.3 for [lumaMatteOverallBrightness] to bring back its overall
+ * brightness.
+ */
+ fun setLumaMatteFactors(
+ lumaMatteBlendFactor: Float = 1f,
+ lumaMatteOverallBrightness: Float = 0f
+ ) {
+ setFloatUniform("in_lumaMatteBlendFactor", lumaMatteBlendFactor)
+ setFloatUniform("in_lumaMatteOverallBrightness", lumaMatteOverallBrightness)
+ }
+
+ /**
* Sets whether to inverse the luminosity of the noise.
*
* By default noise will be used as a luma matte as is. This means that you will see color in
@@ -132,7 +162,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) :
* true.
*/
fun setInverseNoiseLuminosity(inverse: Boolean) {
- setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f)
+ setFloatUniform("in_inverseLuma", if (inverse) -1f else 1f)
}
/** Current noise movements in x, y, and z axes. */
diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
index c3e84787d4fb..43d6504fce84 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseView.kt
@@ -215,10 +215,12 @@ class TurbulenceNoiseView(context: Context?, attrs: AttributeSet?) : View(contex
noiseConfig = config
with(turbulenceNoiseShader) {
setGridCount(config.gridCount)
- setColor(ColorUtils.setAlphaComponent(config.color, config.opacity))
+ setColor(config.color)
setBackgroundColor(config.backgroundColor)
setSize(config.width, config.height)
setPixelDensity(config.pixelDensity)
+ setInverseNoiseLuminosity(inverse = false)
+ setLumaMatteFactors(config.lumaMatteBlendFactor, config.lumaMatteOverallBrightness)
}
paint.blendMode = config.blendMode
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index 465b73e6de19..648ef03895cd 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -74,7 +74,8 @@ class AnimatableClockView @JvmOverloads constructor(
private var onTextAnimatorInitialized: Runnable? = null
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
- { layout, invalidateCb -> TextAnimator(layout, invalidateCb) }
+ { layout, invalidateCb ->
+ TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
@VisibleForTesting var isAnimationEnabled: Boolean = true
@VisibleForTesting var timeOverrideInMillis: Long? = null
@@ -567,6 +568,7 @@ class AnimatableClockView @JvmOverloads constructor(
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
private const val COLOR_ANIM_DURATION: Long = 400
+ private const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
index 2baeaf67df93..9cc87fde122f 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ActivityStarter.java
@@ -133,6 +133,9 @@ public interface ActivityStarter {
boolean willAnimateOnKeyguard,
@Nullable String customMessage);
+ /** Whether we should animate an activity launch. */
+ boolean shouldAnimateLaunch(boolean isActivityIntent);
+
interface Callback {
void onActivityStarted(int resultCode);
}
diff --git a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
index a8f0cc3a1d92..4a9d41fae1d5 100644
--- a/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
+++ b/packages/SystemUI/res/drawable/stat_sys_ringer_silent.xml
@@ -14,20 +14,6 @@ Copyright (C) 2015 The Android Open Source Project
limitations under the License.
-->
<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="3dp"
- android:insetRight="3dp">
- <vector android:width="18dp"
- android:height="18dp"
- android:viewportWidth="24.0"
- android:viewportHeight="24.0">
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4C10,21.1 10.9,22 12,22z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M16,16L2.81,2.81L1.39,4.22l4.85,4.85C6.09,9.68 6,10.33 6,11v6H4v2h12.17l3.61,3.61l1.41,-1.41L16,16zM8,17c0,0 0.01,-6.11 0.01,-6.16L14.17,17H8z"/>
- <path
- android:fillColor="#FFFFFFFF"
- android:pathData="M12,6.5c2.49,0 4,2.02 4,4.5v2.17l2,2V11c0,-3.07 -1.63,-5.64 -4.5,-6.32V4c0,-0.83 -0.67,-1.5 -1.5,-1.5S10.5,3.17 10.5,4v0.68C9.72,4.86 9.05,5.2 8.46,5.63L9.93,7.1C10.51,6.73 11.2,6.5 12,6.5z"/>
- </vector>
-</inset>
+ android:insetLeft="3dp"
+ android:insetRight="3dp"
+ android:drawable="@drawable/ic_speaker_mute" />
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
index 81691898dfe5..efc661a6e974 100644
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ b/packages/SystemUI/res/layout/auth_biometric_contents.xml
@@ -55,13 +55,7 @@
android:layout_height="wrap_content"
android:layout_gravity="center">
- <com.airbnb.lottie.LottieAnimationView
- android:id="@+id/biometric_icon"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center"
- android:contentDescription="@null"
- android:scaleType="fitXY" />
+ <include layout="@layout/auth_biometric_icon"/>
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/biometric_icon_overlay"
diff --git a/packages/SystemUI/res/layout/auth_biometric_icon.xml b/packages/SystemUI/res/layout/auth_biometric_icon.xml
new file mode 100644
index 000000000000..b2df63dab700
--- /dev/null
+++ b/packages/SystemUI/res/layout/auth_biometric_icon.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2023 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+
+<com.airbnb.lottie.LottieAnimationView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/biometric_icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:contentDescription="@null"
+ android:scaleType="fitXY"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
new file mode 100644
index 000000000000..b1d6a270bc67
--- /dev/null
+++ b/packages/SystemUI/res/raw/fingerprint_dialogue_unlocked_to_checkmark_success_lottie.json
@@ -0,0 +1 @@
+{"v":"5.7.13","fr":60,"ip":0,"op":55,"w":80,"h":80,"nm":"unlocked_to_checkmark_success","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[47.143,32,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[2.761,0],[0,-2.7],[0,0]],"o":[[0,0],[0,-2.7],[-2.761,0],[0,0],[0,0]],"v":[[5,5],[5,-0.111],[0,-5],[-5,-0.111],[-5.01,4]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[38,45,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[18,16],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":2,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":4},"w":{"a":0,"k":2.5,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":".colorAccentPrimary","cl":"colorAccentPrimary","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.999,44.999,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.42,0],[0,1.42],[1.42,0],[0,-1.42]],"o":[[1.42,0],[0,-1.42],[-1.42,0],[0,1.42]],"v":[[0,2.571],[2.571,0],[0,-2.571],[-2.571,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.827450990677,0.890196084976,0.992156863213,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[100]},{"t":10,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":85,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40.5,40.75,0],"ix":2,"l":2},"a":{"a":0,"k":[12.5,-6.25,0],"ix":1,"l":2},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":10,"s":[60,60,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":19,"s":[112,112,100]},{"t":30,"s":[100,100,100]}],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-10.556,-9.889],[7.444,6.555],[34.597,-20.486]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.2],"y":[1]},"o":{"x":[0.7],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":10,"op":910,"st":10,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":".green200","cl":"green200","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[93.5,93.5,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.658823529412,0.854901960784,0.709803921569,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":10,"s":[0]},{"t":15,"s":[100]}],"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[0]},{"t":20,"s":[4]}],"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false}],"ip":10,"op":77,"st":10,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":".grey700","cl":"grey700","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[40,40,0],"ix":2,"l":2},"a":{"a":0,"k":[40.25,40.25,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[22.12,0],[0,-22.08],[-22.08,0],[0,22.08]],"o":[[-22.08,0],[0,22.08],[22.12,0],[0,-22.08]],"v":[[-0.04,-40],[-40,0],[-0.04,40],[40,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.278431385756,0.278431385756,0.278431385756,1],"ix":4},"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100]},{"t":20,"s":[0]}],"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[40.25,40.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c619d462a5f6..9108fa615e88 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -353,7 +353,7 @@
<!-- Message shown when a biometric is authenticated, waiting for the user to confirm authentication [CHAR LIMIT=40]-->
<string name="biometric_dialog_tap_confirm">Tap Confirm to complete</string>
<!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
- <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face.</string>
+ <string name="biometric_dialog_tap_confirm_with_face">Unlocked by face</string>
<!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
<string name="biometric_dialog_tap_confirm_with_face_alt_1">Unlocked by face. Press to continue.</string>
<!-- Message shown when a biometric has authenticated with a user's face and is waiting for the user to confirm authentication [CHAR LIMIT=60]-->
@@ -613,8 +613,8 @@
<string name="quick_settings_bluetooth_secondary_label_headset">Headset</string>
<!-- QuickSettings: Bluetooth secondary label for an input/IO device being connected [CHAR LIMIT=20]-->
<string name="quick_settings_bluetooth_secondary_label_input">Input</string>
- <!-- QuickSettings: Bluetooth secondary label for a Hearing Aids device being connected [CHAR LIMIT=20]-->
- <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing Aids</string>
+ <!-- QuickSettings: Bluetooth secondary label for a Hearing aids device being connected [CHAR LIMIT=20]-->
+ <string name="quick_settings_bluetooth_secondary_label_hearing_aids">Hearing aids</string>
<!-- QuickSettings: Bluetooth secondary label shown when bluetooth is being enabled [CHAR LIMIT=NONE] -->
<string name="quick_settings_bluetooth_secondary_label_transient">Turning on&#8230;</string>
<!-- QuickSettings: Brightness [CHAR LIMIT=NONE] -->
@@ -2726,8 +2726,8 @@
<string name="media_output_broadcast_last_update_error">Can\u2019t save.</string>
<!-- The hint message when Broadcast code is less than 4 characters [CHAR LIMIT=60] -->
<string name="media_output_broadcast_code_hint_no_less_than_min">Use at least 4 characters</string>
- <!-- The hint message when Broadcast code is more than 16 characters [CHAR LIMIT=60] -->
- <string name="media_output_broadcast_code_hint_no_more_than_max">Use fewer than 16 characters</string>
+ <!-- The hint message when Broadcast edit is more than 16/254 characters [CHAR LIMIT=60] -->
+ <string name="media_output_broadcast_edit_hint_no_more_than_max">Use fewer than <xliff:g id="length" example="16">%1$d</xliff:g> characters</string>
<!-- Label for clip data when copying the build number off QS [CHAR LIMIT=NONE]-->
<string name="build_number_clip_data_label">Build number</string>
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
index f96d1e3d7fef..070a45170d04 100644
--- a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -62,6 +62,15 @@ open class ViewScreenshotTestRule(
private val isRobolectric = if (Build.FINGERPRINT.contains("robolectric")) true else false
override fun apply(base: Statement, description: Description): Statement {
+ if (isRobolectric) {
+ // In robolectric mode, we enable NATIVE graphics and unpack font and icu files.
+ // We need to use reflection, as this library is only needed and therefore
+ // only available in deviceless mode.
+ val nativeLoaderClassName = "org.robolectric.nativeruntime.DefaultNativeRuntimeLoader"
+ val defaultNativeRuntimeLoader = Class.forName(nativeLoaderClassName)
+ System.setProperty("robolectric.graphicsMode", "NATIVE")
+ defaultNativeRuntimeLoader.getMethod("injectAndLoad").invoke(null)
+ }
val ruleToApply = if (isRobolectric) roboRule else delegateRule
return ruleToApply.apply(base, description)
}
@@ -69,6 +78,7 @@ open class ViewScreenshotTestRule(
protected fun takeScreenshot(
mode: Mode = Mode.WrapContent,
viewProvider: (ComponentActivity) -> View,
+ beforeScreenshot: (ComponentActivity) -> Unit = {}
): Bitmap {
activityRule.scenario.onActivity { activity ->
// Make sure that the activity draws full screen and fits the whole display instead of
@@ -94,6 +104,7 @@ open class ViewScreenshotTestRule(
val content = activity.requireViewById<ViewGroup>(android.R.id.content)
assertEquals(1, content.childCount)
contentView = content.getChildAt(0)
+ beforeScreenshot(activity)
}
return if (isRobolectric) {
@@ -111,9 +122,10 @@ open class ViewScreenshotTestRule(
fun screenshotTest(
goldenIdentifier: String,
mode: Mode = Mode.WrapContent,
- viewProvider: (ComponentActivity) -> View,
+ beforeScreenshot: (ComponentActivity) -> Unit = {},
+ viewProvider: (ComponentActivity) -> View
) {
- val bitmap = takeScreenshot(mode, viewProvider)
+ val bitmap = takeScreenshot(mode, viewProvider, beforeScreenshot)
screenshotRule.assertBitmapAgainstGolden(
bitmap,
goldenIdentifier,
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index 0ca2f7a36311..0fbeb1a054a7 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -202,8 +202,6 @@ constructor(
fun calculateScreenLocation(sampledView: View): RectF? {
- if (!sampledView.isLaidOut) return null
-
val screenLocation = tmpScreenLocation
/**
* The method getLocationOnScreen is used to obtain the view coordinates relative to its
@@ -219,6 +217,10 @@ constructor(
samplingBounds.right = left + sampledView.width
samplingBounds.bottom = top + sampledView.height
+ // ensure never go out of bounds
+ if (samplingBounds.right > displaySize.x) samplingBounds.right = displaySize.x
+ if (samplingBounds.bottom > displaySize.y) samplingBounds.bottom = displaySize.y
+
return RectF(samplingBounds)
}
diff --git a/packages/SystemUI/src/com/android/systemui/DejankUtils.java b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
index 3fce55f6e515..146d12c3e621 100644
--- a/packages/SystemUI/src/com/android/systemui/DejankUtils.java
+++ b/packages/SystemUI/src/com/android/systemui/DejankUtils.java
@@ -29,13 +29,17 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.StrictMode;
import android.os.SystemProperties;
+import android.os.Trace;
import android.view.Choreographer;
+import android.view.View;
+import android.view.ViewRootImpl;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.util.Assert;
import java.util.ArrayList;
import java.util.HashSet;
+import java.util.Random;
import java.util.Stack;
import java.util.function.Supplier;
@@ -43,12 +47,14 @@ import java.util.function.Supplier;
* Utility class for methods used to dejank the UI.
*/
public class DejankUtils {
+ private static final String TRACK_NAME = "DejankUtils";
public static final boolean STRICT_MODE_ENABLED = Build.IS_ENG
|| SystemProperties.getBoolean("persist.sysui.strictmode", false);
private static final Choreographer sChoreographer = Choreographer.getInstance();
private static final Handler sHandler = new Handler();
private static final ArrayList<Runnable> sPendingRunnables = new ArrayList<>();
+ private static final Random sRandom = new Random();
private static Stack<String> sBlockingIpcs = new Stack<>();
private static boolean sTemporarilyIgnoreStrictMode;
private static final HashSet<String> sWhitelistedFrameworkClasses = new HashSet<>();
@@ -254,4 +260,30 @@ public class DejankUtils {
public static void setImmediate(boolean immediate) {
sImmediate = immediate;
}
+
+ /**
+ * Calls notifyRendererOfExpensiveFrame on the ViewRootImpl after performing null checks.
+ */
+ public static void notifyRendererOfExpensiveFrame(View view, String reason) {
+ if (view == null) return;
+ notifyRendererOfExpensiveFrame(view.getViewRootImpl(), reason);
+ }
+
+ /**
+ * Calls notifyRendererOfExpensiveFrame on the ViewRootImpl after performing null checks.
+ */
+ public static void notifyRendererOfExpensiveFrame(ViewRootImpl viewRoot, String reason) {
+ if (viewRoot == null) return;
+ if (Trace.isTagEnabled(Trace.TRACE_TAG_APP)) {
+ int cookie = sRandom.nextInt();
+ Trace.asyncTraceForTrackBegin(
+ Trace.TRACE_TAG_APP,
+ TRACK_NAME,
+ "notifyRendererOfExpensiveFrame (" + reason + ")",
+ cookie);
+ DejankUtils.postAfterTraversal(
+ () -> Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, TRACK_NAME, cookie));
+ }
+ viewRoot.notifyRendererOfExpensiveFrame();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index c30a2146436c..8af92ce4bcb1 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -548,6 +548,10 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
if (!cancelled) {
updateSwipeProgressFromOffset(animView, canBeDismissed);
resetSwipeOfView(animView);
+ // Clear the snapped view after success, assuming it's not being swiped now
+ if (animView == mTouchedView && !mIsSwiping) {
+ mTouchedView = null;
+ }
}
onChildSnappedBack(animView, targetLeft);
});
@@ -813,13 +817,39 @@ public class SwipeHelper implements Gefingerpoken, Dumpable {
}
}
- public void resetSwipeState() {
- View swipedView = getSwipedView();
+ private void resetSwipeState() {
+ resetSwipeStates(/* resetAll= */ false);
+ }
+
+ public void resetTouchState() {
+ resetSwipeStates(/* resetAll= */ true);
+ }
+
+ /** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */
+ private void resetSwipeStates(boolean resetAll) {
+ final View touchedView = mTouchedView;
+ final boolean wasSnapping = mSnappingChild;
+ final boolean wasSwiping = mIsSwiping;
mTouchedView = null;
mIsSwiping = false;
- if (swipedView != null) {
- snapChildIfNeeded(swipedView, false, 0);
- onChildSnappedBack(swipedView, 0);
+ // If we were swiping, then we resetting swipe requires resetting everything.
+ resetAll |= wasSwiping;
+ if (resetAll) {
+ mSnappingChild = false;
+ }
+ if (touchedView == null) return; // No view to reset visually
+ // When snap needs to be reset, first thing is to cancel any translation animation
+ final boolean snapNeedsReset = resetAll && wasSnapping;
+ if (snapNeedsReset) {
+ cancelTranslateAnimation(touchedView);
+ }
+ // actually reset the view to default state
+ if (resetAll) {
+ snapChildIfNeeded(touchedView, false, 0);
+ }
+ // report if a swipe or snap was reset.
+ if (wasSwiping || snapNeedsReset) {
+ onChildSnappedBack(touchedView, 0);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIService.java b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
index 50e03992df49..ba3c8602c6ff 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIService.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIService.java
@@ -30,8 +30,10 @@ import com.android.internal.os.BinderInternal;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpHandler;
+import com.android.systemui.dump.LogBufferEulogizer;
import com.android.systemui.dump.LogBufferFreezer;
import com.android.systemui.dump.SystemUIAuxiliaryDumpService;
+import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager;
import com.android.systemui.statusbar.policy.BatteryStateNotifier;
import java.io.FileDescriptor;
@@ -44,22 +46,29 @@ public class SystemUIService extends Service {
private final Handler mMainHandler;
private final DumpHandler mDumpHandler;
private final BroadcastDispatcher mBroadcastDispatcher;
+ private final LogBufferEulogizer mLogBufferEulogizer;
private final LogBufferFreezer mLogBufferFreezer;
private final BatteryStateNotifier mBatteryStateNotifier;
+ private final UncaughtExceptionPreHandlerManager mUncaughtExceptionPreHandlerManager;
+
@Inject
public SystemUIService(
@Main Handler mainHandler,
DumpHandler dumpHandler,
BroadcastDispatcher broadcastDispatcher,
+ LogBufferEulogizer logBufferEulogizer,
LogBufferFreezer logBufferFreezer,
- BatteryStateNotifier batteryStateNotifier) {
+ BatteryStateNotifier batteryStateNotifier,
+ UncaughtExceptionPreHandlerManager uncaughtExceptionPreHandlerManager) {
super();
mMainHandler = mainHandler;
mDumpHandler = dumpHandler;
mBroadcastDispatcher = broadcastDispatcher;
+ mLogBufferEulogizer = logBufferEulogizer;
mLogBufferFreezer = logBufferFreezer;
mBatteryStateNotifier = batteryStateNotifier;
+ mUncaughtExceptionPreHandlerManager = uncaughtExceptionPreHandlerManager;
}
@Override
@@ -71,7 +80,13 @@ public class SystemUIService extends Service {
// Finish initializing dump logic
mLogBufferFreezer.attach(mBroadcastDispatcher);
- mDumpHandler.init();
+
+ // Attempt to dump all LogBuffers for any uncaught exception
+ mUncaughtExceptionPreHandlerManager.registerHandler((thread, throwable) -> {
+ if (throwable instanceof Exception) {
+ mLogBufferEulogizer.record(((Exception) throwable));
+ }
+ });
// If configured, set up a battery notification
if (getResources().getBoolean(R.bool.config_showNotificationForUnknownBatteryState)) {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 4158390ec953..eebc1f06c516 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -485,13 +485,11 @@ public class SystemActions implements CoreStartable {
}
private void handleNotifications() {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- CentralSurfaces::animateExpandNotificationsPanel);
+ mShadeController.animateExpandShade();
}
private void handleQuickSettings() {
- mCentralSurfacesOptionalLazy.get().ifPresent(
- centralSurfaces -> centralSurfaces.animateExpandSettingsPanel(null));
+ mShadeController.animateExpandQs();
}
private void handlePowerDialog() {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 682888fc39b5..95610aec3562 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -21,6 +21,7 @@ import android.content.Context
import com.airbnb.lottie.LottieAnimationView
import com.android.systemui.R
import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
@@ -47,13 +48,16 @@ open class AuthBiometricFingerprintAndFaceIconController(
@BiometricState oldState: Int,
@BiometricState newState: Int
): Int? = when (newState) {
+ STATE_AUTHENTICATED -> {
+ if (oldState == STATE_PENDING_CONFIRMATION) {
+ R.raw.fingerprint_dialogue_unlocked_to_checkmark_success_lottie
+ } else {
+ super.getAnimationForTransition(oldState, newState)
+ }
+ }
STATE_PENDING_CONFIRMATION -> {
if (oldState == STATE_ERROR || oldState == STATE_HELP) {
R.raw.fingerprint_dialogue_error_to_unlock_lottie
- } else if (oldState == STATE_PENDING_CONFIRMATION) {
- // TODO(jbolinger): missing asset for this transition
- // (unlocked icon to success checkmark)
- R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
} else {
R.raw.fingerprint_dialogue_fingerprint_to_unlock_lottie
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
index 69ce78ce30a8..cd8f04d18500 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
@@ -961,6 +961,10 @@ public abstract class AuthBiometricView extends LinearLayout implements AuthBiom
return Utils.isDeviceCredentialAllowed(mPromptInfo);
}
+ public LottieAnimationView getIconView() {
+ return mIconView;
+ }
+
@AuthDialog.DialogSize int getSize() {
return mSize;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 43a3b9958ee5..7f706859abb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -614,7 +614,11 @@ public class AuthContainerView extends LinearLayout
return ((AuthBiometricFingerprintView) view).isUdfps();
}
if (view instanceof BiometricPromptLayout) {
- return ((BiometricPromptLayout) view).isUdfps();
+ // this will force the prompt to align itself on the edge of the screen
+ // instead of centering (temporary workaround to prevent small implicit view
+ // from breaking due to the way gravity / margins are set in the legacy
+ // AuthPanelController
+ return true;
}
return false;
@@ -638,12 +642,12 @@ public class AuthContainerView extends LinearLayout
case Surface.ROTATION_90:
mPanelController.setPosition(AuthPanelController.POSITION_RIGHT);
- setScrollViewGravity(Gravity.BOTTOM | Gravity.RIGHT);
+ setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.RIGHT);
break;
case Surface.ROTATION_270:
mPanelController.setPosition(AuthPanelController.POSITION_LEFT);
- setScrollViewGravity(Gravity.BOTTOM | Gravity.LEFT);
+ setScrollViewGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
break;
case Surface.ROTATION_180:
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 57f1928fe545..b2ffea3d050c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -201,7 +201,9 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
final TaskStackListener mTaskStackListener = new TaskStackListener() {
@Override
public void onTaskStackChanged() {
- mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+ if (!isOwnerInForeground()) {
+ mHandler.post(AuthController.this::cancelIfOwnerIsNotInForeground);
+ }
}
};
@@ -239,33 +241,37 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
}
}
+ private boolean isOwnerInForeground() {
+ final String clientPackage = mCurrentDialog.getOpPackageName();
+ final List<ActivityManager.RunningTaskInfo> runningTasks =
+ mActivityTaskManager.getTasks(1);
+ if (!runningTasks.isEmpty()) {
+ final String topPackage = runningTasks.get(0).topActivity.getPackageName();
+ if (!topPackage.contentEquals(clientPackage)
+ && !Utils.isSystem(mContext, clientPackage)) {
+ Log.w(TAG, "Evicting client due to: " + topPackage);
+ return false;
+ }
+ }
+ return true;
+ }
+
private void cancelIfOwnerIsNotInForeground() {
mExecution.assertIsMainThread();
if (mCurrentDialog != null) {
try {
- final String clientPackage = mCurrentDialog.getOpPackageName();
- Log.w(TAG, "Task stack changed, current client: " + clientPackage);
- final List<ActivityManager.RunningTaskInfo> runningTasks =
- mActivityTaskManager.getTasks(1);
- if (!runningTasks.isEmpty()) {
- final String topPackage = runningTasks.get(0).topActivity.getPackageName();
- if (!topPackage.contentEquals(clientPackage)
- && !Utils.isSystem(mContext, clientPackage)) {
- Log.e(TAG, "Evicting client due to: " + topPackage);
- mCurrentDialog.dismissWithoutCallback(true /* animate */);
- mCurrentDialog = null;
-
- for (Callback cb : mCallbacks) {
- cb.onBiometricPromptDismissed();
- }
+ mCurrentDialog.dismissWithoutCallback(true /* animate */);
+ mCurrentDialog = null;
- if (mReceiver != null) {
- mReceiver.onDialogDismissed(
- BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
- null /* credentialAttestation */);
- mReceiver = null;
- }
- }
+ for (Callback cb : mCallbacks) {
+ cb.onBiometricPromptDismissed();
+ }
+
+ if (mReceiver != null) {
+ mReceiver.onDialogDismissed(
+ BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
+ null /* credentialAttestation */);
+ mReceiver = null;
}
} catch (RemoteException e) {
Log.e(TAG, "Remote exception", e);
@@ -1253,10 +1259,11 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks,
cb.onBiometricPromptShown();
}
mCurrentDialog = newDialog;
- mCurrentDialog.show(mWindowManager, savedState);
- if (!promptInfo.isAllowBackgroundAuthentication()) {
- mHandler.post(this::cancelIfOwnerIsNotInForeground);
+ if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
+ cancelIfOwnerIsNotInForeground();
+ } else {
+ mCurrentDialog.show(mWindowManager, savedState);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
index acdde3404ab5..167067e7d7e9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthPanelController.java
@@ -114,7 +114,13 @@ public class AuthPanelController extends ViewOutlineProvider {
}
private int getTopBound(@Position int position) {
- return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
+ switch (position) {
+ case POSITION_LEFT:
+ case POSITION_RIGHT:
+ return Math.max((mContainerHeight - mContentHeight) / 2, mMargin);
+ default:
+ return Math.max(mContainerHeight - mContentHeight - mMargin, mMargin);
+ }
}
public void setContainerDimensions(int containerWidth, int containerHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index baaa96efb5f0..d48b9c339d15 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -35,6 +35,7 @@ import android.os.Handler
import android.util.Log
import android.util.RotationUtils
import android.view.Display
+import android.view.DisplayInfo
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
@@ -58,6 +59,7 @@ import com.android.systemui.dagger.qualifiers.Application
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.util.boundsOnScreen
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.traceSection
import java.io.PrintWriter
@@ -129,6 +131,8 @@ constructor(
}
@VisibleForTesting var overlayOffsets: SensorLocationInternal = SensorLocationInternal.DEFAULT
+ private val displayInfo = DisplayInfo()
+
private val overlayViewParams =
WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
@@ -214,6 +218,23 @@ constructor(
for (requestSource in requests) {
pw.println(" $requestSource.name")
}
+
+ pw.println("overlayView:")
+ pw.println(" width=${overlayView?.width}")
+ pw.println(" height=${overlayView?.height}")
+ pw.println(" boundsOnScreen=${overlayView?.boundsOnScreen}")
+
+ pw.println("displayStateInteractor:")
+ pw.println(" isInRearDisplayMode=${displayStateInteractor?.isInRearDisplayMode?.value}")
+
+ pw.println("sensorProps:")
+ pw.println(" displayId=${displayInfo.uniqueId}")
+ pw.println(" sensorType=${sensorProps?.sensorType}")
+ pw.println(" location=${sensorProps?.getLocation(displayInfo.uniqueId)}")
+
+ pw.println("overlayOffsets=$overlayOffsets")
+ pw.println("isReverseDefaultRotation=$isReverseDefaultRotation")
+ pw.println("currentRotation=${displayInfo.rotation}")
}
private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
@@ -226,6 +247,8 @@ constructor(
val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
overlayView = view
val display = context.display!!
+ // b/284098873 `context.display.rotation` may not up-to-date, we use displayInfo.rotation
+ display.getDisplayInfo(displayInfo)
val offsets =
sensorProps.getLocation(display.uniqueId).let { location ->
if (location == null) {
@@ -239,12 +262,12 @@ constructor(
view.rotation =
display.asSideFpsAnimationRotation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
lottie.setAnimation(
display.asSideFpsAnimation(
offsets.isYAligned(),
- getRotationFromDefault(display.rotation)
+ getRotationFromDefault(displayInfo.rotation)
)
)
lottie.addLottieOnCompositionLoadedListener {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index c935aa290e21..26b6f2a7a3cc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -78,6 +78,7 @@ constructor(
sendFoldStateUpdate(isFolded)
}
}
+
sendFoldStateUpdate(false)
screenSizeFoldProvider.registerCallback(callback, mainExecutor)
awaitClose { screenSizeFoldProvider.unregisterCallback(callback) }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt
new file mode 100644
index 000000000000..bd0907e588ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintIconViewBinder.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.view.DisplayInfo
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.android.systemui.biometrics.AuthBiometricFingerprintView
+import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import kotlinx.coroutines.launch
+
+/** Sub-binder for [AuthBiometricFingerprintView.mIconView]. */
+object AuthBiometricFingerprintIconViewBinder {
+
+ /**
+ * Binds a [AuthBiometricFingerprintView.mIconView] to a [AuthBiometricFingerprintViewModel].
+ */
+ @JvmStatic
+ fun bind(view: LottieAnimationView, viewModel: AuthBiometricFingerprintViewModel) {
+ view.repeatWhenAttached {
+ repeatOnLifecycle(Lifecycle.State.STARTED) {
+ val displayInfo = DisplayInfo()
+ view.context.display?.getDisplayInfo(displayInfo)
+ viewModel.setRotation(displayInfo.rotation)
+ viewModel.onConfigurationChanged(view.context.resources.configuration)
+ launch { viewModel.iconAsset.collect { iconAsset -> view.setAnimation(iconAsset) } }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
index ae0cf3771ed3..9c1bcec2f396 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/AuthBiometricFingerprintViewBinder.kt
@@ -17,31 +17,18 @@
package com.android.systemui.biometrics.ui.binder
-import android.view.Surface
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.repeatOnLifecycle
import com.android.systemui.biometrics.AuthBiometricFingerprintView
import com.android.systemui.biometrics.ui.viewmodel.AuthBiometricFingerprintViewModel
-import com.android.systemui.lifecycle.repeatWhenAttached
-import kotlinx.coroutines.launch
object AuthBiometricFingerprintViewBinder {
- /** Binds a [AuthBiometricFingerprintView] to a [AuthBiometricFingerprintViewModel]. */
+ /**
+ * Binds a [AuthBiometricFingerprintView.mIconView] to a [AuthBiometricFingerprintViewModel].
+ */
@JvmStatic
fun bind(view: AuthBiometricFingerprintView, viewModel: AuthBiometricFingerprintViewModel) {
- view.repeatWhenAttached {
- repeatOnLifecycle(Lifecycle.State.STARTED) {
- viewModel.onConfigurationChanged(view.context.resources.configuration)
- viewModel.setRotation(view.context.display?.orientation ?: Surface.ROTATION_0)
- launch {
- viewModel.iconAsset.collect { iconAsset ->
- if (view.isSfps) {
- view.updateIconViewAnimation(iconAsset)
- }
- }
- }
- }
+ if (view.isSfps) {
+ AuthBiometricFingerprintIconViewBinder.bind(view.getIconView(), viewModel)
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index e4c4e9aedb56..1dffa80a084f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -19,8 +19,11 @@ package com.android.systemui.biometrics.ui.binder
import android.animation.Animator
import android.animation.AnimatorSet
import android.animation.ValueAnimator
+import android.view.Surface
import android.view.View
import android.view.ViewGroup
+import android.view.WindowInsets
+import android.view.WindowManager
import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import androidx.core.animation.addListener
@@ -52,7 +55,9 @@ object BiometricViewSizeBinder {
panelViewController: AuthPanelController,
jankListener: BiometricJankListener,
) {
- val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
+ val windowManager = requireNotNull(view.context.getSystemService(WindowManager::class.java))
+ val accessibilityManager =
+ requireNotNull(view.context.getSystemService(AccessibilityManager::class.java))
fun notifyAccessibilityChanged() {
Utils.notifyAccessibilityContentChanged(accessibilityManager, view)
}
@@ -102,15 +107,26 @@ object BiometricViewSizeBinder {
when {
size.isSmall -> {
iconHolderView.alpha = 1f
+ val bottomInset =
+ windowManager.maximumWindowMetrics.windowInsets
+ .getInsets(WindowInsets.Type.navigationBars())
+ .bottom
iconHolderView.y =
- view.height - iconHolderView.height - iconPadding
+ if (view.isLandscape()) {
+ (view.height - iconHolderView.height - bottomInset) / 2f
+ } else {
+ view.height -
+ iconHolderView.height -
+ iconPadding -
+ bottomInset
+ }
val newHeight =
- iconHolderView.height + 2 * iconPadding.toInt() -
+ iconHolderView.height + (2 * iconPadding.toInt()) -
iconHolderView.paddingTop -
iconHolderView.paddingBottom
panelViewController.updateForContentDimensions(
width,
- newHeight,
+ newHeight + bottomInset,
0, /* animateDurationMs */
)
}
@@ -181,6 +197,11 @@ object BiometricViewSizeBinder {
}
}
+private fun View.isLandscape(): Boolean {
+ val r = context.display.rotation
+ return r == Surface.ROTATION_90 || r == Surface.ROTATION_270
+}
+
private fun TextView.showTextOrHide(forceHide: Boolean = false) {
visibility = if (forceHide || text.isBlank()) View.GONE else View.VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 1da7900e985b..df2a749d986d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -283,6 +283,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ
mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE);
+ mWindow.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS);
mWindow.requestFeature(Window.FEATURE_NO_TITLE);
// Hide all insets when the dream is showing
mWindow.getDecorView().getWindowInsetsController().hide(WindowInsets.Type.systemBars());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index b141db15ea0f..c2421dcbc6ca 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -19,7 +19,6 @@ package com.android.systemui.dreams;
import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED;
import android.service.dreams.DreamService;
-import android.util.Log;
import androidx.annotation.NonNull;
@@ -52,7 +51,6 @@ import javax.inject.Named;
public class DreamOverlayStateController implements
CallbackController<DreamOverlayStateController.Callback> {
private static final String TAG = "DreamOverlayStateCtlr";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
@@ -110,13 +108,17 @@ public class DreamOverlayStateController implements
private final int mSupportedTypes;
+ private final DreamLogger mLogger;
+
@VisibleForTesting
@Inject
public DreamOverlayStateController(@Main Executor executor,
@Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled,
- FeatureFlags featureFlags) {
+ FeatureFlags featureFlags,
+ DreamLogger dreamLogger) {
mExecutor = executor;
mOverlayEnabled = overlayEnabled;
+ mLogger = dreamLogger;
mFeatureFlags = featureFlags;
if (mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)) {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE
@@ -124,9 +126,7 @@ public class DreamOverlayStateController implements
} else {
mSupportedTypes = Complication.COMPLICATION_TYPE_NONE;
}
- if (DEBUG) {
- Log.d(TAG, "Dream overlay enabled:" + mOverlayEnabled);
- }
+ mLogger.d(TAG, "Dream overlay enabled: " + mOverlayEnabled);
}
/**
@@ -134,18 +134,14 @@ public class DreamOverlayStateController implements
*/
public void addComplication(Complication complication) {
if (!mOverlayEnabled) {
- if (DEBUG) {
- Log.d(TAG,
- "Ignoring adding complication due to overlay disabled:" + complication);
- }
+ mLogger.d(TAG,
+ "Ignoring adding complication due to overlay disabled: " + complication);
return;
}
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
- if (DEBUG) {
- Log.d(TAG, "addComplication: added " + complication);
- }
+ mLogger.d(TAG, "Added dream complication: " + complication);
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -156,18 +152,14 @@ public class DreamOverlayStateController implements
*/
public void removeComplication(Complication complication) {
if (!mOverlayEnabled) {
- if (DEBUG) {
- Log.d(TAG,
- "Ignoring removing complication due to overlay disabled:" + complication);
- }
+ mLogger.d(TAG,
+ "Ignoring removing complication due to overlay disabled: " + complication);
return;
}
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
- if (DEBUG) {
- Log.d(TAG, "removeComplication: removed " + complication);
- }
+ mLogger.d(TAG, "Removed dream complication: " + complication);
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -313,6 +305,7 @@ public class DreamOverlayStateController implements
* @param active {@code true} if overlay is active, {@code false} otherwise.
*/
public void setOverlayActive(boolean active) {
+ mLogger.d(TAG, "Dream overlay active: " + active);
modifyState(active ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_ACTIVE);
}
@@ -321,6 +314,8 @@ public class DreamOverlayStateController implements
* @param active {@code true} if low light mode is active, {@code false} otherwise.
*/
public void setLowLightActive(boolean active) {
+ mLogger.d(TAG, "Low light mode active: " + active);
+
if (isLowLightActive() && !active) {
// Notify that we're exiting low light only on the transition from active to not active.
mCallbacks.forEach(Callback::onExitLowLight);
@@ -351,6 +346,7 @@ public class DreamOverlayStateController implements
* @param hasAttention {@code true} if has the user's attention, {@code false} otherwise.
*/
public void setHasAssistantAttention(boolean hasAttention) {
+ mLogger.d(TAG, "Dream overlay has Assistant attention: " + hasAttention);
modifyState(hasAttention ? OP_SET_STATE : OP_CLEAR_STATE, STATE_HAS_ASSISTANT_ATTENTION);
}
@@ -359,6 +355,7 @@ public class DreamOverlayStateController implements
* @param visible {@code true} if the status bar is visible, {@code false} otherwise.
*/
public void setDreamOverlayStatusBarVisible(boolean visible) {
+ mLogger.d(TAG, "Dream overlay status bar visible: " + visible);
modifyState(
visible ? OP_SET_STATE : OP_CLEAR_STATE, STATE_DREAM_OVERLAY_STATUS_BAR_VISIBLE);
}
@@ -376,6 +373,7 @@ public class DreamOverlayStateController implements
*/
public void setAvailableComplicationTypes(@Complication.ComplicationType int types) {
mExecutor.execute(() -> {
+ mLogger.d(TAG, "Available complication types: " + types);
mAvailableComplicationTypes = types;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
@@ -393,6 +391,7 @@ public class DreamOverlayStateController implements
*/
public void setShouldShowComplications(boolean shouldShowComplications) {
mExecutor.execute(() -> {
+ mLogger.d(TAG, "Should show complications: " + shouldShowComplications);
mShouldShowComplications = shouldShowComplications;
mCallbacks.forEach(Callback::onAvailableComplicationTypesChanged);
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 7c1bfeda30b2..1865c38632e5 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -141,6 +141,20 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
mExtraSystemStatusViewGroup = findViewById(R.id.dream_overlay_extra_items);
}
+ protected static String getLoggableStatusIconType(@StatusIconType int type) {
+ return switch (type) {
+ case STATUS_ICON_NOTIFICATIONS -> "notifications";
+ case STATUS_ICON_WIFI_UNAVAILABLE -> "wifi_unavailable";
+ case STATUS_ICON_ALARM_SET -> "alarm_set";
+ case STATUS_ICON_CAMERA_DISABLED -> "camera_disabled";
+ case STATUS_ICON_MIC_DISABLED -> "mic_disabled";
+ case STATUS_ICON_MIC_CAMERA_DISABLED -> "mic_camera_disabled";
+ case STATUS_ICON_PRIORITY_MODE_ON -> "priority_mode_on";
+ case STATUS_ICON_ASSISTANT_ATTENTION_ACTIVE -> "assistant_attention_active";
+ default -> type + "(unknown)";
+ };
+ }
+
void showIcon(@StatusIconType int iconType, boolean show, @Nullable String contentDescription) {
View icon = mStatusIcons.get(iconType);
if (icon == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index c954f98ad36e..3a284083e844 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -61,6 +61,8 @@ import javax.inject.Inject;
*/
@DreamOverlayComponent.DreamOverlayScope
public class DreamOverlayStatusBarViewController extends ViewController<DreamOverlayStatusBarView> {
+ private static final String TAG = "DreamStatusBarCtrl";
+
private final ConnectivityManager mConnectivityManager;
private final TouchInsetManager.TouchInsetSession mTouchInsetSession;
private final NextAlarmController mNextAlarmController;
@@ -78,6 +80,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
private final Executor mMainExecutor;
private final List<DreamOverlayStatusBarItemsProvider.StatusBarItem> mExtraStatusBarItems =
new ArrayList<>();
+ private final DreamLogger mLogger;
private boolean mIsAttached;
@@ -157,7 +160,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
StatusBarWindowStateController statusBarWindowStateController,
DreamOverlayStatusBarItemsProvider statusBarItemsProvider,
DreamOverlayStateController dreamOverlayStateController,
- UserTracker userTracker) {
+ UserTracker userTracker,
+ DreamLogger dreamLogger) {
super(view);
mResources = resources;
mMainExecutor = mainExecutor;
@@ -173,6 +177,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
mZenModeController = zenModeController;
mDreamOverlayStateController = dreamOverlayStateController;
mUserTracker = userTracker;
+ mLogger = dreamLogger;
// Register to receive show/hide updates for the system status bar. Our custom status bar
// needs to hide when the system status bar is showing to ovoid overlapping status bars.
@@ -341,6 +346,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
@Nullable String contentDescription) {
mMainExecutor.execute(() -> {
if (mIsAttached) {
+ mLogger.d(TAG, (show ? "Showing" : "Hiding") + " dream status bar item: "
+ + DreamOverlayStatusBarView.getLoggableStatusIconType(iconType));
mView.showIcon(iconType, show, contentDescription);
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index 78e132ff6397..4b297a3d321d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -33,10 +33,10 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_DREAM
import com.android.systemui.smartspace.SmartspacePrecondition
import com.android.systemui.smartspace.SmartspaceTargetFilter
-import com.android.systemui.smartspace.dagger.SmartspaceModule
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
import java.util.Optional
@@ -58,7 +58,7 @@ class DreamSmartspaceController @Inject constructor(
@Named(DREAM_SMARTSPACE_TARGET_FILTER)
private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
@Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
- @Named(SmartspaceModule.WEATHER_SMARTSPACE_DATA_PLUGIN)
+ @Named(DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN)
optionalWeatherPlugin: Optional<BcSmartspaceDataPlugin>,
) {
companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
index 4b03fd334cb5..75284fc18149 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpHandler.kt
@@ -20,12 +20,16 @@ import android.content.Context
import android.os.SystemClock
import android.os.Trace
import com.android.systemui.CoreStartable
+import com.android.systemui.ProtoDumpable
import com.android.systemui.R
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_CRITICAL
import com.android.systemui.dump.DumpHandler.Companion.PRIORITY_ARG_NORMAL
+import com.android.systemui.dump.DumpsysEntry.DumpableEntry
+import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
+import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
import com.android.systemui.dump.nano.SystemUIProtoDump
import com.android.systemui.log.LogBuffer
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.android.systemui.log.table.TableLogBuffer
import com.google.protobuf.nano.MessageNano
import java.io.BufferedOutputStream
import java.io.FileDescriptor
@@ -39,11 +43,11 @@ import javax.inject.Provider
*
* Dump output is split into two sections, CRITICAL and NORMAL. In general, the CRITICAL section
* contains all dumpables that were registered to the [DumpManager], while the NORMAL sections
- * contains all [LogBuffer]s (due to their length).
+ * contains all [LogBuffer]s and [TableLogBuffer]s (due to their length).
*
- * The CRITICAL and NORMAL sections can be found within a bug report by searching for
- * "SERVICE com.android.systemui/.SystemUIService" and
- * "SERVICE com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
+ * The CRITICAL and NORMAL sections can be found within a bug report by searching for "SERVICE
+ * com.android.systemui/.SystemUIService" and "SERVICE
+ * com.android.systemui/.dump.SystemUIAuxiliaryDumpService", respectively.
*
* Finally, some or all of the dump can be triggered on-demand via adb (see below).
*
@@ -72,6 +76,7 @@ import javax.inject.Provider
* # To dump all dumpables or all buffers:
* $ <invocation> dumpables
* $ <invocation> buffers
+ * $ <invocation> tables
*
* # Finally, the following will simulate what we dump during the CRITICAL and NORMAL sections of a
* # bug report:
@@ -83,37 +88,26 @@ import javax.inject.Provider
* $ <invocation> --help
* ```
*/
-class DumpHandler @Inject constructor(
+class DumpHandler
+@Inject
+constructor(
private val context: Context,
private val dumpManager: DumpManager,
private val logBufferEulogizer: LogBufferEulogizer,
private val startables: MutableMap<Class<*>, Provider<CoreStartable>>,
- private val uncaughtExceptionPreHandlerManager: UncaughtExceptionPreHandlerManager
) {
- /**
- * Registers an uncaught exception handler
- */
- fun init() {
- uncaughtExceptionPreHandlerManager.registerHandler { _, e ->
- if (e is Exception) {
- logBufferEulogizer.record(e)
- }
- }
- }
-
- /**
- * Dump the diagnostics! Behavior can be controlled via [args].
- */
+ /** Dump the diagnostics! Behavior can be controlled via [args]. */
fun dump(fd: FileDescriptor, pw: PrintWriter, args: Array<String>) {
Trace.beginSection("DumpManager#dump()")
val start = SystemClock.uptimeMillis()
- val parsedArgs = try {
- parseArgs(args)
- } catch (e: ArgParseException) {
- pw.println(e.message)
- return
- }
+ val parsedArgs =
+ try {
+ parseArgs(args)
+ } catch (e: ArgParseException) {
+ pw.println(e.message)
+ return
+ }
when {
parsedArgs.dumpPriority == PRIORITY_ARG_CRITICAL -> dumpCritical(pw, parsedArgs)
@@ -134,6 +128,7 @@ class DumpHandler @Inject constructor(
"bugreport-normal" -> dumpNormal(pw, args)
"dumpables" -> dumpDumpables(pw, args)
"buffers" -> dumpBuffers(pw, args)
+ "tables" -> dumpTables(pw, args)
"config" -> dumpConfig(pw)
"help" -> dumpHelp(pw)
else -> {
@@ -147,44 +142,65 @@ class DumpHandler @Inject constructor(
}
private fun dumpCritical(pw: PrintWriter, args: ParsedArgs) {
- dumpManager.dumpCritical(pw, args.rawArgs)
+ val targets = dumpManager.getDumpables()
+ for (target in targets) {
+ if (target.priority == DumpPriority.CRITICAL) {
+ dumpDumpable(target, pw, args.rawArgs)
+ }
+ }
dumpConfig(pw)
}
private fun dumpNormal(pw: PrintWriter, args: ParsedArgs) {
- dumpManager.dumpNormal(pw, args.rawArgs, args.tailLength)
- logBufferEulogizer.readEulogyIfPresent(pw)
- }
+ val targets = dumpManager.getDumpables()
+ for (target in targets) {
+ if (target.priority == DumpPriority.NORMAL) {
+ dumpDumpable(target, pw, args.rawArgs)
+ }
+ }
- private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) {
- if (args.listOnly) {
- dumpManager.listDumpables(pw)
- } else {
- dumpManager.dumpDumpables(pw, args.rawArgs)
+ val buffers = dumpManager.getLogBuffers()
+ for (buffer in buffers) {
+ dumpBuffer(buffer, pw, args.tailLength)
}
+
+ val tableBuffers = dumpManager.getTableLogBuffers()
+ for (table in tableBuffers) {
+ dumpTableBuffer(table, pw, args.rawArgs)
+ }
+
+ logBufferEulogizer.readEulogyIfPresent(pw)
}
- private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) {
- if (args.listOnly) {
- dumpManager.listBuffers(pw)
- } else {
- dumpManager.dumpBuffers(pw, args.tailLength)
+ private fun dumpDumpables(pw: PrintWriter, args: ParsedArgs) =
+ dumpManager.getDumpables().listOrDumpEntries(pw, args)
+
+ private fun dumpBuffers(pw: PrintWriter, args: ParsedArgs) =
+ dumpManager.getLogBuffers().listOrDumpEntries(pw, args)
+
+ private fun dumpTables(pw: PrintWriter, args: ParsedArgs) =
+ dumpManager.getTableLogBuffers().listOrDumpEntries(pw, args)
+
+ private fun listTargetNames(targets: Collection<DumpsysEntry>, pw: PrintWriter) {
+ for (target in targets) {
+ pw.println(target.name)
}
}
- private fun dumpProtoTargets(
- targets: List<String>,
- fd: FileDescriptor,
- args: ParsedArgs
- ) {
+ private fun dumpProtoTargets(targets: List<String>, fd: FileDescriptor, args: ParsedArgs) {
val systemUIProto = SystemUIProtoDump()
+ val dumpables = dumpManager.getDumpables()
if (targets.isNotEmpty()) {
for (target in targets) {
- dumpManager.dumpProtoTarget(target, systemUIProto, args.rawArgs)
+ findBestProtoTargetMatch(dumpables, target)?.dumpProto(systemUIProto, args.rawArgs)
}
} else {
- dumpManager.dumpProtoDumpables(systemUIProto, args.rawArgs)
+ // Dump all protos
+ for (dumpable in dumpables) {
+ (dumpable.dumpable as? ProtoDumpable)?.dumpProto(systemUIProto, args.rawArgs)
+ }
}
+
val buffer = BufferedOutputStream(FileOutputStream(fd))
buffer.use {
it.write(MessageNano.toByteArray(systemUIProto))
@@ -192,36 +208,70 @@ class DumpHandler @Inject constructor(
}
}
- private fun dumpTargets(
- targets: List<String>,
- pw: PrintWriter,
- args: ParsedArgs
- ) {
+ // Attempts to dump the target list to the given PrintWriter. Since the arguments come in as
+ // a list of strings, we use the [findBestTargetMatch] method to determine the most-correct
+ // target with the given search string.
+ private fun dumpTargets(targets: List<String>, pw: PrintWriter, args: ParsedArgs) {
if (targets.isNotEmpty()) {
- for (target in targets) {
- dumpManager.dumpTarget(target, pw, args.rawArgs, args.tailLength)
+ val dumpables = dumpManager.getDumpables()
+ val buffers = dumpManager.getLogBuffers()
+ val tableBuffers = dumpManager.getTableLogBuffers()
+
+ targets.forEach { target ->
+ findTargetInCollection(target, dumpables, buffers, tableBuffers)?.dump(pw, args)
}
} else {
if (args.listOnly) {
+ val dumpables = dumpManager.getDumpables()
+ val buffers = dumpManager.getLogBuffers()
+
pw.println("Dumpables:")
- dumpManager.listDumpables(pw)
+ listTargetNames(dumpables, pw)
pw.println()
pw.println("Buffers:")
- dumpManager.listBuffers(pw)
+ listTargetNames(buffers, pw)
} else {
pw.println("Nothing to dump :(")
}
}
}
+ private fun findTargetInCollection(
+ target: String,
+ dumpables: Collection<DumpableEntry>,
+ logBuffers: Collection<LogBufferEntry>,
+ tableBuffers: Collection<TableLogBufferEntry>,
+ ) =
+ sequence {
+ findBestTargetMatch(dumpables, target)?.let { yield(it) }
+ findBestTargetMatch(logBuffers, target)?.let { yield(it) }
+ findBestTargetMatch(tableBuffers, target)?.let { yield(it) }
+ }
+ .sortedBy { it.name }
+ .minByOrNull { it.name.length }
+
+ private fun dumpDumpable(entry: DumpableEntry, pw: PrintWriter, args: Array<String>) {
+ pw.preamble(entry)
+ entry.dumpable.dump(pw, args)
+ }
+
+ private fun dumpBuffer(entry: LogBufferEntry, pw: PrintWriter, tailLength: Int) {
+ pw.preamble(entry)
+ entry.buffer.dump(pw, tailLength)
+ }
+
+ private fun dumpTableBuffer(buffer: TableLogBufferEntry, pw: PrintWriter, args: Array<String>) {
+ pw.preamble(buffer)
+ buffer.table.dump(pw, args)
+ }
+
private fun dumpConfig(pw: PrintWriter) {
pw.println("SystemUiServiceComponents configuration:")
pw.print("vendor component: ")
pw.println(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
- val services: MutableList<String> = startables.keys
- .map({ cls: Class<*> -> cls.simpleName })
- .toMutableList()
+ val services: MutableList<String> =
+ startables.keys.map({ cls: Class<*> -> cls.simpleName }).toMutableList()
services.add(context.resources.getString(R.string.config_systemUIVendorServiceComponent))
dumpServiceList(pw, "global", services.toTypedArray())
@@ -265,6 +315,7 @@ class DumpHandler @Inject constructor(
pw.println("Special commands:")
pw.println("$ <invocation> dumpables")
pw.println("$ <invocation> buffers")
+ pw.println("$ <invocation> tables")
pw.println("$ <invocation> bugreport-critical")
pw.println("$ <invocation> bugreport-normal")
pw.println("$ <invocation> config")
@@ -274,6 +325,7 @@ class DumpHandler @Inject constructor(
pw.println("$ <invocation> --list")
pw.println("$ <invocation> dumpables --list")
pw.println("$ <invocation> buffers --list")
+ pw.println("$ <invocation> tables --list")
pw.println()
pw.println("Show only the most recent N lines of buffers")
@@ -291,24 +343,26 @@ class DumpHandler @Inject constructor(
iterator.remove()
when (arg) {
PRIORITY_ARG -> {
- pArgs.dumpPriority = readArgument(iterator, PRIORITY_ARG) {
- if (PRIORITY_OPTIONS.contains(it)) {
- it
- } else {
- throw IllegalArgumentException()
+ pArgs.dumpPriority =
+ readArgument(iterator, PRIORITY_ARG) {
+ if (PRIORITY_OPTIONS.contains(it)) {
+ it
+ } else {
+ throw IllegalArgumentException()
+ }
}
- }
}
PROTO -> pArgs.proto = true
- "-t", "--tail" -> {
- pArgs.tailLength = readArgument(iterator, arg) {
- it.toInt()
- }
+ "-t",
+ "--tail" -> {
+ pArgs.tailLength = readArgument(iterator, arg) { it.toInt() }
}
- "-l", "--list" -> {
+ "-l",
+ "--list" -> {
pArgs.listOnly = true
}
- "-h", "--help" -> {
+ "-h",
+ "--help" -> {
pArgs.command = "help"
}
// This flag is passed as part of the proto dump in Bug reports, we can ignore
@@ -345,29 +399,131 @@ class DumpHandler @Inject constructor(
}
}
+ private fun DumpsysEntry.dump(pw: PrintWriter, args: ParsedArgs) =
+ when (this) {
+ is DumpableEntry -> dumpDumpable(this, pw, args.rawArgs)
+ is LogBufferEntry -> dumpBuffer(this, pw, args.tailLength)
+ is TableLogBufferEntry -> dumpTableBuffer(this, pw, args.rawArgs)
+ }
+
+ private fun Collection<DumpsysEntry>.listOrDumpEntries(pw: PrintWriter, args: ParsedArgs) =
+ if (args.listOnly) {
+ listTargetNames(this, pw)
+ } else {
+ forEach { it.dump(pw, args) }
+ }
+
companion object {
const val PRIORITY_ARG = "--dump-priority"
const val PRIORITY_ARG_CRITICAL = "CRITICAL"
const val PRIORITY_ARG_NORMAL = "NORMAL"
const val PROTO = "--proto"
+
+ /**
+ * Important: do not change this divider without updating any bug report processing tools
+ * (e.g. ABT), since this divider is used to determine boundaries for bug report views
+ */
+ const val DUMPSYS_DUMPABLE_DIVIDER =
+ "----------------------------------------------------------------------------"
+
+ /**
+ * Important: do not change this divider without updating any bug report processing tools
+ * (e.g. ABT), since this divider is used to determine boundaries for bug report views
+ */
+ const val DUMPSYS_BUFFER_DIVIDER =
+ "============================================================================"
+
+ private fun findBestTargetMatch(c: Collection<DumpsysEntry>, target: String) =
+ c.asSequence().filter { it.name.endsWith(target) }.minByOrNull { it.name.length }
+
+ private fun findBestProtoTargetMatch(
+ c: Collection<DumpableEntry>,
+ target: String
+ ): ProtoDumpable? =
+ c.asSequence()
+ .filter { it.name.endsWith(target) }
+ .filter { it.dumpable is ProtoDumpable }
+ .minByOrNull { it.name.length }
+ ?.dumpable as? ProtoDumpable
+
+ private fun PrintWriter.preamble(entry: DumpsysEntry) =
+ when (entry) {
+ // Historically TableLogBuffer was not separate from dumpables, so they have the
+ // same header
+ is DumpableEntry,
+ is TableLogBufferEntry -> {
+ println()
+ println(entry.name)
+ println(DUMPSYS_DUMPABLE_DIVIDER)
+ }
+ is LogBufferEntry -> {
+ println()
+ println()
+ println("BUFFER ${entry.name}:")
+ println(DUMPSYS_BUFFER_DIVIDER)
+ }
+ }
+
+ /**
+ * Zero-arg utility to write a [DumpableEntry] to the given [PrintWriter] in a
+ * dumpsys-appropriate format.
+ */
+ private fun dumpDumpable(entry: DumpableEntry, pw: PrintWriter) {
+ pw.preamble(entry)
+ entry.dumpable.dump(pw, arrayOf())
+ }
+
+ /**
+ * Zero-arg utility to write a [LogBufferEntry] to the given [PrintWriter] in a
+ * dumpsys-appropriate format.
+ */
+ private fun dumpBuffer(entry: LogBufferEntry, pw: PrintWriter) {
+ pw.preamble(entry)
+ entry.buffer.dump(pw, 0)
+ }
+
+ /**
+ * Zero-arg utility to write a [TableLogBufferEntry] to the given [PrintWriter] in a
+ * dumpsys-appropriate format.
+ */
+ private fun dumpTableBuffer(entry: TableLogBufferEntry, pw: PrintWriter) {
+ pw.preamble(entry)
+ entry.table.dump(pw, arrayOf())
+ }
+
+ /**
+ * Zero-arg utility to write a [DumpsysEntry] to the given [PrintWriter] in a
+ * dumpsys-appropriate format.
+ */
+ fun DumpsysEntry.dump(pw: PrintWriter) {
+ when (this) {
+ is DumpableEntry -> dumpDumpable(this, pw)
+ is LogBufferEntry -> dumpBuffer(this, pw)
+ is TableLogBufferEntry -> dumpTableBuffer(this, pw)
+ }
+ }
+
+ /** Format [entries] in a dumpsys-appropriate way, using [pw] */
+ fun dumpEntries(entries: Collection<DumpsysEntry>, pw: PrintWriter) {
+ entries.forEach { it.dump(pw) }
+ }
}
}
private val PRIORITY_OPTIONS = arrayOf(PRIORITY_ARG_CRITICAL, PRIORITY_ARG_NORMAL)
-private val COMMANDS = arrayOf(
+private val COMMANDS =
+ arrayOf(
"bugreport-critical",
"bugreport-normal",
"buffers",
"dumpables",
+ "tables",
"config",
"help"
-)
+ )
-private class ParsedArgs(
- val rawArgs: Array<String>,
- val nonFlagArgs: List<String>
-) {
+private class ParsedArgs(val rawArgs: Array<String>, val nonFlagArgs: List<String>) {
var dumpPriority: String? = null
var tailLength: Int = 0
var command: String? = null
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
index 2d57633e47a8..c924df6da263 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpManager.kt
@@ -18,9 +18,11 @@ package com.android.systemui.dump
import com.android.systemui.Dumpable
import com.android.systemui.ProtoDumpable
-import com.android.systemui.dump.nano.SystemUIProtoDump
+import com.android.systemui.dump.DumpsysEntry.DumpableEntry
+import com.android.systemui.dump.DumpsysEntry.LogBufferEntry
+import com.android.systemui.dump.DumpsysEntry.TableLogBufferEntry
import com.android.systemui.log.LogBuffer
-import java.io.PrintWriter
+import com.android.systemui.log.table.TableLogBuffer
import java.util.TreeMap
import javax.inject.Inject
import javax.inject.Singleton
@@ -37,8 +39,9 @@ import javax.inject.Singleton
@Singleton
open class DumpManager @Inject constructor() {
// NOTE: Using TreeMap ensures that iteration is in a predictable & alphabetical order.
- private val dumpables: MutableMap<String, RegisteredDumpable<Dumpable>> = TreeMap()
- private val buffers: MutableMap<String, RegisteredDumpable<LogBuffer>> = TreeMap()
+ private val dumpables: MutableMap<String, DumpableEntry> = TreeMap()
+ private val buffers: MutableMap<String, LogBufferEntry> = TreeMap()
+ private val tableLogBuffers: MutableMap<String, TableLogBufferEntry> = TreeMap()
/** See [registerCriticalDumpable]. */
fun registerCriticalDumpable(module: Dumpable) {
@@ -77,14 +80,14 @@ open class DumpManager @Inject constructor() {
* Register a dumpable to be called during a bug report.
*
* @param name The name to register the dumpable under. This is typically the qualified class
- * name of the thing being dumped (getClass().getName()), but can be anything as long as it
- * doesn't clash with an existing registration.
+ * name of the thing being dumped (getClass().getName()), but can be anything as long as it
+ * doesn't clash with an existing registration.
* @param priority the priority level of this dumpable, which affects at what point in the bug
- * report this gets dump. By default, the dumpable will be called during the CRITICAL section of
- * the bug report, so don't dump an excessive amount of stuff here.
+ * report this gets dump. By default, the dumpable will be called during the CRITICAL section
+ * of the bug report, so don't dump an excessive amount of stuff here.
*
* TODO(b/259973758): Replace all calls to this method with calls to [registerCriticalDumpable]
- * or [registerNormalDumpable] instead.
+ * or [registerNormalDumpable] instead.
*/
@Synchronized
@JvmOverloads
@@ -98,7 +101,7 @@ open class DumpManager @Inject constructor() {
throw IllegalArgumentException("'$name' is already registered")
}
- dumpables[name] = RegisteredDumpable(name, module, priority)
+ dumpables[name] = DumpableEntry(module, name, priority)
}
/**
@@ -110,217 +113,62 @@ open class DumpManager @Inject constructor() {
registerDumpable(module::class.java.simpleName, module)
}
- /**
- * Unregisters a previously-registered dumpable.
- */
+ /** Unregisters a previously-registered dumpable. */
@Synchronized
fun unregisterDumpable(name: String) {
dumpables.remove(name)
}
- /**
- * Register a [LogBuffer] to be dumped during a bug report.
- */
+ /** Register a [LogBuffer] to be dumped during a bug report. */
@Synchronized
fun registerBuffer(name: String, buffer: LogBuffer) {
if (!canAssignToNameLocked(name, buffer)) {
throw IllegalArgumentException("'$name' is already registered")
}
- // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
- // data.
- buffers[name] = RegisteredDumpable(name, buffer, DumpPriority.NORMAL)
- }
-
- /**
- * Dumps the alphabetically first, shortest-named dumpable or buffer whose registered name ends
- * with [target].
- */
- @Synchronized
- fun dumpTarget(
- target: String,
- pw: PrintWriter,
- args: Array<String>,
- tailLength: Int,
- ) {
- sequence {
- findBestTargetMatch(dumpables, target)?.let {
- yield(it.name to { dumpDumpable(it, pw, args) })
- }
- findBestTargetMatch(buffers, target)?.let {
- yield(it.name to { dumpBuffer(it, pw, tailLength) })
- }
- }.sortedBy { it.first }.minByOrNull { it.first.length }?.second?.invoke()
- }
-
- @Synchronized
- fun dumpProtoTarget(
- target: String,
- protoDump: SystemUIProtoDump,
- args: Array<String>
- ) {
- findBestProtoTargetMatch(dumpables, target)?.let {
- dumpProtoDumpable(it, protoDump, args)
- }
+ buffers[name] = LogBufferEntry(buffer, name)
}
+ /** Register a [TableLogBuffer] to be dumped during a bugreport */
@Synchronized
- fun dumpProtoDumpables(
- systemUIProtoDump: SystemUIProtoDump,
- args: Array<String>
- ) {
- for (dumpable in dumpables.values) {
- if (dumpable.dumpable is ProtoDumpable) {
- dumpProtoDumpable(
- dumpable.dumpable,
- systemUIProtoDump,
- args
- )
- }
- }
- }
-
- /**
- * Dumps all registered dumpables with critical priority to [pw]
- */
- @Synchronized
- fun dumpCritical(pw: PrintWriter, args: Array<String>) {
- for (dumpable in dumpables.values) {
- if (dumpable.priority == DumpPriority.CRITICAL) {
- dumpDumpable(dumpable, pw, args)
- }
- }
- }
-
- /**
- * To [pw], dumps (1) all registered dumpables with normal priority; and (2) all [LogBuffer]s.
- */
- @Synchronized
- fun dumpNormal(pw: PrintWriter, args: Array<String>, tailLength: Int = 0) {
- for (dumpable in dumpables.values) {
- if (dumpable.priority == DumpPriority.NORMAL) {
- dumpDumpable(dumpable, pw, args)
- }
- }
-
- for (buffer in buffers.values) {
- dumpBuffer(buffer, pw, tailLength)
+ fun registerTableLogBuffer(name: String, buffer: TableLogBuffer) {
+ if (!canAssignToNameLocked(name, buffer)) {
+ throw IllegalArgumentException("'$name' is already registered")
}
- }
- /**
- * Dump all the instances of [Dumpable].
- */
- @Synchronized
- fun dumpDumpables(pw: PrintWriter, args: Array<String>) {
- for (module in dumpables.values) {
- dumpDumpable(module, pw, args)
- }
+ // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+ // data.
+ tableLogBuffers[name] = TableLogBufferEntry(buffer, name)
}
- /**
- * Dumps the names of all registered dumpables (one per line)
- */
- @Synchronized
- fun listDumpables(pw: PrintWriter) {
- for (module in dumpables.values) {
- pw.println(module.name)
- }
- }
+ @Synchronized fun getDumpables(): Collection<DumpableEntry> = dumpables.values.toList()
- /**
- * Dumps all registered [LogBuffer]s to [pw]
- */
- @Synchronized
- fun dumpBuffers(pw: PrintWriter, tailLength: Int) {
- for (buffer in buffers.values) {
- dumpBuffer(buffer, pw, tailLength)
- }
- }
+ @Synchronized fun getLogBuffers(): Collection<LogBufferEntry> = buffers.values.toList()
- /**
- * Dumps the names of all registered buffers (one per line)
- */
@Synchronized
- fun listBuffers(pw: PrintWriter) {
- for (buffer in buffers.values) {
- pw.println(buffer.name)
- }
- }
+ fun getTableLogBuffers(): Collection<TableLogBufferEntry> = tableLogBuffers.values.toList()
@Synchronized
fun freezeBuffers() {
for (buffer in buffers.values) {
- buffer.dumpable.freeze()
+ buffer.buffer.freeze()
}
}
@Synchronized
fun unfreezeBuffers() {
for (buffer in buffers.values) {
- buffer.dumpable.unfreeze()
+ buffer.buffer.unfreeze()
}
}
- private fun dumpDumpable(
- dumpable: RegisteredDumpable<Dumpable>,
- pw: PrintWriter,
- args: Array<String>
- ) {
- pw.println()
- pw.println("${dumpable.name}:")
- pw.println("----------------------------------------------------------------------------")
- dumpable.dumpable.dump(pw, args)
- }
-
- private fun dumpBuffer(
- buffer: RegisteredDumpable<LogBuffer>,
- pw: PrintWriter,
- tailLength: Int
- ) {
- pw.println()
- pw.println()
- pw.println("BUFFER ${buffer.name}:")
- pw.println("============================================================================")
- buffer.dumpable.dump(pw, tailLength)
- }
-
- private fun dumpProtoDumpable(
- protoDumpable: ProtoDumpable,
- systemUIProtoDump: SystemUIProtoDump,
- args: Array<String>
- ) {
- protoDumpable.dumpProto(systemUIProtoDump, args)
- }
-
private fun canAssignToNameLocked(name: String, newDumpable: Any): Boolean {
- val existingDumpable = dumpables[name]?.dumpable ?: buffers[name]?.dumpable
+ val existingDumpable =
+ dumpables[name]?.dumpable ?: buffers[name]?.buffer ?: tableLogBuffers[name]?.table
return existingDumpable == null || newDumpable == existingDumpable
}
-
- private fun <V : Any> findBestTargetMatch(map: Map<String, V>, target: String): V? = map
- .asSequence()
- .filter { it.key.endsWith(target) }
- .minByOrNull { it.key.length }
- ?.value
-
- private fun findBestProtoTargetMatch(
- map: Map<String, RegisteredDumpable<Dumpable>>,
- target: String
- ): ProtoDumpable? = map
- .asSequence()
- .filter { it.key.endsWith(target) }
- .filter { it.value.dumpable is ProtoDumpable }
- .minByOrNull { it.key.length }
- ?.value?.dumpable as? ProtoDumpable
}
-private data class RegisteredDumpable<T>(
- val name: String,
- val dumpable: T,
- val priority: DumpPriority,
-)
-
/**
* The priority level for a given dumpable, which affects at what point in the bug report this gets
* dumped.
diff --git a/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt b/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt
new file mode 100644
index 000000000000..cd3e1bb7acac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dump/DumpsysEntry.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.dump
+
+import com.android.systemui.Dumpable
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.table.TableLogBuffer
+
+/**
+ * A DumpsysEntry is a named, registered entry tracked by [DumpManager] which can be addressed and
+ * used both in a bugreport / dumpsys invocation or in an individual CLI implementation.
+ *
+ * The idea here is that we define every type that [DumpManager] knows about and defines the minimum
+ * shared interface between each type. So far, just [name] and [priority]. This way, [DumpManager]
+ * can just store them in separate maps and do the minimal amount of work to discriminate between
+ * them.
+ *
+ * Individual consumers can request these participants in a list via the relevant get* methods on
+ * [DumpManager]
+ */
+sealed interface DumpsysEntry {
+ val name: String
+ val priority: DumpPriority
+
+ data class DumpableEntry(
+ val dumpable: Dumpable,
+ override val name: String,
+ override val priority: DumpPriority,
+ ) : DumpsysEntry
+
+ data class LogBufferEntry(
+ val buffer: LogBuffer,
+ override val name: String,
+ ) : DumpsysEntry {
+ // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+ // data.
+ override val priority: DumpPriority = DumpPriority.NORMAL
+ }
+
+ data class TableLogBufferEntry(
+ val table: TableLogBuffer,
+ override val name: String,
+ ) : DumpsysEntry {
+ // All buffers must be priority NORMAL, not CRITICAL, because they often contain a lot of
+ // data.
+ override val priority: DumpPriority = DumpPriority.NORMAL
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
index 2d5c9ae2e641..25b90bebf0d1 100644
--- a/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
+++ b/packages/SystemUI/src/com/android/systemui/dump/LogBufferEulogizer.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.icu.text.SimpleDateFormat
import android.util.Log
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpHandler.Companion.dumpEntries
import com.android.systemui.log.LogBuffer
import com.android.systemui.util.io.Files
import com.android.systemui.util.time.SystemClock
@@ -48,20 +49,21 @@ class LogBufferEulogizer(
private val files: Files,
private val logPath: Path,
private val minWriteGap: Long,
- private val maxLogAgeToDump: Long
+ private val maxLogAgeToDump: Long,
) {
- @Inject constructor(
+ @Inject
+ constructor(
context: Context,
dumpManager: DumpManager,
systemClock: SystemClock,
- files: Files
+ files: Files,
) : this(
dumpManager,
systemClock,
files,
Paths.get(context.filesDir.toPath().toString(), "log_buffers.txt"),
MIN_WRITE_GAP,
- MAX_AGE_TO_DUMP
+ MAX_AGE_TO_DUMP,
)
/**
@@ -91,7 +93,8 @@ class LogBufferEulogizer(
pw.println()
pw.println("Dump triggered by exception:")
reason.printStackTrace(pw)
- dumpManager.dumpBuffers(pw, 0)
+ val buffers = dumpManager.getLogBuffers()
+ dumpEntries(buffers, pw)
duration = systemClock.uptimeMillis() - start
pw.println()
pw.println("Buffer eulogy took ${duration}ms")
@@ -105,16 +108,17 @@ class LogBufferEulogizer(
return reason
}
- /**
- * If a eulogy file is present, writes its contents to [pw].
- */
+ /** If a eulogy file is present, writes its contents to [pw]. */
fun readEulogyIfPresent(pw: PrintWriter) {
try {
val millisSinceLastWrite = getMillisSinceLastWrite(logPath)
if (millisSinceLastWrite > maxLogAgeToDump) {
- Log.i(TAG, "Not eulogizing buffers; they are " +
+ Log.i(
+ TAG,
+ "Not eulogizing buffers; they are " +
TimeUnit.HOURS.convert(millisSinceLastWrite, TimeUnit.MILLISECONDS) +
- " hours old")
+ " hours old"
+ )
return
}
@@ -122,9 +126,7 @@ class LogBufferEulogizer(
pw.println()
pw.println()
pw.println("=============== BUFFERS FROM MOST RECENT CRASH ===============")
- s.forEach { line ->
- pw.println(line)
- }
+ s.forEach { line -> pw.println(line) }
}
} catch (e: IOException) {
// File doesn't exist, okay
@@ -134,12 +136,13 @@ class LogBufferEulogizer(
}
private fun getMillisSinceLastWrite(path: Path): Long {
- val stats = try {
- files.readAttributes(path, BasicFileAttributes::class.java)
- } catch (e: IOException) {
- // File doesn't exist
- null
- }
+ val stats =
+ try {
+ files.readAttributes(path, BasicFileAttributes::class.java)
+ } catch (e: IOException) {
+ // File doesn't exist
+ null
+ }
return systemClock.currentTimeMillis() - (stats?.lastModifiedTime()?.toMillis() ?: 0)
}
}
@@ -147,4 +150,4 @@ class LogBufferEulogizer(
private const val TAG = "BufferEulogizer"
private val MIN_WRITE_GAP = TimeUnit.MINUTES.toMillis(5)
private val MAX_AGE_TO_DUMP = TimeUnit.HOURS.toMillis(48)
-private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US) \ No newline at end of file
+private val DATE_FORMAT = SimpleDateFormat("MM-dd HH:mm:ss.SSS", Locale.US)
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index 280710755ff6..c71775b19e90 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -125,6 +125,7 @@ import com.android.systemui.plugins.GlobalActions.GlobalActionsManager;
import com.android.systemui.plugins.GlobalActionsPanelPlugin;
import com.android.systemui.scrim.ScrimDrawable;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -250,6 +251,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
protected Handler mMainHandler;
private int mSmallestScreenWidthDp;
private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+ private final ShadeController mShadeController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -360,6 +362,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
@Main Handler handler,
PackageManager packageManager,
Optional<CentralSurfaces> centralSurfacesOptional,
+ ShadeController shadeController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
DialogLaunchAnimator dialogLaunchAnimator) {
mContext = context;
@@ -392,6 +395,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mMainHandler = handler;
mSmallestScreenWidthDp = resources.getConfiguration().smallestScreenWidthDp;
mCentralSurfacesOptional = centralSurfacesOptional;
+ mShadeController = shadeController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mDialogLaunchAnimator = dialogLaunchAnimator;
@@ -700,7 +704,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mAdapter, mOverflowAdapter, mSysuiColorExtractor, mStatusBarService,
mLightBarController,
mNotificationShadeWindowController, this::onRefresh, mKeyguardShowing,
- mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional, mKeyguardUpdateMonitor,
+ mPowerAdapter, mUiEventLogger, mCentralSurfacesOptional,
+ mShadeController,
+ mKeyguardUpdateMonitor,
mLockPatternUtils);
dialog.setOnDismissListener(this);
@@ -2205,6 +2211,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private UiEventLogger mUiEventLogger;
private GestureDetector mGestureDetector;
private Optional<CentralSurfaces> mCentralSurfacesOptional;
+ private final ShadeController mShadeController;
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private LockPatternUtils mLockPatternUtils;
private float mWindowDimAmount;
@@ -2278,6 +2285,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
Runnable onRefreshCallback, boolean keyguardShowing,
MyPowerOptionsAdapter powerAdapter, UiEventLogger uiEventLogger,
Optional<CentralSurfaces> centralSurfacesOptional,
+ ShadeController shadeController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
LockPatternUtils lockPatternUtils) {
// We set dismissOnDeviceLock to false because we have a custom broadcast receiver to
@@ -2295,6 +2303,7 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mKeyguardShowing = keyguardShowing;
mUiEventLogger = uiEventLogger;
mCentralSurfacesOptional = centralSurfacesOptional;
+ mShadeController = shadeController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockPatternUtils = lockPatternUtils;
mGestureDetector = new GestureDetector(mContext, mGestureListener);
@@ -2342,12 +2351,10 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mUiEventLogger.log(GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
if (mCentralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
// match existing lockscreen behavior to open QS when swiping from status bar
- mCentralSurfacesOptional.ifPresent(
- centralSurfaces -> centralSurfaces.animateExpandSettingsPanel(null));
+ mShadeController.animateExpandQs();
} else {
// otherwise, swiping down should expand notification shade
- mCentralSurfacesOptional.ifPresent(
- centralSurfaces -> centralSurfaces.animateExpandNotificationsPanel());
+ mShadeController.animateExpandShade();
}
dismiss();
}
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
index 8125d70a6ab3..68dc1b3dc7d7 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/ShutdownUi.java
@@ -98,7 +98,7 @@ public class ShutdownUi {
window.setBackgroundDrawable(background);
window.setWindowAnimations(com.android.systemui.R.style.Animation_ShutdownUi);
- d.setContentView(getShutdownDialogContent());
+ d.setContentView(getShutdownDialogContent(isReboot));
d.setCancelable(false);
int color;
@@ -129,7 +129,12 @@ public class ShutdownUi {
d.show();
}
- public int getShutdownDialogContent() {
+ /**
+ * Returns the layout resource to use for UI while shutting down.
+ * @param isReboot Whether this is a reboot or a shutdown.
+ * @return
+ */
+ public int getShutdownDialogContent(boolean isReboot) {
return R.layout.shutdown_dialog;
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index 5a8c2253b185..94227bccfced 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
@@ -182,7 +183,8 @@ public class KeyguardService extends Service {
// Wrap Keyguard going away animation.
// Note: Also used for wrapping occlude by Dream animation. It works (with some redundancy).
- public static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
+ public static IRemoteTransition wrap(final KeyguardViewMediator keyguardViewMediator,
+ final IRemoteAnimationRunner runner, final boolean lockscreenLiveWallpaperEnabled) {
return new IRemoteTransition.Stub() {
private final ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = new ArrayMap<>();
@@ -213,7 +215,9 @@ public class KeyguardService extends Service {
}
}
initAlphaForAnimationTargets(t, apps);
- initAlphaForAnimationTargets(t, wallpapers);
+ if (lockscreenLiveWallpaperEnabled) {
+ initAlphaForAnimationTargets(t, wallpapers);
+ }
t.apply();
mFinishCallback = finishCallback;
runner.onAnimationStart(
@@ -236,6 +240,12 @@ public class KeyguardService extends Service {
SurfaceControl.Transaction candidateT, IBinder currentTransition,
IRemoteTransitionFinishedCallback candidateFinishCallback)
throws RemoteException {
+ if ((candidateInfo.getFlags() & TRANSIT_FLAG_KEYGUARD_APPEARING) != 0) {
+ keyguardViewMediator.setPendingLock(true);
+ keyguardViewMediator.cancelKeyguardExitAnimation();
+ return;
+ }
+
try {
synchronized (mLeashMap) {
runner.onAnimationCancelled();
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index c706363c9454..68e72c58972b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -19,6 +19,7 @@ package com.android.systemui.keyguard
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
+import android.app.WallpaperManager
import android.content.Context
import android.graphics.Matrix
import android.graphics.Rect
@@ -50,6 +51,7 @@ import com.android.systemui.shared.system.smartspace.SmartspaceState
import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM
import com.android.systemui.statusbar.policy.KeyguardStateController
import dagger.Lazy
import javax.inject.Inject
@@ -148,7 +150,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>,
private val statusBarStateController: SysuiStatusBarStateController,
private val notificationShadeWindowController: NotificationShadeWindowController,
- private val powerManager: PowerManager
+ private val powerManager: PowerManager,
+ private val wallpaperManager: WallpaperManager
) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() {
interface KeyguardUnlockAnimationListener {
@@ -171,7 +174,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
@JvmDefault
fun onUnlockAnimationStarted(
playingCannedAnimation: Boolean,
- fromWakeAndUnlock: Boolean,
+ isWakeAndUnlockNotFromDream: Boolean,
unlockAnimationStartDelay: Long,
unlockAnimationDuration: Long
) {}
@@ -204,6 +207,12 @@ class KeyguardUnlockAnimationController @Inject constructor(
var playingCannedUnlockAnimation = false
/**
+ * Whether we reached the swipe gesture threshold to dismiss keyguard, or restore it, once
+ * and should ignore any future changes to the dismiss amount before the animation finishes.
+ */
+ var dismissAmountThresholdsReached = false
+
+ /**
* Remote callback provided by Launcher that allows us to control the Launcher's unlock
* animation and smartspace.
*
@@ -582,10 +591,13 @@ class KeyguardUnlockAnimationController @Inject constructor(
playCannedUnlockAnimation()
}
+ // Notify if waking from AOD only
+ val isWakeAndUnlockNotFromDream = biometricUnlockControllerLazy.get().isWakeAndUnlock &&
+ biometricUnlockControllerLazy.get().mode != MODE_WAKE_AND_UNLOCK_FROM_DREAM
listeners.forEach {
it.onUnlockAnimationStarted(
playingCannedUnlockAnimation /* playingCannedAnimation */,
- biometricUnlockControllerLazy.get().isWakeAndUnlock /* isWakeAndUnlock */,
+ isWakeAndUnlockNotFromDream /* isWakeAndUnlockNotFromDream */,
CANNED_UNLOCK_START_DELAY /* unlockStartDelay */,
LAUNCHER_ICONS_ANIMATION_DURATION_MS /* unlockAnimationDuration */) }
@@ -647,6 +659,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
@VisibleForTesting
fun unlockToLauncherWithInWindowAnimations() {
+ surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f, wallpapers = false)
try {
@@ -686,8 +699,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
return@postDelayed
}
- if (wallpaperTargets != null) {
- fadeInWallpaper()
+ if ((wallpaperTargets?.isNotEmpty() == true) &&
+ wallpaperManager.isLockscreenLiveWallpaperEnabled()) {
+ fadeInWallpaper()
+ hideKeyguardViewAfterRemoteAnimation()
} else {
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
false /* cancelled */)
@@ -758,6 +773,10 @@ class KeyguardUnlockAnimationController @Inject constructor(
return
}
+ if (dismissAmountThresholdsReached) {
+ return
+ }
+
if (!keyguardStateController.isShowing) {
return
}
@@ -789,6 +808,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
return
}
+ // no-op if we alreaddy reached a threshold.
+ if (dismissAmountThresholdsReached) {
+ return
+ }
+
// no-op if animation is not requested yet.
if (!keyguardViewMediator.get().requestedShowSurfaceBehindKeyguard() ||
!keyguardViewMediator.get().isAnimatingBetweenKeyguardAndSurfaceBehindOrWillBe) {
@@ -803,6 +827,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
!keyguardStateController.isFlingingToDismissKeyguardDuringSwipeGesture &&
dismissAmount >= DISMISS_AMOUNT_EXIT_KEYGUARD_THRESHOLD)) {
setSurfaceBehindAppearAmount(1f)
+ dismissAmountThresholdsReached = true
keyguardViewMediator.get().exitKeyguardAndFinishSurfaceBehindRemoteAnimation(
false /* cancelled */)
}
@@ -937,6 +962,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
wallpaperTargets = null
playingCannedUnlockAnimation = false
+ dismissAmountThresholdsReached = false
willUnlockWithInWindowLauncherAnimations = false
willUnlockWithSmartspaceTransition = false
@@ -961,7 +987,7 @@ class KeyguardUnlockAnimationController @Inject constructor(
0 /* fadeOutDuration */
)
} else {
- Log.e(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
+ Log.i(TAG, "#hideKeyguardViewAfterRemoteAnimation called when keyguard view is not " +
"showing. Ignoring...")
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index fd00f858a33d..835a491bf92a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -47,6 +47,7 @@ import android.app.BroadcastOptions;
import android.app.IActivityTaskManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.WallpaperManager;
import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
@@ -282,6 +283,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
private AlarmManager mAlarmManager;
private AudioManager mAudioManager;
private StatusBarManager mStatusBarManager;
+ private WallpaperManager mWallpaperManager;
private final IStatusBarService mStatusBarService;
private final IBinder mStatusBarDisableToken = new Binder();
private final UserTracker mUserTracker;
@@ -1364,11 +1366,12 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
setShowingLocked(false /* showing */, true /* forceCallbacks */);
}
+ boolean isLLwpEnabled = getWallpaperManager().isLockscreenLiveWallpaperEnabled();
mKeyguardTransitions.register(
- KeyguardService.wrap(getExitAnimationRunner()),
- KeyguardService.wrap(getOccludeAnimationRunner()),
- KeyguardService.wrap(getOccludeByDreamAnimationRunner()),
- KeyguardService.wrap(getUnoccludeAnimationRunner()));
+ KeyguardService.wrap(this, getExitAnimationRunner(), isLLwpEnabled),
+ KeyguardService.wrap(this, getOccludeAnimationRunner(), isLLwpEnabled),
+ KeyguardService.wrap(this, getOccludeByDreamAnimationRunner(), isLLwpEnabled),
+ KeyguardService.wrap(this, getUnoccludeAnimationRunner(), isLLwpEnabled));
final ContentResolver cr = mContext.getContentResolver();
@@ -1414,6 +1417,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mWorkLockController = new WorkLockActivityController(mContext, mUserTracker);
}
+ // TODO(b/273443374) remove, temporary util to get a feature flag
+ private WallpaperManager getWallpaperManager() {
+ if (mWallpaperManager == null) {
+ mWallpaperManager = mContext.getSystemService(WallpaperManager.class);
+ }
+ return mWallpaperManager;
+ }
+
@Override
public void start() {
synchronized (this) {
@@ -1921,19 +1932,19 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
public IRemoteAnimationRunner getExitAnimationRunner() {
- return mExitAnimationRunner;
+ return validatingRemoteAnimationRunner(mExitAnimationRunner);
}
public IRemoteAnimationRunner getOccludeAnimationRunner() {
- return mOccludeAnimationRunner;
+ return validatingRemoteAnimationRunner(mOccludeAnimationRunner);
}
public IRemoteAnimationRunner getOccludeByDreamAnimationRunner() {
- return mOccludeByDreamAnimationRunner;
+ return validatingRemoteAnimationRunner(mOccludeByDreamAnimationRunner);
}
public IRemoteAnimationRunner getUnoccludeAnimationRunner() {
- return mUnoccludeAnimationRunner;
+ return validatingRemoteAnimationRunner(mUnoccludeAnimationRunner);
}
public boolean isHiding() {
@@ -1962,7 +1973,8 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
startKeyguardExitAnimation(0, 0);
}
- mPowerGestureIntercepted = mUpdateMonitor.isSecureCameraLaunchedOverKeyguard();
+ mPowerGestureIntercepted =
+ isOccluded && mUpdateMonitor.isSecureCameraLaunchedOverKeyguard();
if (mOccluded != isOccluded) {
mOccluded = isOccluded;
@@ -2909,6 +2921,7 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
// re-locking. We should just end the surface-behind animation without exiting the
// keyguard. The pending lock will be handled by onFinishedGoingToSleep().
finishSurfaceBehindRemoteAnimation(true);
+ maybeHandlePendingLock();
} else {
Log.d(TAG, "#handleCancelKeyguardExitAnimation: keyguard exit animation cancelled. "
+ "No pending lock, we should end up unlocked with the app/launcher visible.");
@@ -3264,8 +3277,6 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
/**
* Cancel the keyguard exit animation, usually because we were swiping to unlock but WM starts
* a new remote animation before finishing the keyguard exit animation.
- *
- * This will dismiss the keyguard.
*/
public void cancelKeyguardExitAnimation() {
Trace.beginSection("KeyguardViewMediator#cancelKeyguardExitAnimation");
@@ -3438,11 +3449,15 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
}
}
- private void setPendingLock(boolean hasPendingLock) {
+ public void setPendingLock(boolean hasPendingLock) {
mPendingLock = hasPendingLock;
Trace.traceCounter(Trace.TRACE_TAG_APP, "pendingLock", mPendingLock ? 1 : 0);
}
+ private boolean isViewRootReady() {
+ return mKeyguardViewControllerLazy.get().getViewRootImpl() != null;
+ }
+
public void addStateMonitorCallback(IKeyguardStateCallback callback) {
synchronized (this) {
mKeyguardStateCallbacks.add(callback);
@@ -3545,4 +3560,27 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable,
mInteractionJankMonitor.cancel(CUJ_LOCKSCREEN_OCCLUSION);
}
}
+
+ private IRemoteAnimationRunner validatingRemoteAnimationRunner(IRemoteAnimationRunner wrapped) {
+ return new IRemoteAnimationRunner.Stub() {
+ @Override
+ public void onAnimationCancelled() throws RemoteException {
+ wrapped.onAnimationCancelled();
+ }
+
+ @Override
+ public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+ RemoteAnimationTarget[] wallpapers,
+ RemoteAnimationTarget[] nonApps,
+ IRemoteAnimationFinishedCallback finishedCallback)
+ throws RemoteException {
+ if (!isViewRootReady()) {
+ Log.w(TAG, "Skipping remote animation - view root not ready");
+ return;
+ }
+
+ wrapped.onAnimationStart(transit, apps, wallpapers, nonApps, finishedCallback);
+ }
+ };
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
index c0a5a51a910d..4dc52ff46707 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/bouncer/ui/BouncerMessageView.kt
@@ -41,6 +41,8 @@ class BouncerMessageView : LinearLayout {
super.onFinishInflate()
primaryMessageView = findViewById(R.id.bouncer_primary_message_area)
secondaryMessageView = findViewById(R.id.bouncer_secondary_message_area)
+ primaryMessageView?.disable()
+ secondaryMessageView?.disable()
}
fun init(factory: KeyguardMessageAreaController.Factory) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 44e74e7e339b..9db3c22dff84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -63,7 +63,7 @@ constructor(
val hasCards = response?.walletCards?.isNotEmpty() == true
trySendWithFailureLogging(
state(
- isFeatureEnabled = walletController.isWalletEnabled,
+ isFeatureEnabled = isWalletAvailable(),
hasCard = hasCards,
tileIcon = walletController.walletClient.tileIcon,
),
@@ -100,7 +100,7 @@ constructor(
return when {
!walletController.walletClient.isWalletServiceAvailable ->
KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
- !walletController.isWalletEnabled || queryCards().isEmpty() -> {
+ !isWalletAvailable() || queryCards().isEmpty() -> {
KeyguardQuickAffordanceConfig.PickerScreenState.Disabled(
instructions =
listOf(
@@ -146,6 +146,11 @@ constructor(
}
}
+ private fun isWalletAvailable() =
+ with(walletController.walletClient) {
+ isWalletServiceAvailable && isWalletFeatureAvailable
+ }
+
private fun state(
isFeatureEnabled: Boolean,
hasCard: Boolean,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index 100bc596103d..c94aa1151b84 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -47,6 +47,11 @@ import kotlinx.coroutines.flow.filter
* To create or modify logic that controls when and how transitions get created, look at
* [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
* this repository.
+ *
+ * To print all transitions to logcat to help with debugging, run this command:
+ * adb shell settings put global systemui/buffer/KeyguardLog VERBOSE
+ *
+ * This will print all keyguard transitions to logcat with the KeyguardTransitionAuditLogger tag.
*/
interface KeyguardTransitionRepository {
/**
diff --git a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
index 19e112487c46..1e2f71f01c35 100644
--- a/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/table/TableLogBufferFactory.kt
@@ -61,7 +61,7 @@ constructor(
bgDispatcher,
coroutineScope,
)
- dumpManager.registerNormalDumpable(name, tableBuffer)
+ dumpManager.registerTableLogBuffer(name, tableBuffer)
tableBuffer.init()
return tableBuffer
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
index ff763d81f950..f908481d3912 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/pipeline/LocalMediaManagerFactory.kt
@@ -18,8 +18,8 @@ package com.android.systemui.media.controls.pipeline
import android.content.Context
import com.android.settingslib.bluetooth.LocalBluetoothManager
-import com.android.settingslib.media.InfoMediaManager
import com.android.settingslib.media.LocalMediaManager
+import com.android.settingslib.media.ManagerInfoMediaManager
import javax.inject.Inject
/** Factory to create [LocalMediaManager] objects. */
@@ -31,7 +31,7 @@ constructor(
) {
/** Creates a [LocalMediaManager] for the given package. */
fun create(packageName: String): LocalMediaManager {
- return InfoMediaManager(context, packageName, null, localBluetoothManager).run {
+ return ManagerInfoMediaManager(context, packageName, null, localBluetoothManager).run {
LocalMediaManager(context, localBluetoothManager, this, packageName)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 516fbf5ca12c..14386c1c0fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -76,7 +76,6 @@ import androidx.constraintlayout.widget.ConstraintSet;
import com.android.app.animation.Interpolators;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.graphics.ColorUtils;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.InstanceId;
import com.android.internal.widget.CachingIconView;
@@ -1211,24 +1210,24 @@ public class MediaControlPanel {
private TurbulenceNoiseAnimationConfig createTurbulenceNoiseAnimation() {
return new TurbulenceNoiseAnimationConfig(
- TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_GRID_COUNT,
+ /* gridCount= */ 2.14f,
TurbulenceNoiseAnimationConfig.DEFAULT_LUMINOSITY_MULTIPLIER,
- /* noiseMoveSpeedX= */ 0f,
+ /* noiseMoveSpeedX= */ 0.42f,
/* noiseMoveSpeedY= */ 0f,
TurbulenceNoiseAnimationConfig.DEFAULT_NOISE_SPEED_Z,
/* color= */ mColorSchemeTransition.getAccentPrimary().getCurrentColor(),
- // We want to add (BlendMode.PLUS) the turbulence noise on top of the album art.
- // Thus, set the background color with alpha 0.
- /* backgroundColor= */ ColorUtils.setAlphaComponent(Color.BLACK, 0),
- TurbulenceNoiseAnimationConfig.DEFAULT_OPACITY,
+ /* backgroundColor= */ Color.BLACK,
+ /* opacity= */ 51,
/* width= */ mMediaViewHolder.getMultiRippleView().getWidth(),
/* height= */ mMediaViewHolder.getMultiRippleView().getHeight(),
TurbulenceNoiseAnimationConfig.DEFAULT_MAX_DURATION_IN_MILLIS,
- /* easeInDuration= */ 2500f,
- /* easeOutDuration= */ 2500f,
+ /* easeInDuration= */ 1350f,
+ /* easeOutDuration= */ 1350f,
this.getContext().getResources().getDisplayMetrics().density,
- BlendMode.PLUS,
- /* onAnimationEnd= */ null
+ BlendMode.SCREEN,
+ /* onAnimationEnd= */ null,
+ /* lumaMatteBlendFactor= */ 0.26f,
+ /* lumaMatteOverallBrightness= */ 0.09f
);
}
private void clearButton(final ImageButton button) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
index abf0932c8407..b4578e97eda2 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -41,6 +41,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.graphics.drawable.IconCompat;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.qrcode.QrCodeGenerator;
@@ -58,6 +59,17 @@ import com.google.zxing.WriterException;
public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private static final String TAG = "MediaOutputBroadcastDialog";
+ static final int METADATA_BROADCAST_NAME = 0;
+ static final int METADATA_BROADCAST_CODE = 1;
+
+ private static final int MAX_BROADCAST_INFO_UPDATE = 3;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MAX_LENGTH = 16;
+ @VisibleForTesting
+ static final int BROADCAST_CODE_MIN_LENGTH = 4;
+ @VisibleForTesting
+ static final int BROADCAST_NAME_MAX_LENGTH = 254;
+
private ViewStub mBroadcastInfoArea;
private ImageView mBroadcastQrCodeView;
private ImageView mBroadcastNotify;
@@ -67,14 +79,16 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
private ImageView mBroadcastCodeEye;
private Boolean mIsPasswordHide = true;
private ImageView mBroadcastCodeEdit;
- private AlertDialog mAlertDialog;
+ @VisibleForTesting
+ AlertDialog mAlertDialog;
private TextView mBroadcastErrorMessage;
private int mRetryCount = 0;
private String mCurrentBroadcastName;
private String mCurrentBroadcastCode;
private boolean mIsStopbyUpdateBroadcastCode = false;
+ private boolean mIsLeBroadcastAssistantCallbackRegistered;
- private TextWatcher mTextWatcher = new TextWatcher() {
+ private TextWatcher mBroadcastCodeTextWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// Do nothing
@@ -102,7 +116,9 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
R.string.media_output_broadcast_code_hint_no_less_than_min);
} else if (breakBroadcastCodeRuleTextLengthMoreThanMax) {
mBroadcastErrorMessage.setText(
- R.string.media_output_broadcast_code_hint_no_more_than_max);
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_CODE_MAX_LENGTH));
}
mBroadcastErrorMessage.setVisibility(breakRule ? View.VISIBLE : View.INVISIBLE);
@@ -113,7 +129,40 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- private boolean mIsLeBroadcastAssistantCallbackRegistered;
+ private TextWatcher mBroadcastNameTextWatcher = new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ // Do nothing
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ // Do nothing
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (mAlertDialog == null || mBroadcastErrorMessage == null) {
+ return;
+ }
+ boolean breakBroadcastNameRuleTextLengthMoreThanMax =
+ s.length() > BROADCAST_NAME_MAX_LENGTH;
+ boolean breakRule = breakBroadcastNameRuleTextLengthMoreThanMax || (s.length() == 0);
+
+ if (breakBroadcastNameRuleTextLengthMoreThanMax) {
+ mBroadcastErrorMessage.setText(
+ mContext.getResources().getString(
+ R.string.media_output_broadcast_edit_hint_no_more_than_max,
+ BROADCAST_NAME_MAX_LENGTH));
+ }
+ mBroadcastErrorMessage.setVisibility(
+ breakBroadcastNameRuleTextLengthMoreThanMax ? View.VISIBLE : View.INVISIBLE);
+ Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveBtn != null) {
+ positiveBtn.setEnabled(breakRule ? false : true);
+ }
+ }
+ };
private BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
new BluetoothLeBroadcastAssistant.Callback() {
@@ -186,13 +235,6 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
};
- static final int METADATA_BROADCAST_NAME = 0;
- static final int METADATA_BROADCAST_CODE = 1;
-
- private static final int MAX_BROADCAST_INFO_UPDATE = 3;
- private static final int BROADCAST_CODE_MAX_LENGTH = 16;
- private static final int BROADCAST_CODE_MIN_LENGTH = 4;
-
MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
super(context, broadcastSender, mediaOutputController);
@@ -391,13 +433,12 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
R.layout.media_output_broadcast_update_dialog, null);
final EditText editText = layout.requireViewById(R.id.broadcast_edit_text);
editText.setText(editString);
- if (isBroadcastCode) {
- editText.addTextChangedListener(mTextWatcher);
- }
+ editText.addTextChangedListener(
+ isBroadcastCode ? mBroadcastCodeTextWatcher : mBroadcastNameTextWatcher);
mBroadcastErrorMessage = layout.requireViewById(R.id.broadcast_error_message);
mAlertDialog = new Builder(mContext)
.setTitle(isBroadcastCode ? R.string.media_output_broadcast_code
- : R.string.media_output_broadcast_name)
+ : R.string.media_output_broadcast_name)
.setView(layout)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.media_output_broadcast_dialog_save,
@@ -420,7 +461,8 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
return mMediaOutputController.getBroadcastMetadata();
}
- private void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) {
+ @VisibleForTesting
+ void updateBroadcastInfo(boolean isBroadcastCode, String updatedString) {
Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
if (positiveBtn != null) {
positiveBtn.setEnabled(false);
@@ -523,16 +565,33 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
}
private void handleUpdateFailedUi() {
- final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
- mBroadcastErrorMessage.setVisibility(View.VISIBLE);
+ if (mAlertDialog == null) {
+ Log.d(TAG, "handleUpdateFailedUi: mAlertDialog is null");
+ return;
+ }
+ int errorMessageStringId = -1;
+ boolean enablePositiveBtn = false;
if (mRetryCount < MAX_BROADCAST_INFO_UPDATE) {
- if (positiveBtn != null) {
- positiveBtn.setEnabled(true);
- }
- mBroadcastErrorMessage.setText(R.string.media_output_broadcast_update_error);
+ enablePositiveBtn = true;
+ errorMessageStringId = R.string.media_output_broadcast_update_error;
} else {
mRetryCount = 0;
- mBroadcastErrorMessage.setText(R.string.media_output_broadcast_last_update_error);
+ errorMessageStringId = R.string.media_output_broadcast_last_update_error;
}
+
+ // update UI
+ final Button positiveBtn = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ if (positiveBtn != null && enablePositiveBtn) {
+ positiveBtn.setEnabled(true);
+ }
+ if (mBroadcastErrorMessage != null) {
+ mBroadcastErrorMessage.setVisibility(View.VISIBLE);
+ mBroadcastErrorMessage.setText(errorMessageStringId);
+ }
+ }
+
+ @VisibleForTesting
+ int getRetryCount() {
+ return mRetryCount;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index cc75478ef506..25899e5ae178 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -76,6 +76,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcastMetadata;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.media.InfoMediaManager;
import com.android.settingslib.media.LocalMediaManager;
+import com.android.settingslib.media.ManagerInfoMediaManager;
import com.android.settingslib.media.MediaDevice;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.R;
@@ -194,7 +195,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
mKeyGuardManager = keyGuardManager;
mFeatureFlags = featureFlags;
mUserTracker = userTracker;
- InfoMediaManager imm = new InfoMediaManager(mContext, packageName, null, lbm);
+ InfoMediaManager imm = new ManagerInfoMediaManager(mContext, packageName, null, lbm);
mLocalMediaManager = new LocalMediaManager(mContext, lbm, imm, packageName);
mMetricLogger = new MediaOutputMetricLogger(mContext, mPackageName);
mDialogLaunchAnimator = dialogLaunchAnimator;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index b0fb349083e6..682335e0b419 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -76,7 +76,6 @@ import android.telecom.TelecomManager;
import android.text.TextUtils;
import android.util.Log;
import android.view.Display;
-import android.view.DisplayCutout;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
import android.view.InsetsFrameProvider;
@@ -1730,9 +1729,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
tappableElementProvider.setInsetsSize(Insets.NONE);
}
- final DisplayCutout cutout = userContext.getDisplay().getCutout();
- final int safeInsetsLeft = cutout != null ? cutout.getSafeInsetLeft() : 0;
- final int safeInsetsRight = cutout != null ? cutout.getSafeInsetRight() : 0;
final int gestureHeight = userContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.navigation_bar_gesture_height);
final boolean handlingGesture = mEdgeBackGestureHandler.isHandlingGestures();
@@ -1742,19 +1738,23 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
mandatoryGestureProvider.setInsetsSize(Insets.of(0, 0, 0, gestureHeight));
}
final int gestureInsetsLeft = handlingGesture
- ? mEdgeBackGestureHandler.getEdgeWidthLeft() + safeInsetsLeft : 0;
+ ? mEdgeBackGestureHandler.getEdgeWidthLeft() : 0;
final int gestureInsetsRight = handlingGesture
- ? mEdgeBackGestureHandler.getEdgeWidthRight() + safeInsetsRight : 0;
+ ? mEdgeBackGestureHandler.getEdgeWidthRight() : 0;
return new InsetsFrameProvider[] {
navBarProvider,
tappableElementProvider,
mandatoryGestureProvider,
new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.systemGestures())
.setSource(InsetsFrameProvider.SOURCE_DISPLAY)
- .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0)),
+ .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0))
+ .setMinimalInsetsSizeInDisplayCutoutSafe(
+ Insets.of(gestureInsetsLeft, 0, 0, 0)),
new InsetsFrameProvider(mInsetsSourceOwner, 1, WindowInsets.Type.systemGestures())
.setSource(InsetsFrameProvider.SOURCE_DISPLAY)
.setInsetsSize(Insets.of(0, 0, gestureInsetsRight, 0))
+ .setMinimalInsetsSizeInDisplayCutoutSafe(
+ Insets.of(0, 0, gestureInsetsRight, 0))
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
index c804df8fa555..a256b59ac076 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -157,17 +157,17 @@ class BackPanel(
arrowPaint.color = Utils.getColorAttrDefaultColor(context,
if (isDeviceInNightTheme) {
- com.android.internal.R.attr.colorAccentPrimary
+ com.android.internal.R.attr.materialColorOnSecondaryContainer
} else {
- com.android.internal.R.attr.textColorPrimary
+ com.android.internal.R.attr.materialColorOnSecondaryFixed
}
)
arrowBackgroundPaint.color = Utils.getColorAttrDefaultColor(context,
if (isDeviceInNightTheme) {
- com.android.internal.R.attr.materialColorOnSecondary
+ com.android.internal.R.attr.materialColorSecondaryContainer
} else {
- com.android.internal.R.attr.colorAccentSecondary
+ com.android.internal.R.attr.materialColorSecondaryFixedDim
}
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index 59b94b7c4bd4..d2568ac79105 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -52,8 +52,8 @@ import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepositor
import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.tuner.TunerService.Tunable;
import com.android.systemui.util.settings.SecureSettings;
@@ -66,7 +66,6 @@ import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Objects;
-import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.function.Predicate;
@@ -108,7 +107,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
private AutoTileManager mAutoTiles;
private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
private int mCurrentUser;
- private final Optional<CentralSurfaces> mCentralSurfacesOptional;
+ private final ShadeController mShadeController;
private Context mUserContext;
private UserTracker mUserTracker;
private SecureSettings mSecureSettings;
@@ -129,7 +128,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
- Optional<CentralSurfaces> centralSurfacesOptional,
+ ShadeController shadeController,
QSLogger qsLogger,
UserTracker userTracker,
SecureSettings secureSettings,
@@ -148,7 +147,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
mUserFileManager = userFileManager;
mFeatureFlags = featureFlags;
- mCentralSurfacesOptional = centralSurfacesOptional;
+ mShadeController = shadeController;
mQsFactories.add(defaultFactory);
pluginManager.addPluginListener(this, QSFactory.class, true);
@@ -209,17 +208,17 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P
@Override
public void collapsePanels() {
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateCollapsePanels);
+ mShadeController.postAnimateCollapseShade();
}
@Override
public void forceCollapsePanels() {
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateForceCollapsePanels);
+ mShadeController.postAnimateForceCollapseShade();
}
@Override
public void openPanels() {
- mCentralSurfacesOptional.ifPresent(CentralSurfaces::postAnimateOpenPanels);
+ mShadeController.postAnimateExpandQs();
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
index 260caa767a5e..fa6de8dcdafe 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt
@@ -16,8 +16,7 @@
package com.android.systemui.qs.pipeline.domain.interactor
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.Optional
+import com.android.systemui.shade.ShadeController
import javax.inject.Inject
/** Encapsulates business logic for interacting with the QS panel. */
@@ -37,17 +36,17 @@ interface PanelInteractor {
class PanelInteractorImpl
@Inject
constructor(
- private val centralSurfaces: Optional<CentralSurfaces>,
+ private val shadeController: ShadeController,
) : PanelInteractor {
override fun collapsePanels() {
- centralSurfaces.ifPresent { it.postAnimateCollapsePanels() }
+ shadeController.postAnimateCollapseShade()
}
override fun forceCollapsePanels() {
- centralSurfaces.ifPresent { it.postAnimateForceCollapsePanels() }
+ shadeController.postAnimateForceCollapseShade()
}
override fun openPanels() {
- centralSurfaces.ifPresent { it.postAnimateOpenPanels() }
+ shadeController.postAnimateExpandQs()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index bb38b30339ef..e7dde6617964 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -360,7 +360,7 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis
@Override
public void toggleNotificationPanel() {
verifyCallerAndClearCallingIdentityPostMain("toggleNotificationPanel", () ->
- mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::togglePanel));
+ mCommandQueue.togglePanel());
}
private boolean verifyCaller(String reason) {
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index e1ac0fd1fd16..2c4555a2378a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -427,6 +427,7 @@ public class RecordingService extends Service implements ScreenMediaRecorderList
Log.e(TAG, "stopRecording called, but there was an error when ending"
+ "recording");
exception.printStackTrace();
+ createErrorNotification();
} catch (Throwable throwable) {
// Something unexpected happen, SystemUI will crash but let's delete
// the temporary files anyway
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 3227ef47f733..bd1b7ca7916a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -137,7 +137,12 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Since Quick Share target recommendation does not rely on image URL, it is
// queried and surfaced before image compress/export. Action intent would not be
// used, because it does not contain image URL.
- queryQuickShareAction(image, mParams.owner);
+ Notification.Action quickShare =
+ queryQuickShareAction(mScreenshotId, image, mParams.owner, null);
+ if (quickShare != null) {
+ mQuickShareData.quickShareAction = quickShare;
+ mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
+ }
}
// Call synchronously here since already on a background thread.
@@ -176,9 +181,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
smartActionsEnabled);
mImageData.deleteAction = createDeleteAction(mContext, mContext.getResources(), uri,
smartActionsEnabled);
- mImageData.quickShareAction = createQuickShareAction(mContext,
- mQuickShareData.quickShareAction, uri);
- mImageData.subject = getSubjectString();
+ mImageData.quickShareAction = createQuickShareAction(
+ mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image,
+ mParams.owner);
+ mImageData.subject = getSubjectString(mImageTime);
mParams.mActionsReadyListener.onActionsReady(mImageData);
if (DEBUG_CALLBACK) {
@@ -251,7 +257,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
new String[]{ClipDescription.MIMETYPE_TEXT_PLAIN}),
new ClipData.Item(uri));
sharingIntent.setClipData(clipdata);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString());
+ sharingIntent.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(mImageTime));
sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
@@ -417,60 +423,73 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
}
/**
- * Populate image uri into intent of Quick Share action.
+ * Wrap the quickshare intent and populate the fillin intent with the URI
*/
@VisibleForTesting
- private Notification.Action createQuickShareAction(Context context, Notification.Action action,
- Uri uri) {
- if (action == null) {
+ Notification.Action createQuickShareAction(
+ Notification.Action quickShare, String screenshotId, Uri uri, long imageTime,
+ Bitmap image, UserHandle user) {
+ if (quickShare == null) {
return null;
+ } else if (quickShare.actionIntent.isImmutable()) {
+ Notification.Action quickShareWithUri =
+ queryQuickShareAction(screenshotId, image, user, uri);
+ if (quickShareWithUri == null
+ || !quickShareWithUri.title.toString().contentEquals(quickShare.title)) {
+ return null;
+ }
+ quickShare = quickShareWithUri;
}
- // Populate image URI into Quick Share chip intent
- Intent sharingIntent = action.actionIntent.getIntent();
- sharingIntent.setType("image/png");
- sharingIntent.putExtra(Intent.EXTRA_STREAM, uri);
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
- String subject = String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
- sharingIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
- // Include URI in ClipData also, so that grantPermission picks it up.
- // We don't use setData here because some apps interpret this as "to:".
- ClipData clipdata = new ClipData(new ClipDescription("content",
- new String[]{"image/png"}),
- new ClipData.Item(uri));
- sharingIntent.setClipData(clipdata);
- sharingIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- PendingIntent updatedPendingIntent = PendingIntent.getActivity(
- context, 0, sharingIntent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
-
- // Proxy smart actions through {@link SmartActionsReceiver} for logging smart actions.
- Bundle extras = action.getExtras();
+
+ Intent wrappedIntent = new Intent(mContext, SmartActionsReceiver.class)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent)
+ .putExtra(ScreenshotController.EXTRA_ACTION_INTENT_FILLIN,
+ createFillInIntent(uri, imageTime))
+ .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+ Bundle extras = quickShare.getExtras();
String actionType = extras.getString(
ScreenshotNotificationSmartActionsProvider.ACTION_TYPE,
ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE);
- Intent intent = new Intent(context, SmartActionsReceiver.class)
- .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, updatedPendingIntent)
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// We only query for quick share actions when smart actions are enabled, so we can assert
// that it's true here.
- addIntentExtras(mScreenshotId, intent, actionType, true /* smartActionsEnabled */);
- PendingIntent broadcastIntent = PendingIntent.getBroadcast(context,
- mRandom.nextInt(),
- intent,
- PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- return new Notification.Action.Builder(action.getIcon(), action.title,
- broadcastIntent).setContextual(true).addExtras(extras).build();
+ addIntentExtras(screenshotId, wrappedIntent, actionType, true /* smartActionsEnabled */);
+ PendingIntent broadcastIntent =
+ PendingIntent.getBroadcast(mContext, mRandom.nextInt(), wrappedIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ return new Notification.Action.Builder(quickShare.getIcon(), quickShare.title,
+ broadcastIntent)
+ .setContextual(true)
+ .addExtras(extras)
+ .build();
+ }
+
+ private Intent createFillInIntent(Uri uri, long imageTime) {
+ Intent fillIn = new Intent();
+ fillIn.setType("image/png");
+ fillIn.putExtra(Intent.EXTRA_STREAM, uri);
+ fillIn.putExtra(Intent.EXTRA_SUBJECT, getSubjectString(imageTime));
+ // Include URI in ClipData also, so that grantPermission picks it up.
+ // We don't use setData here because some apps interpret this as "to:".
+ ClipData clipData = new ClipData(
+ new ClipDescription("content", new String[]{"image/png"}),
+ new ClipData.Item(uri));
+ fillIn.setClipData(clipData);
+ fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ return fillIn;
}
/**
* Query and surface Quick Share chip if it is available. Action intent would not be used,
* because it does not contain image URL which would be populated in {@link
- * #createQuickShareAction(Context, Notification.Action, Uri)}
+ * #createQuickShareAction(Notification.Action, String, Uri, long, Bitmap, UserHandle)}
*/
- private void queryQuickShareAction(Bitmap image, UserHandle user) {
+
+ @VisibleForTesting
+ Notification.Action queryQuickShareAction(
+ String screenshotId, Bitmap image, UserHandle user, Uri uri) {
CompletableFuture<List<Notification.Action>> quickShareActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture(
- mScreenshotId, null, image, mSmartActionsProvider,
+ screenshotId, uri, image, mSmartActionsProvider,
ScreenshotSmartActionType.QUICK_SHARE_ACTION,
true /* smartActionsEnabled */, user);
int timeoutMs = DeviceConfig.getInt(
@@ -479,17 +498,17 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
500);
List<Notification.Action> quickShareActions =
mScreenshotSmartActions.getSmartActions(
- mScreenshotId, quickShareActionsFuture, timeoutMs,
+ screenshotId, quickShareActionsFuture, timeoutMs,
mSmartActionsProvider,
ScreenshotSmartActionType.QUICK_SHARE_ACTION);
if (!quickShareActions.isEmpty()) {
- mQuickShareData.quickShareAction = quickShareActions.get(0);
- mParams.mQuickShareActionsReadyListener.onActionsReady(mQuickShareData);
+ return quickShareActions.get(0);
}
+ return null;
}
- private String getSubjectString() {
- String subjectDate = DateFormat.getDateTimeInstance().format(new Date(mImageTime));
+ private static String getSubjectString(long imageTime) {
+ String subjectDate = DateFormat.getDateTimeInstance().format(new Date(imageTime));
return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index 77a65b22a7f4..b59106efb769 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -246,6 +246,7 @@ public class ScreenshotController {
static final String EXTRA_SMART_ACTIONS_ENABLED = "android:smart_actions_enabled";
static final String EXTRA_OVERRIDE_TRANSITION = "android:screenshot_override_transition";
static final String EXTRA_ACTION_INTENT = "android:screenshot_action_intent";
+ static final String EXTRA_ACTION_INTENT_FILLIN = "android:screenshot_action_intent_fillin";
static final String SCREENSHOT_URI_ID = "android:screenshot_uri_id";
static final String EXTRA_CANCEL_NOTIFICATION = "android:screenshot_cancel_notification";
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 68b46d2b7525..ca713feafe80 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -30,7 +30,6 @@ import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -61,7 +60,6 @@ public class ScreenshotSmartActions {
screenshotNotificationSmartActionsProviderProvider;
}
- @VisibleForTesting
CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
String screenshotId, Uri screenshotUri, Bitmap image,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
@@ -83,7 +81,7 @@ public class ScreenshotSmartActions {
if (image.getConfig() != Bitmap.Config.HARDWARE) {
if (DEBUG_ACTIONS) {
Log.d(TAG, String.format("Bitmap expected: Hardware, Bitmap found: %s. "
- + "Returning empty list.", image.getConfig()));
+ + "Returning empty list.", image.getConfig()));
}
return CompletableFuture.completedFuture(Collections.emptyList());
}
@@ -112,7 +110,6 @@ public class ScreenshotSmartActions {
return smartActionsFuture;
}
- @VisibleForTesting
List<Notification.Action> getSmartActions(String screenshotId,
CompletableFuture<List<Notification.Action>> smartActionsFuture, int timeoutMs,
ScreenshotNotificationSmartActionsProvider smartActionsProvider,
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index 45af1874e9db..9761f5931193 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -18,6 +18,7 @@ package com.android.systemui.screenshot;
import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT;
+import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_INTENT_FILLIN;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ACTION_TYPE;
import static com.android.systemui.screenshot.ScreenshotController.EXTRA_ID;
@@ -46,7 +47,9 @@ public class SmartActionsReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
- PendingIntent pendingIntent = intent.getParcelableExtra(EXTRA_ACTION_INTENT);
+ PendingIntent pendingIntent =
+ intent.getParcelableExtra(EXTRA_ACTION_INTENT, PendingIntent.class);
+ Intent fillIn = intent.getParcelableExtra(EXTRA_ACTION_INTENT_FILLIN, Intent.class);
String actionType = intent.getStringExtra(EXTRA_ACTION_TYPE);
if (DEBUG_ACTIONS) {
Log.d(TAG, "Executing smart action [" + actionType + "]:" + pendingIntent.getIntent());
@@ -54,7 +57,7 @@ public class SmartActionsReceiver extends BroadcastReceiver {
ActivityOptions opts = ActivityOptions.makeBasic();
try {
- pendingIntent.send(context, 0, null, null, null, null, opts.toBundle());
+ pendingIntent.send(context, 0, fillIn, null, null, null, opts.toBundle());
} catch (PendingIntent.CanceledException e) {
Log.e(TAG, "Pending intent canceled", e);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 127c415a339a..452fc3904c32 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -224,6 +224,8 @@ import com.android.systemui.util.Utils;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
+import kotlin.Unit;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
@@ -234,7 +236,6 @@ import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Provider;
-import kotlin.Unit;
import kotlinx.coroutines.CoroutineDispatcher;
@CentralSurfacesComponent.CentralSurfacesScope
@@ -939,10 +940,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@Override
public void onUnlockAnimationStarted(
boolean playingCannedAnimation,
- boolean isWakeAndUnlock,
+ boolean isWakeAndUnlockNotFromDream,
long startDelay,
long unlockAnimationDuration) {
- unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
+ unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlockNotFromDream,
+ startDelay);
}
});
mAlternateBouncerInteractor = alternateBouncerInteractor;
@@ -957,7 +959,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
private void unlockAnimationStarted(
boolean playingCannedAnimation,
- boolean isWakeAndUnlock,
+ boolean isWakeAndUnlockNotFromDream,
long unlockAnimationStartDelay) {
// Disable blurs while we're unlocking so that panel expansion does not
// cause blurring. This will eventually be re-enabled by the panel view on
@@ -965,7 +967,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
// unlock gesture, and we don't want that to cause blurring either.
mDepthController.setBlursDisabledForUnlock(mTracking);
- if (playingCannedAnimation && !isWakeAndUnlock) {
+ if (playingCannedAnimation && !isWakeAndUnlockNotFromDream) {
// Hide the panel so it's not in the way or the surface behind the
// keyguard, which will be appearing. If we're wake and unlocking, the
// lock screen is hidden instantly so should not be flung away.
@@ -2016,6 +2018,8 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
}
updateExpansionAndVisibility();
mNotificationStackScrollLayoutController.setPanelFlinging(false);
+ // expandImmediate should be always reset at the end of animation
+ mQsController.setExpandImmediate(false);
}
private boolean isInContentBounds(float x, float y) {
@@ -3454,6 +3458,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump
@VisibleForTesting
void notifyExpandingStarted() {
if (!mExpanding) {
+ DejankUtils.notifyRendererOfExpensiveFrame(mView, "notifyExpandingStarted");
mExpanding = true;
mIsExpandingOrCollapsing = true;
mQsController.onExpandingStarted(mQsController.getFullyExpanded());
diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
index c42c2f4fa15a..6480164cdaf5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java
@@ -63,6 +63,7 @@ import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.FaceAuthApiRequestReason;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.DejankUtils;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.classifier.Classifier;
@@ -958,6 +959,7 @@ public class QuickSettingsController implements Dumpable {
// TODO (b/265193930): remove dependency on NPVC
mPanelViewControllerLazy.get().cancelHeightAnimator();
// end
+ DejankUtils.notifyRendererOfExpensiveFrame(mPanelView, "onExpansionStarted");
// Reset scroll position and apply that position to the expanded height.
float height = mExpansionHeight;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index d0a3cbbf0b02..29c4acc792ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -38,23 +38,38 @@ public interface ShadeController {
/** Collapse the shade instantly with no animation. */
void instantCollapseShade();
- /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
void animateCollapseShade();
- /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
void animateCollapseShade(int flags);
- /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
+ /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
void animateCollapseShadeForced();
- /** See {@link #animateCollapsePanels(int, boolean, boolean, float)}. */
- void animateCollapseShadeDelayed();
+ /** See {@link #animateCollapseShade(int, boolean, boolean, float)}. */
+ void animateCollapseShadeForcedDelayed();
/**
* Collapse the shade animated, showing the bouncer when on {@link StatusBarState#KEYGUARD} or
* dismissing status bar when on {@link StatusBarState#SHADE}.
*/
- void animateCollapsePanels(int flags, boolean force, boolean delayed, float speedUpFactor);
+ void animateCollapseShade(int flags, boolean force, boolean delayed, float speedUpFactor);
+
+ /** Expand the shade with an animation. */
+ void animateExpandShade();
+
+ /** Expand the shade with quick settings expanded with an animation. */
+ void animateExpandQs();
+
+ /** Posts a request to collapse the shade. */
+ void postAnimateCollapseShade();
+
+ /** Posts a request to force collapse the shade. */
+ void postAnimateForceCollapseShade();
+
+ /** Posts a request to expand the shade to quick settings. */
+ void postAnimateExpandQs();
/**
* If the shade is not fully expanded, collapse it animated.
@@ -115,6 +130,9 @@ public interface ShadeController {
*/
void collapseShade(boolean animate);
+ /** Calls #collapseShade if already on the main thread. If not, posts a call to it. */
+ void collapseOnMainThread();
+
/** Makes shade expanded but not visible. */
void makeExpandedInvisible();
@@ -127,8 +145,11 @@ public interface ShadeController {
/** Handle status bar touch event. */
void onStatusBarTouch(MotionEvent event);
- /** Called when the shade finishes collapsing. */
- void onClosingFinished();
+ /** Called when a launch animation was cancelled. */
+ void onLaunchAnimationCancelled(boolean isLaunchForActivity);
+
+ /** Called when a launch animation ends. */
+ void onLaunchAnimationEnd(boolean launchIsFullScreen);
/** Sets the listener for when the visibility of the shade changes. */
void setVisibilityListener(ShadeVisibilityListener listener);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index d00dab633014..7942b588866a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -17,6 +17,7 @@
package com.android.systemui.shade;
import android.content.ComponentCallbacks2;
+import android.os.Looper;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewTreeObserver;
@@ -25,6 +26,7 @@ import android.view.WindowManagerGlobal;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -32,12 +34,14 @@ import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import dagger.Lazy;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -51,11 +55,13 @@ public final class ShadeControllerImpl implements ShadeController {
private final int mDisplayId;
private final CommandQueue mCommandQueue;
+ private final Executor mMainExecutor;
private final KeyguardStateController mKeyguardStateController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final StatusBarStateController mStatusBarStateController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final StatusBarWindowController mStatusBarWindowController;
+ private final DeviceProvisionedController mDeviceProvisionedController;
private final Lazy<AssistManager> mAssistManagerLazy;
private final Lazy<NotificationGutsManager> mGutsManager;
@@ -72,18 +78,22 @@ public final class ShadeControllerImpl implements ShadeController {
@Inject
public ShadeControllerImpl(
CommandQueue commandQueue,
+ @Main Executor mainExecutor,
KeyguardStateController keyguardStateController,
StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
StatusBarWindowController statusBarWindowController,
+ DeviceProvisionedController deviceProvisionedController,
NotificationShadeWindowController notificationShadeWindowController,
WindowManager windowManager,
Lazy<AssistManager> assistManagerLazy,
Lazy<NotificationGutsManager> gutsManager
) {
mCommandQueue = commandQueue;
+ mMainExecutor = mainExecutor;
mStatusBarStateController = statusBarStateController;
mStatusBarWindowController = statusBarWindowController;
+ mDeviceProvisionedController = deviceProvisionedController;
mGutsManager = gutsManager;
mNotificationShadeWindowController = notificationShadeWindowController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
@@ -107,21 +117,21 @@ public final class ShadeControllerImpl implements ShadeController {
@Override
public void animateCollapseShade(int flags) {
- animateCollapsePanels(flags, false, false, 1.0f);
+ animateCollapseShade(flags, false, false, 1.0f);
}
@Override
public void animateCollapseShadeForced() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true, false, 1.0f);
}
@Override
- public void animateCollapseShadeDelayed() {
- animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
+ public void animateCollapseShadeForcedDelayed() {
+ animateCollapseShade(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true, true, 1.0f);
}
@Override
- public void animateCollapsePanels(int flags, boolean force, boolean delayed,
+ public void animateCollapseShade(int flags, boolean force, boolean delayed,
float speedUpFactor) {
if (!force && mStatusBarStateController.getState() != StatusBarState.SHADE) {
runPostCollapseRunnables();
@@ -143,6 +153,25 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
+ public void animateExpandShade() {
+ if (!mCommandQueue.panelsEnabled()) {
+ return;
+ }
+ mNotificationPanelViewController.expandToNotifications();
+ }
+
+ @Override
+ public void animateExpandQs() {
+ if (!mCommandQueue.panelsEnabled()) {
+ return;
+ }
+ // Settings are not available in setup
+ if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
+
+ mNotificationPanelViewController.expandToQs();
+ }
+
+ @Override
public boolean closeShadeIfOpen() {
if (!mNotificationPanelViewController.isFullyCollapsed()) {
mCommandQueue.animateCollapsePanels(
@@ -167,6 +196,20 @@ public final class ShadeControllerImpl implements ShadeController {
public boolean isExpandingOrCollapsing() {
return mNotificationPanelViewController.isExpandingOrCollapsing();
}
+ @Override
+ public void postAnimateCollapseShade() {
+ mMainExecutor.execute(this::animateCollapseShade);
+ }
+
+ @Override
+ public void postAnimateForceCollapseShade() {
+ mMainExecutor.execute(this::animateCollapseShadeForced);
+ }
+
+ @Override
+ public void postAnimateExpandQs() {
+ mMainExecutor.execute(this::animateExpandQs);
+ }
@Override
public void postOnShadeExpanded(Runnable executable) {
@@ -202,7 +245,7 @@ public final class ShadeControllerImpl implements ShadeController {
public boolean collapseShade() {
if (!mNotificationPanelViewController.isFullyCollapsed()) {
// close the shade if it was open
- animateCollapseShadeDelayed();
+ animateCollapseShadeForcedDelayed();
notifyVisibilityChanged(false);
return true;
@@ -227,6 +270,15 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
+ public void collapseOnMainThread() {
+ if (Looper.getMainLooper().isCurrentThread()) {
+ collapseShade();
+ } else {
+ mMainExecutor.execute(this::collapseShade);
+ }
+ }
+
+ @Override
public void onStatusBarTouch(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
if (mExpandedVisible) {
@@ -235,8 +287,7 @@ public final class ShadeControllerImpl implements ShadeController {
}
}
- @Override
- public void onClosingFinished() {
+ private void onClosingFinished() {
runPostCollapseRunnables();
if (!mPresenter.isPresenterFullyCollapsed()) {
// if we set it not to be focusable when collapsing, we have to undo it when we aborted
@@ -246,6 +297,27 @@ public final class ShadeControllerImpl implements ShadeController {
}
@Override
+ public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
+ if (mPresenter.isPresenterFullyCollapsed()
+ && !mPresenter.isCollapsing()
+ && isLaunchForActivity) {
+ onClosingFinished();
+ } else {
+ collapseShade(true /* animate */);
+ }
+ }
+
+ @Override
+ public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
+ if (!mPresenter.isCollapsing()) {
+ onClosingFinished();
+ }
+ if (launchIsFullScreen) {
+ instantCollapseShade();
+ }
+ }
+
+ @Override
public void instantCollapseShade() {
mNotificationPanelViewController.instantCollapse();
runPostCollapseRunnables();
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index 641131e4dcc1..03be88fc31d9 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -34,6 +34,11 @@ abstract class SmartspaceModule {
const val DREAM_SMARTSPACE_DATA_PLUGIN = "dreams_smartspace_data_plugin"
/**
+ * The BcSmartspaceDataPlugin for the standalone weather on dream.
+ */
+ const val DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN = "dream_weather_smartspace_data_plugin"
+
+ /**
* The dream smartspace target filter.
*/
const val DREAM_SMARTSPACE_TARGET_FILTER = "dream_smartspace_target_filter"
@@ -62,6 +67,10 @@ abstract class SmartspaceModule {
@Named(DREAM_SMARTSPACE_DATA_PLUGIN)
abstract fun optionalDreamsBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
+ @BindsOptionalOf
+ @Named(DREAM_WEATHER_SMARTSPACE_DATA_PLUGIN)
+ abstract fun optionalDreamWeatherSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
+
@Binds
@Named(DREAM_SMARTSPACE_PRECONDITION)
abstract fun bindSmartspacePrecondition(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
index ae4e19550f8c..a0cbdf3e3e01 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BlurUtils.kt
@@ -83,7 +83,8 @@ open class BlurUtils @Inject constructor(
return
}
if (lastAppliedBlur == 0 && radius != 0) {
- Trace.asyncTraceForTrackBegin(TRACE_TAG_APP, TRACK_NAME, EARLY_WAKEUP_SLICE_NAME, 0)
+ Trace.asyncTraceForTrackBegin(
+ TRACE_TAG_APP, TRACK_NAME, "eEarlyWakeup (prepareBlur)", 0)
earlyWakeupEnabled = true
createTransaction().use {
it.setEarlyWakeupStart()
@@ -110,7 +111,7 @@ open class BlurUtils @Inject constructor(
Trace.asyncTraceForTrackBegin(
TRACE_TAG_APP,
TRACK_NAME,
- EARLY_WAKEUP_SLICE_NAME,
+ "eEarlyWakeup (applyBlur)",
0
)
it.setEarlyWakeupStart()
@@ -159,6 +160,5 @@ open class BlurUtils @Inject constructor(
companion object {
const val TRACK_NAME = "BlurUtils"
- const val EARLY_WAKEUP_SLICE_NAME = "eEarlyWakeup"
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
index 06f43f1eeaa5..39181449aaa0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java
@@ -56,7 +56,6 @@ import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.EditText;
@@ -337,6 +336,12 @@ public final class KeyboardShortcutListSearch {
mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
mModifierNames.put(KeyEvent.META_META_ON, "Meta");
mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
@@ -411,27 +416,45 @@ public final class KeyboardShortcutListSearch {
mKeyCharacterMap = mBackupKeyCharacterMap;
}
+ private boolean mAppShortcutsReceived;
+ private boolean mImeShortcutsReceived;
+
@VisibleForTesting
void showKeyboardShortcuts(int deviceId) {
retrieveKeyCharacterMap(deviceId);
- mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
- @Override
- public void onKeyboardShortcutsReceived(
- final List<KeyboardShortcutGroup> result) {
- // Add specific app shortcuts
- if (result.isEmpty()) {
- mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
- } else {
- mSpecificAppGroup = reMapToKeyboardShortcutMultiMappingGroup(result);
- mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
- }
- mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
- mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
- mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
- mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
- showKeyboardShortcutSearchList(mFullShortsGroup);
+ mAppShortcutsReceived = false;
+ mImeShortcutsReceived = false;
+ mWindowManager.requestAppKeyboardShortcuts(result -> {
+ // Add specific app shortcuts
+ if (result.isEmpty()) {
+ mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false);
+ } else {
+ mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
+ mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true);
+ }
+ mAppShortcutsReceived = true;
+ if (mImeShortcutsReceived) {
+ mergeAndShowKeyboardShortcutsGroups();
}
}, deviceId);
+ mWindowManager.requestImeKeyboardShortcuts(result -> {
+ // Add specific Ime shortcuts
+ if (!result.isEmpty()) {
+ mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result));
+ }
+ mImeShortcutsReceived = true;
+ if (mAppShortcutsReceived) {
+ mergeAndShowKeyboardShortcutsGroups();
+ }
+ }, deviceId);
+ }
+
+ private void mergeAndShowKeyboardShortcutsGroups() {
+ mFullShortsGroup.add(SHORTCUT_SYSTEM_INDEX, mSystemGroup);
+ mFullShortsGroup.add(SHORTCUT_INPUT_INDEX, mInputGroup);
+ mFullShortsGroup.add(SHORTCUT_OPENAPPS_INDEX, mOpenAppsGroup);
+ mFullShortsGroup.add(SHORTCUT_SPECIFICAPP_INDEX, mSpecificAppGroup);
+ showKeyboardShortcutSearchList(mFullShortsGroup);
}
// The original data structure is only for 1-to-1 shortcut mapping, so remap the old
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
index 43fbc7cbae03..a3fd82e9b140 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -56,7 +56,6 @@ import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.WindowManager.KeyboardShortcutsReceiver;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -129,6 +128,9 @@ public final class KeyboardShortcuts {
private KeyCharacterMap mKeyCharacterMap;
private KeyCharacterMap mBackupKeyCharacterMap;
+ @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null;
+ @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null;
+
@VisibleForTesting
KeyboardShortcuts(Context context, WindowManager windowManager) {
this.mContext = new ContextThemeWrapper(
@@ -324,6 +326,12 @@ public final class KeyboardShortcuts {
mSpecialCharacterNames.put(KeyEvent.KEYCODE_MUHENKAN, "無変換");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_HENKAN, "変換");
mSpecialCharacterNames.put(KeyEvent.KEYCODE_KATAKANA_HIRAGANA, "かな");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_LEFT, "Alt");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_ALT_RIGHT, "Alt");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_LEFT, "Ctrl");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_CTRL_RIGHT, "Ctrl");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_LEFT, "Shift");
+ mSpecialCharacterNames.put(KeyEvent.KEYCODE_SHIFT_RIGHT, "Shift");
mModifierNames.put(KeyEvent.META_META_ON, "Meta");
mModifierNames.put(KeyEvent.META_CTRL_ON, "Ctrl");
@@ -382,18 +390,36 @@ public final class KeyboardShortcuts {
@VisibleForTesting
void showKeyboardShortcuts(int deviceId) {
retrieveKeyCharacterMap(deviceId);
- mWindowManager.requestAppKeyboardShortcuts(new KeyboardShortcutsReceiver() {
- @Override
- public void onKeyboardShortcutsReceived(
- final List<KeyboardShortcutGroup> result) {
- result.add(getSystemShortcuts());
- final KeyboardShortcutGroup appShortcuts = getDefaultApplicationShortcuts();
- if (appShortcuts != null) {
- result.add(appShortcuts);
- }
- showKeyboardShortcutsDialog(result);
- }
- }, deviceId);
+ mReceivedAppShortcutGroups = null;
+ mReceivedImeShortcutGroups = null;
+ mWindowManager.requestAppKeyboardShortcuts(
+ result -> {
+ mReceivedAppShortcutGroups = result;
+ maybeMergeAndShowKeyboardShortcuts();
+ }, deviceId);
+ mWindowManager.requestImeKeyboardShortcuts(
+ result -> {
+ mReceivedImeShortcutGroups = result;
+ maybeMergeAndShowKeyboardShortcuts();
+ }, deviceId);
+ }
+
+ private void maybeMergeAndShowKeyboardShortcuts() {
+ if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) {
+ return;
+ }
+ List<KeyboardShortcutGroup> shortcutGroups = mReceivedAppShortcutGroups;
+ shortcutGroups.addAll(mReceivedImeShortcutGroups);
+ mReceivedAppShortcutGroups = null;
+ mReceivedImeShortcutGroups = null;
+
+ final KeyboardShortcutGroup defaultAppShortcuts =
+ getDefaultApplicationShortcuts();
+ if (defaultAppShortcuts != null) {
+ shortcutGroups.add(defaultAppShortcuts);
+ }
+ shortcutGroups.add(getSystemShortcuts());
+ showKeyboardShortcutsDialog(shortcutGroups);
}
private void dismissKeyboardShortcuts() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
index 0a18f2d89d87..56ea703668d0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImpl.kt
@@ -188,7 +188,9 @@ constructor(
if (animationState.value == ANIMATING_OUT) {
coroutineScope.launch {
withTimeout(DISAPPEAR_ANIMATION_DURATION) {
- animationState.first { it == SHOWING_PERSISTENT_DOT || it == ANIMATION_QUEUED }
+ animationState.first {
+ it == SHOWING_PERSISTENT_DOT || it == IDLE || it == ANIMATION_QUEUED
+ }
notifyHidePersistentDot()
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index 518825cea5e0..cf3903860e94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -137,6 +137,19 @@ constructor(
updateTextColorFromWallpaper()
statusBarStateListener.onDozeAmountChanged(0f, statusBarStateController.dozeAmount)
+
+ if (regionSamplingEnabled && (!regionSamplers.containsKey(v))) {
+ var regionSampler = RegionSampler(
+ v as View,
+ uiExecutor,
+ bgExecutor,
+ regionSamplingEnabled,
+ isLockscreen = true,
+ ) { updateTextColorFromRegionSampler() }
+ initializeTextColors(regionSampler)
+ regionSamplers[v] = regionSampler
+ regionSampler.startRegionSampler()
+ }
}
override fun onViewDetachedFromWindow(v: View) {
@@ -171,23 +184,6 @@ constructor(
val filteredTargets = targets.filter(::filterSmartspaceTarget)
plugin?.onTargetsAvailable(filteredTargets)
- if (!isRegionSamplersCreated) {
- for (v in smartspaceViews) {
- if (regionSamplingEnabled) {
- var regionSampler = RegionSampler(
- v as View,
- uiExecutor,
- bgExecutor,
- regionSamplingEnabled,
- isLockscreen = true,
- ) { updateTextColorFromRegionSampler() }
- initializeTextColors(regionSampler)
- regionSamplers[v] = regionSampler
- regionSampler.startRegionSampler()
- }
- }
- isRegionSamplersCreated = true
- }
}
private val userTrackerCallback = object : UserTracker.Callback {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
index 2fa070ca20b5..706594cee81d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt
@@ -28,12 +28,9 @@ import com.android.systemui.dump.DumpManager
import com.android.systemui.keyguard.data.repository.KeyguardRepository
import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
import com.android.systemui.statusbar.expansionChanges
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
@@ -50,30 +47,29 @@ import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
import java.io.PrintWriter
import javax.inject.Inject
-import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.emitAll
-import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.onStart
-import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.yield
/**
* Filters low priority and privacy-sensitive notifications from the lockscreen, and hides section
- * headers on the lockscreen.
+ * headers on the lockscreen. If enabled, it will also track and hide seen notifications on the
+ * lockscreen.
*/
@CoordinatorScope
class KeyguardCoordinator
@@ -86,7 +82,6 @@ constructor(
private val keyguardRepository: KeyguardRepository,
private val keyguardTransitionRepository: KeyguardTransitionRepository,
private val logger: KeyguardCoordinatorLogger,
- private val notifPipelineFlags: NotifPipelineFlags,
@Application private val scope: CoroutineScope,
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider,
private val secureSettings: SecureSettings,
@@ -95,6 +90,8 @@ constructor(
) : Coordinator, Dumpable {
private val unseenNotifications = mutableSetOf<NotificationEntry>()
+ private val unseenEntryAdded = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
+ private val unseenEntryRemoved = MutableSharedFlow<NotificationEntry>(extraBufferCapacity = 1)
private var unseenFilterEnabled = false
override fun attach(pipeline: NotifPipeline) {
@@ -109,79 +106,131 @@ constructor(
private fun attachUnseenFilter(pipeline: NotifPipeline) {
pipeline.addFinalizeFilter(unseenNotifFilter)
pipeline.addCollectionListener(collectionListener)
- scope.launch { trackUnseenNotificationsWhileUnlocked() }
+ scope.launch { trackSeenNotifications() }
scope.launch { invalidateWhenUnseenSettingChanges() }
dumpManager.registerDumpable(this)
}
- private suspend fun trackUnseenNotificationsWhileUnlocked() {
- // Whether or not we're actively tracking unseen notifications to mark them as seen when
- // appropriate.
- val isTrackingUnseen: Flow<Boolean> =
- keyguardRepository.isKeyguardShowing
- // transformLatest so that we can cancel listening to keyguard transitions once
- // isKeyguardShowing changes (after a successful transition to the keyguard).
- .transformLatest { isShowing ->
- if (isShowing) {
- // If the keyguard is showing, we're not tracking unseen.
- emit(false)
- } else {
- // If the keyguard stops showing, then start tracking unseen notifications.
- emit(true)
- // If the screen is turning off, stop tracking, but if that transition is
- // cancelled, then start again.
- emitAll(
- keyguardTransitionRepository.transitions.map { step ->
- !step.isScreenTurningOff
- }
- )
- }
- }
- // Prevent double emit of `false` caused by transition to AOD, followed by keyguard
- // showing
+ private suspend fun trackSeenNotifications() {
+ // Whether or not keyguard is visible (or occluded).
+ val isKeyguardPresent: Flow<Boolean> =
+ keyguardTransitionRepository.transitions
+ .map { step -> step.to != KeyguardState.GONE }
.distinctUntilChanged()
.onEach { trackingUnseen -> logger.logTrackingUnseen(trackingUnseen) }
- // Use collectLatest so that trackUnseenNotifications() is cancelled when the keyguard is
- // showing again
- var clearUnseenOnBeginTracking = false
- isTrackingUnseen.collectLatest { trackingUnseen ->
- if (!trackingUnseen) {
- // Wait for the user to spend enough time on the lock screen before clearing unseen
- // set when unlocked
- awaitTimeSpentNotDozing(SEEN_TIMEOUT)
- clearUnseenOnBeginTracking = true
- logger.logSeenOnLockscreen()
+ // Separately track seen notifications while the device is locked, applying once the device
+ // is unlocked.
+ val notificationsSeenWhileLocked = mutableSetOf<NotificationEntry>()
+
+ // Use [collectLatest] to cancel any running jobs when [trackingUnseen] changes.
+ isKeyguardPresent.collectLatest { isKeyguardPresent: Boolean ->
+ if (isKeyguardPresent) {
+ // Keyguard is not gone, notifications need to be visible for a certain threshold
+ // before being marked as seen
+ trackSeenNotificationsWhileLocked(notificationsSeenWhileLocked)
} else {
- if (clearUnseenOnBeginTracking) {
- clearUnseenOnBeginTracking = false
- logger.logAllMarkedSeenOnUnlock()
- unseenNotifications.clear()
+ // Mark all seen-while-locked notifications as seen for real.
+ if (notificationsSeenWhileLocked.isNotEmpty()) {
+ unseenNotifications.removeAll(notificationsSeenWhileLocked)
+ logger.logAllMarkedSeenOnUnlock(
+ seenCount = notificationsSeenWhileLocked.size,
+ remainingUnseenCount = unseenNotifications.size
+ )
+ notificationsSeenWhileLocked.clear()
}
unseenNotifFilter.invalidateList("keyguard no longer showing")
- trackUnseenNotifications()
+ // Keyguard is gone, notifications can be immediately marked as seen when they
+ // become visible.
+ trackSeenNotificationsWhileUnlocked()
}
}
}
- private suspend fun awaitTimeSpentNotDozing(duration: Duration) {
- keyguardRepository.isDozing
- // Use transformLatest so that the timeout delay is cancelled if the device enters doze,
- // and is restarted when doze ends.
- .transformLatest { isDozing ->
- if (!isDozing) {
- delay(duration)
- // Signal timeout has completed
- emit(Unit)
+ /**
+ * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+ * been "seen" while the device is on the keyguard.
+ */
+ private suspend fun trackSeenNotificationsWhileLocked(
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>,
+ ) = coroutineScope {
+ // Remove removed notifications from the set
+ launch {
+ unseenEntryRemoved.collect { entry ->
+ if (notificationsSeenWhileLocked.remove(entry)) {
+ logger.logRemoveSeenOnLockscreen(entry)
}
}
- // Suspend until the first emission
- .first()
+ }
+ // Use collectLatest so that the timeout delay is cancelled if the device enters doze, and
+ // is restarted when doze ends.
+ keyguardRepository.isDozing.collectLatest { isDozing ->
+ if (!isDozing) {
+ trackSeenNotificationsWhileLockedAndNotDozing(notificationsSeenWhileLocked)
+ }
+ }
}
- // Track "unseen" notifications, marking them as seen when either shade is expanded or the
+ /**
+ * Keep [notificationsSeenWhileLocked] updated to represent which notifications have actually
+ * been "seen" while the device is on the keyguard and not dozing. Any new and existing unseen
+ * notifications are not marked as seen until they are visible for the [SEEN_TIMEOUT] duration.
+ */
+ private suspend fun trackSeenNotificationsWhileLockedAndNotDozing(
+ notificationsSeenWhileLocked: MutableSet<NotificationEntry>
+ ) = coroutineScope {
+ // All child tracking jobs will be cancelled automatically when this is cancelled.
+ val trackingJobsByEntry = mutableMapOf<NotificationEntry, Job>()
+
+ /**
+ * Wait for the user to spend enough time on the lock screen before removing notification
+ * from unseen set upon unlock.
+ */
+ suspend fun trackSeenDurationThreshold(entry: NotificationEntry) {
+ if (notificationsSeenWhileLocked.remove(entry)) {
+ logger.logResetSeenOnLockscreen(entry)
+ }
+ delay(SEEN_TIMEOUT)
+ notificationsSeenWhileLocked.add(entry)
+ trackingJobsByEntry.remove(entry)
+ logger.logSeenOnLockscreen(entry)
+ }
+
+ /** Stop any unseen tracking when a notification is removed. */
+ suspend fun stopTrackingRemovedNotifs(): Nothing =
+ unseenEntryRemoved.collect { entry ->
+ trackingJobsByEntry.remove(entry)?.let {
+ it.cancel()
+ logger.logStopTrackingLockscreenSeenDuration(entry)
+ }
+ }
+
+ /** Start tracking new notifications when they are posted. */
+ suspend fun trackNewUnseenNotifs(): Nothing = coroutineScope {
+ unseenEntryAdded.collect { entry ->
+ logger.logTrackingLockscreenSeenDuration(entry)
+ // If this is an update, reset the tracking.
+ trackingJobsByEntry[entry]?.let {
+ it.cancel()
+ logger.logResetSeenOnLockscreen(entry)
+ }
+ trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+ }
+ }
+
+ // Start tracking for all notifications that are currently unseen.
+ logger.logTrackingLockscreenSeenDuration(unseenNotifications)
+ unseenNotifications.forEach { entry ->
+ trackingJobsByEntry[entry] = launch { trackSeenDurationThreshold(entry) }
+ }
+
+ launch { trackNewUnseenNotifs() }
+ launch { stopTrackingRemovedNotifs() }
+ }
+
+ // Track "seen" notifications, marking them as such when either shade is expanded or the
// notification becomes heads up.
- private suspend fun trackUnseenNotifications() {
+ private suspend fun trackSeenNotificationsWhileUnlocked() {
coroutineScope {
launch { clearUnseenNotificationsWhenShadeIsExpanded() }
launch { markHeadsUpNotificationsAsSeen() }
@@ -250,6 +299,7 @@ constructor(
) {
logger.logUnseenAdded(entry.key)
unseenNotifications.add(entry)
+ unseenEntryAdded.tryEmit(entry)
}
}
@@ -259,12 +309,14 @@ constructor(
) {
logger.logUnseenUpdated(entry.key)
unseenNotifications.add(entry)
+ unseenEntryAdded.tryEmit(entry)
}
}
override fun onEntryRemoved(entry: NotificationEntry, reason: Int) {
if (unseenNotifications.remove(entry)) {
logger.logUnseenRemoved(entry.key)
+ unseenEntryRemoved.tryEmit(entry)
}
}
}
@@ -347,6 +399,3 @@ constructor(
private val SEEN_TIMEOUT = 5.seconds
}
}
-
-private val TransitionStep.isScreenTurningOff: Boolean
- get() = transitionState == TransitionState.STARTED && to != KeyguardState.GONE
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
index 1f8ec3411bcd..c61281661717 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorLogger.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.UnseenNotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
import javax.inject.Inject
private const val TAG = "KeyguardCoordinator"
@@ -28,11 +29,14 @@ class KeyguardCoordinatorLogger
constructor(
@UnseenNotificationLog private val buffer: LogBuffer,
) {
- fun logSeenOnLockscreen() =
+ fun logSeenOnLockscreen(entry: NotificationEntry) =
buffer.log(
TAG,
LogLevel.DEBUG,
- "Notifications on lockscreen will be marked as seen when unlocked."
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Notification [$str1] on lockscreen will be marked as seen when unlocked."
+ },
)
fun logTrackingUnseen(trackingUnseen: Boolean) =
@@ -43,11 +47,21 @@ constructor(
messagePrinter = { "${if (bool1) "Start" else "Stop"} tracking unseen notifications." },
)
- fun logAllMarkedSeenOnUnlock() =
+ fun logAllMarkedSeenOnUnlock(
+ seenCount: Int,
+ remainingUnseenCount: Int,
+ ) =
buffer.log(
TAG,
LogLevel.DEBUG,
- "Notifications have been marked as seen now that device is unlocked."
+ messageInitializer = {
+ int1 = seenCount
+ int2 = remainingUnseenCount
+ },
+ messagePrinter = {
+ "$int1 Notifications have been marked as seen now that device is unlocked. " +
+ "$int2 notifications remain unseen."
+ },
)
fun logShadeExpanded() =
@@ -96,4 +110,60 @@ constructor(
messageInitializer = { str1 = key },
messagePrinter = { "Unseen notif has become heads up: $str1" },
)
+
+ fun logTrackingLockscreenSeenDuration(unseenNotifications: Set<NotificationEntry>) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = {
+ str1 = unseenNotifications.joinToString { it.key }
+ int1 = unseenNotifications.size
+ },
+ messagePrinter = {
+ "Tracking $int1 unseen notifications for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Tracking new notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logStopTrackingLockscreenSeenDuration(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Stop tracking removed notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logResetSeenOnLockscreen(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = {
+ "Reset tracking updated notification for lockscreen seen duration threshold: $str1"
+ },
+ )
+ }
+
+ fun logRemoveSeenOnLockscreen(entry: NotificationEntry) {
+ buffer.log(
+ TAG,
+ LogLevel.DEBUG,
+ messageInitializer = { str1 = entry.key },
+ messagePrinter = { "Notification marked as seen on lockscreen removed: $str1" },
+ )
+ }
}
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 bfb6fb0fcdc7..9f397fe9ac0c 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
@@ -556,6 +556,11 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void onNotificationUpdated() {
+ if (mIsSummaryWithChildren) {
+ Trace.beginSection("ExpNotRow#onNotifUpdated (summary)");
+ } else {
+ Trace.beginSection("ExpNotRow#onNotifUpdated (leaf)");
+ }
for (NotificationContentView l : mLayouts) {
l.onNotificationUpdated(mEntry);
}
@@ -591,6 +596,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mUpdateSelfBackgroundOnUpdate = false;
updateBackgroundColorsOfSelf();
}
+ Trace.endSection();
}
private void updateBackgroundColorsOfSelf() {
@@ -2588,6 +2594,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
mIsSummaryWithChildren = mChildrenContainer != null
&& mChildrenContainer.getNotificationChildCount() > 0;
if (mIsSummaryWithChildren) {
+ Trace.beginSection("ExpNotRow#onChildCountChanged (summary)");
NotificationViewWrapper wrapper = mChildrenContainer.getNotificationViewWrapper();
if (wrapper == null || wrapper.getNotificationHeader() == null) {
mChildrenContainer.recreateNotificationHeader(mExpandClickListener,
@@ -2599,6 +2606,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateChildrenAppearance();
updateChildrenVisibility();
applyChildrenRoundness();
+ if (mIsSummaryWithChildren) {
+ Trace.endSection();
+ }
}
protected void expandNotification() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
index 7a2bee91e972..b95018777fea 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragController.java
@@ -148,7 +148,7 @@ public class ExpandableNotificationRowDragController {
private void dismissShade() {
// Speed up dismissing the shade since the drag needs to be handled by
// the shell layer underneath
- mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
+ mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
false /* delayed */, 1.1f /* speedUpFactor */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
index 77fd05186090..3e10f2ad52e5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java
@@ -22,6 +22,7 @@ import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -57,6 +58,7 @@ public class HybridGroupManager {
}
private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) {
+ Trace.beginSection("HybridGroupManager#inflateHybridView");
LayoutInflater inflater = LayoutInflater.from(mContext);
int layout = contentView instanceof ConversationLayout
? R.layout.hybrid_conversation_notification
@@ -64,6 +66,7 @@ public class HybridGroupManager {
HybridNotificationView hybrid = (HybridNotificationView)
inflater.inflate(layout, parent, false);
parent.addView(hybrid);
+ Trace.endSection();
return hybrid;
}
@@ -90,12 +93,18 @@ public class HybridGroupManager {
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
View contentView, StatusBarNotification notification,
ViewGroup parent) {
+ boolean isNewView = false;
if (reusableView == null) {
+ Trace.beginSection("HybridGroupManager#bindFromNotification");
reusableView = inflateHybridView(contentView, parent);
+ isNewView = true;
}
CharSequence titleText = resolveTitle(notification.getNotification());
CharSequence contentText = resolveText(notification.getNotification());
reusableView.bind(titleText, contentText, contentView);
+ if (isNewView) {
+ Trace.endSection();
+ }
return reusableView;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 451d837b63a0..124df8c3b815 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -26,6 +26,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.RemoteException;
+import android.os.Trace;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.AttributeSet;
@@ -1193,6 +1194,7 @@ public class NotificationContentView extends FrameLayout implements Notification
private void updateSingleLineView() {
if (mIsChildInGroup) {
+ Trace.beginSection("NotifContentView#updateSingleLineView");
boolean isNewView = mSingleLineView == null;
mSingleLineView = mHybridGroupManager.bindFromNotification(
mSingleLineView, mContractedChild, mNotificationEntry.getSbn(), this);
@@ -1200,6 +1202,7 @@ public class NotificationContentView extends FrameLayout implements Notification
updateViewVisibility(mVisibleType, VISIBLE_TYPE_SINGLELINE,
mSingleLineView, mSingleLineView);
}
+ Trace.endSection();
} else if (mSingleLineView != null) {
removeView(mSingleLineView);
mSingleLineView = null;
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 d18757d3453f..f8e374de11e2 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
@@ -379,6 +379,7 @@ public class NotificationChildrenContainer extends ViewGroup
}
public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
+ Trace.beginSection("NotifChildCont#recreateHeader");
mHeaderClickListener = listener;
mIsConversation = isConversation;
StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
@@ -406,6 +407,7 @@ public class NotificationChildrenContainer extends ViewGroup
recreateLowPriorityHeader(builder, isConversation);
updateHeaderVisibility(false /* animate */);
updateChildrenAppearance();
+ Trace.endSection();
}
/**
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 cdc7cee9de5d..36025e8b8d2a 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
@@ -104,13 +104,13 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.notification.row.FooterView;
import com.android.systemui.statusbar.notification.row.StackScrollerDecorView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
@@ -315,7 +315,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
};
private NotificationStackScrollLogger mLogger;
- private CentralSurfaces mCentralSurfaces;
+ private NotificationsController mNotificationsController;
private ActivityStarter mActivityStarter;
private final int[] mTempInt2 = new int[2];
private boolean mGenerateChildOrderChangedEvent;
@@ -3989,7 +3989,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mAmbientState.setExpansionChanging(false);
if (!mIsExpanded) {
resetScrollPosition();
- mCentralSurfaces.resetUserExpandedStates();
+ mNotificationsController.resetUserExpandedStates();
clearTemporaryViews();
clearUserLockedViews();
cancelActiveSwipe();
@@ -4060,6 +4060,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (!mIsExpansionChanging) {
cancelActiveSwipe();
}
+ finalizeClearAllAnimation();
}
updateNotificationAnimationStates();
updateChronometers();
@@ -4155,20 +4156,30 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
runAnimationFinishedRunnables();
clearTransient();
clearHeadsUpDisappearRunning();
+ finalizeClearAllAnimation();
+ }
+ private void finalizeClearAllAnimation() {
if (mAmbientState.isClearAllInProgress()) {
setClearAllInProgress(false);
if (mShadeNeedsToClose) {
mShadeNeedsToClose = false;
- postDelayed(
- () -> {
- mShadeController.animateCollapseShade(CommandQueue.FLAG_EXCLUDE_NONE);
- },
- DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+ if (mIsExpanded) {
+ collapseShadeDelayed();
+ }
}
}
}
+ private void collapseShadeDelayed() {
+ postDelayed(
+ () -> {
+ mShadeController.animateCollapseShade(
+ CommandQueue.FLAG_EXCLUDE_NONE);
+ },
+ DELAY_BEFORE_SHADE_CLOSE /* delayMillis */);
+ }
+
private void clearHeadsUpDisappearRunning() {
for (int i = 0; i < getChildCount(); i++) {
View view = getChildAt(i);
@@ -4376,6 +4387,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
boolean nowHiddenAtAll = mAmbientState.isHiddenAtAll();
if (nowFullyHidden != wasFullyHidden) {
updateVisibility();
+ mSwipeHelper.resetTouchState();
}
if (!wasHiddenAtAll && nowHiddenAtAll) {
resetExposedMenuView(true /* animate */, true /* animate */);
@@ -4516,6 +4528,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mFooterView.setFooterLabelVisible(mHasFilteredOutSeenNotifications);
}
+ @VisibleForTesting
public void setClearAllInProgress(boolean clearAllInProgress) {
mClearAllInProgress = clearAllInProgress;
mAmbientState.setClearAllInProgress(clearAllInProgress);
@@ -4570,8 +4583,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
return max + getStackTranslation();
}
- public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
- this.mCentralSurfaces = centralSurfaces;
+ public void setNotificationsController(NotificationsController notificationsController) {
+ this.mNotificationsController = notificationsController;
}
public void setActivityStarter(ActivityStarter activityStarter) {
@@ -4669,6 +4682,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (v instanceof ExpandableNotificationRow && !mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
+ mController.updateImportantForAccessibility();
}
updateSpeedBumpIndex();
@@ -4680,6 +4694,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
+ mController.updateImportantForAccessibility();
}
updateSpeedBumpIndex();
@@ -4692,6 +4707,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (v instanceof ExpandableNotificationRow && mController.isShowingEmptyShadeView()) {
mController.updateShowEmptyShadeView();
updateFooter();
+ mController.updateImportantForAccessibility();
}
updateSpeedBumpIndex();
@@ -5132,7 +5148,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mHeadsUpAppearanceController = headsUpAppearanceController;
}
- private boolean isVisible(View child) {
+ @VisibleForTesting
+ public boolean isVisible(View child) {
boolean hasClipBounds = child.getClipBounds(mTmpRect);
return child.getVisibility() == View.VISIBLE
&& (!hasClipBounds || mTmpRect.height() > 0);
@@ -5831,7 +5848,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
private void cancelActiveSwipe() {
- mSwipeHelper.resetSwipeState();
+ mSwipeHelper.resetTouchState();
updateContinuousShadowDrawing();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index a52c84300b4d..a70862aead0f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -100,6 +100,7 @@ import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.SilentHeader;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -146,6 +147,7 @@ public class NotificationStackScrollLayoutController {
private final boolean mAllowLongPress;
private final NotificationGutsManager mNotificationGutsManager;
+ private final NotificationsController mNotificationsController;
private final NotificationVisibilityProvider mVisibilityProvider;
private final HeadsUpManagerPhone mHeadsUpManager;
private final NotificationRoundnessManager mNotificationRoundnessManager;
@@ -330,6 +332,7 @@ public class NotificationStackScrollLayoutController {
mView.updateSensitiveness(mStatusBarStateController.goingToFullShade(),
mLockscreenUserManager.isAnyProfilePublicMode());
mView.onStatePostChange(mStatusBarStateController.fromShadeLocked());
+ updateImportantForAccessibility();
}
};
@@ -431,7 +434,7 @@ public class NotificationStackScrollLayoutController {
@Override
public void onSnooze(StatusBarNotification sbn,
NotificationSwipeActionHelper.SnoozeOption snoozeOption) {
- mCentralSurfaces.setNotificationSnoozed(sbn, snoozeOption);
+ mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
}
@Override
@@ -616,6 +619,7 @@ public class NotificationStackScrollLayoutController {
NotificationStackScrollLayout view,
@Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
NotificationGutsManager notificationGutsManager,
+ NotificationsController notificationsController,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
NotificationRoundnessManager notificationRoundnessManager,
@@ -664,6 +668,7 @@ public class NotificationStackScrollLayoutController {
mLogger = logger;
mAllowLongPress = allowLongPress;
mNotificationGutsManager = notificationGutsManager;
+ mNotificationsController = notificationsController;
mVisibilityProvider = visibilityProvider;
mHeadsUpManager = headsUpManager;
mNotificationRoundnessManager = notificationRoundnessManager;
@@ -714,7 +719,7 @@ public class NotificationStackScrollLayoutController {
mView.setController(this);
mView.setLogger(mLogger);
mView.setTouchHandler(new TouchHandler());
- mView.setCentralSurfaces(mCentralSurfaces);
+ mView.setNotificationsController(mNotificationsController);
mView.setActivityStarter(mActivityStarter);
mView.setClearAllAnimationListener(this::onAnimationEnd);
mView.setClearAllListener((selection) -> mUiEventLogger.log(
@@ -1211,6 +1216,7 @@ public class NotificationStackScrollLayoutController {
if (mView.getVisibility() == View.VISIBLE) {
// Synchronize EmptyShadeView visibility with the parent container.
updateShowEmptyShadeView();
+ updateImportantForAccessibility();
}
}
@@ -1238,6 +1244,22 @@ public class NotificationStackScrollLayoutController {
}
/**
+ * Update the importantForAccessibility of NotificationStackScrollLayout.
+ * <p>
+ * We want the NSSL to be unimportant for accessibility when there's no
+ * notifications in it while the device is on lock screen, to avoid unlablel NSSL view.
+ * Otherwise, we want it to be important for accessibility to enable accessibility
+ * auto-scrolling in NSSL.
+ */
+ public void updateImportantForAccessibility() {
+ if (getVisibleNotificationCount() == 0 && mView.onKeyguard()) {
+ mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ } else {
+ mView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+ }
+
+ /**
* @return true if {@link StatusBarStateController} is in transition to the KEYGUARD
* and false otherwise.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
index 5ad5d841fad5..217d32c93c6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterImpl.kt
@@ -389,6 +389,35 @@ constructor(
}
/**
+ * Whether we should animate an activity launch.
+ *
+ * Note: This method must be called *before* dismissing the keyguard.
+ */
+ private fun shouldAnimateLaunch(
+ isActivityIntent: Boolean,
+ showOverLockscreen: Boolean,
+ ): Boolean {
+ // TODO(b/184121838): Support launch animations when occluded.
+ if (keyguardStateController.isOccluded) {
+ return false
+ }
+
+ // Always animate if we are not showing the keyguard or if we animate over the lockscreen
+ // (without unlocking it).
+ if (showOverLockscreen || !keyguardStateController.isShowing) {
+ return true
+ }
+
+ // We don't animate non-activity launches as they can break the animation.
+ // TODO(b/184121838): Support non activity launches on the lockscreen.
+ return isActivityIntent
+ }
+
+ override fun shouldAnimateLaunch(isActivityIntent: Boolean): Boolean {
+ return shouldAnimateLaunch(isActivityIntent, false)
+ }
+
+ /**
* Encapsulates the activity logic for activity starter.
*
* Logic is duplicated in {@link CentralSurfacesImpl}
@@ -419,7 +448,7 @@ constructor(
val animate =
animationController != null &&
!willLaunchResolverActivity &&
- centralSurfaces?.shouldAnimateLaunch(true /* isActivityIntent */) == true
+ shouldAnimateLaunch(isActivityIntent = true)
val animController =
wrapAnimationController(
animationController = animationController,
@@ -538,7 +567,7 @@ constructor(
val animate =
!willLaunchResolverActivity &&
animationController != null &&
- centralSurfaces?.shouldAnimateLaunch(intent.isActivity) == true
+ shouldAnimateLaunch(intent.isActivity)
// If we animate, don't collapse the shade and defer the keyguard dismiss (in case we
// run the animation on the keyguard). The animation will take care of (instantly)
@@ -595,7 +624,7 @@ constructor(
Log.w(TAG, "Sending intent failed: $e")
if (!collapse) {
// executeRunnableDismissingKeyguard did not collapse for us already.
- centralSurfaces?.collapsePanelOnMainThread()
+ shadeControllerLazy.get().collapseOnMainThread()
}
// TODO: Dismiss Keyguard.
}
@@ -637,7 +666,7 @@ constructor(
val animate =
animationController != null &&
- centralSurfaces?.shouldAnimateLaunch(
+ shouldAnimateLaunch(
/* isActivityIntent= */ true,
showOverLockscreenWhenLocked
) == true
@@ -804,7 +833,7 @@ constructor(
shadeControllerLazy.get().isExpandedVisible &&
!statusBarKeyguardViewManagerLazy.get().isBouncerShowing
) {
- shadeControllerLazy.get().animateCollapseShadeDelayed()
+ shadeControllerLazy.get().animateCollapseShadeForcedDelayed()
} else {
// Do it after DismissAction has been processed to conserve the
// needed ordering.
@@ -867,7 +896,8 @@ constructor(
if (dismissShade) {
return StatusBarLaunchAnimatorController(
animationController,
- it,
+ it.shadeViewController,
+ shadeControllerLazy.get(),
notifShadeWindowControllerLazy.get(),
isLaunchForActivity
)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 118e961acd24..0929a4c90613 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -26,7 +26,6 @@ import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.PowerManager;
import android.os.UserHandle;
-import android.service.notification.StatusBarNotification;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationAdapter;
@@ -44,7 +43,6 @@ import com.android.systemui.Dumpable;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.qs.QSPanelController;
import com.android.systemui.shade.ShadeViewController;
import com.android.systemui.shared.system.RemoteAnimationRunnerCompat;
@@ -187,14 +185,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
return contextForUser.getPackageManager();
}
- void animateExpandNotificationsPanel();
-
- void animateExpandSettingsPanel(@Nullable String subpanel);
-
- void collapsePanelOnMainThread();
-
- void togglePanel();
-
void start();
boolean updateIsKeyguard();
@@ -230,25 +220,10 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
boolean isOccluded();
- //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
- void onLaunchAnimationCancelled(boolean isLaunchForActivity);
-
- void onLaunchAnimationEnd(boolean launchIsFullScreen);
-
- boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen);
-
- boolean shouldAnimateLaunch(boolean isActivityIntent);
-
boolean isDeviceInVrMode();
NotificationPresenter getPresenter();
- void postAnimateCollapsePanels();
-
- void postAnimateForceCollapsePanels();
-
- void postAnimateOpenPanels();
-
boolean isPanelExpanded();
/**
@@ -266,8 +241,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
*/
default void onStatusBarTrackpadEvent(MotionEvent event) {}
- void animateCollapseQuickSettings();
-
/** */
boolean getCommandQueuePanelsEnabled();
@@ -293,8 +266,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
void readyForKeyguardDone();
- void resetUserExpandedStates();
-
void setLockscreenUser(int newUserId);
void showKeyguard();
@@ -383,9 +354,6 @@ public interface CentralSurfaces extends Dumpable, LifecycleOwner {
boolean isDeviceInteractive();
- void setNotificationSnoozed(StatusBarNotification sbn,
- NotificationSwipeActionHelper.SnoozeOption snoozeOption);
-
void awakenDreams();
void clearNotificationEffects();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index 0ccc81981e58..337acb6ef88c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -208,7 +208,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
@Override
public void animateCollapsePanels(int flags, boolean force) {
- mShadeController.animateCollapsePanels(flags, force, false /* delayed */,
+ mShadeController.animateCollapseShade(flags, force, false /* delayed */,
1.0f /* speedUpFactor */);
}
@@ -218,11 +218,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
Log.d(CentralSurfaces.TAG,
"animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
-
- mShadeViewController.expandToNotifications();
+ mShadeController.animateExpandShade();
}
@Override
@@ -231,14 +227,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
Log.d(CentralSurfaces.TAG,
"animateExpand: mExpandedVisible=" + mShadeController.isExpandedVisible());
}
- if (!mCommandQueue.panelsEnabled()) {
- return;
- }
-
- // Settings are not available in setup
- if (!mDeviceProvisionedController.isCurrentUserSetup()) return;
-
- mShadeViewController.expandToQs();
+ mShadeController.animateExpandQs();
}
@Override
@@ -559,7 +548,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
if (mCentralSurfaces.isPanelExpanded()) {
mShadeController.animateCollapseShade();
} else {
- animateExpandNotificationsPanel();
+ mShadeController.animateExpandShade();
}
}
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 c220fd2aafe2..a020da584e00 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -69,7 +69,6 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
-import android.os.Looper;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -173,7 +172,6 @@ import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.PluginListener;
import com.android.systemui.plugins.PluginManager;
import com.android.systemui.plugins.qs.QS;
-import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSFragment;
import com.android.systemui.qs.QSPanelController;
@@ -412,24 +410,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return mQSPanelController;
}
- /** */
- @Override
- public void animateExpandNotificationsPanel() {
- mCommandQueueCallbacks.animateExpandNotificationsPanel();
- }
-
- /** */
- @Override
- public void animateExpandSettingsPanel(@Nullable String subpanel) {
- mCommandQueueCallbacks.animateExpandSettingsPanel(subpanel);
- }
-
- /** */
- @Override
- public void togglePanel() {
- mCommandQueueCallbacks.togglePanel();
- }
-
/**
* The {@link StatusBarState} of the status bar.
*/
@@ -1823,58 +1803,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
return mKeyguardStateController.isOccluded();
}
- /** A launch animation was cancelled. */
- //TODO: These can / should probably be moved to NotificationPresenter or ShadeController
- @Override
- public void onLaunchAnimationCancelled(boolean isLaunchForActivity) {
- if (mPresenter.isPresenterFullyCollapsed() && !mPresenter.isCollapsing()
- && isLaunchForActivity) {
- mShadeController.onClosingFinished();
- } else {
- mShadeController.collapseShade(true /* animate */);
- }
- }
-
- /** A launch animation ended. */
- @Override
- public void onLaunchAnimationEnd(boolean launchIsFullScreen) {
- if (!mPresenter.isCollapsing()) {
- mShadeController.onClosingFinished();
- }
- if (launchIsFullScreen) {
- mShadeController.instantCollapseShade();
- }
- }
-
- /**
- * Whether we should animate an activity launch.
- *
- * Note: This method must be called *before* dismissing the keyguard.
- */
- @Override
- public boolean shouldAnimateLaunch(boolean isActivityIntent, boolean showOverLockscreen) {
- // TODO(b/184121838): Support launch animations when occluded.
- if (isOccluded()) {
- return false;
- }
-
- // Always animate if we are not showing the keyguard or if we animate over the lockscreen
- // (without unlocking it).
- if (showOverLockscreen || !mKeyguardStateController.isShowing()) {
- return true;
- }
-
- // We don't animate non-activity launches as they can break the animation.
- // TODO(b/184121838): Support non activity launches on the lockscreen.
- return isActivityIntent;
- }
-
- /** Whether we should animate an activity launch. */
- @Override
- public boolean shouldAnimateLaunch(boolean isActivityIntent) {
- return shouldAnimateLaunch(isActivityIntent, false /* showOverLockscreen */);
- }
-
@Override
public boolean isDeviceInVrMode() {
return mPresenter.isDeviceInVrMode();
@@ -1931,21 +1859,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
@Override
- public void postAnimateCollapsePanels() {
- mMainExecutor.execute(mShadeController::animateCollapseShade);
- }
-
- @Override
- public void postAnimateForceCollapsePanels() {
- mMainExecutor.execute(mShadeController::animateCollapseShadeForced);
- }
-
- @Override
- public void postAnimateOpenPanels() {
- mMessageRouter.sendMessage(MSG_OPEN_SETTINGS_PANEL);
- }
-
- @Override
public boolean isPanelExpanded() {
return mPanelExpanded;
}
@@ -1971,14 +1884,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mCentralSurfacesComponent.getNotificationPanelViewController().handleExternalTouch(event);
}
- @Override
- public void animateCollapseQuickSettings() {
- if (mState == StatusBarState.SHADE) {
- mShadeSurface.collapse(
- true, false /* delayed */, 1.0f /* speedUpFactor */);
- }
- }
-
private void onExpandedInvisible() {
setInteracting(StatusBarManager.WINDOW_STATUS_BAR, false);
if (!mNotificationActivityStarter.isCollapsingToShowActivityOverLockscreen()) {
@@ -2314,7 +2219,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
mNotificationShadeWindowController.setNotTouchable(false);
}
finishBarAnimations();
- resetUserExpandedStates();
+ mNotificationsController.resetUserExpandedStates();
}
Trace.endSection();
}
@@ -2333,11 +2238,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
};
- @Override
- public void resetUserExpandedStates() {
- mNotificationsController.resetUserExpandedStates();
- }
-
/**
* Notify the shade controller that the current user changed
*
@@ -2963,19 +2863,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
}
/**
- * Collapse the panel directly if we are on the main thread, post the collapsing on the main
- * thread if we are not.
- */
- @Override
- public void collapsePanelOnMainThread() {
- if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapseShade();
- } else {
- mContext.getMainExecutor().execute(mShadeController::collapseShade);
- }
- }
-
- /**
* Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
* from the power button).
* @param wakingUp Whether we're updating because we're waking up (true) or going to sleep
@@ -3577,12 +3464,6 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
};
@Override
- public void setNotificationSnoozed(StatusBarNotification sbn, SnoozeOption snoozeOption) {
- mNotificationsController.setNotificationSnoozed(sbn, snoozeOption);
- }
-
-
- @Override
public void awakenDreams() {
mUiBgExecutor.execute(() -> {
try {
@@ -3651,7 +3532,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
private void onShadeVisibilityChanged(boolean visible) {
if (mVisible != visible) {
mVisible = visible;
- if (!visible) {
+ if (visible) {
+ DejankUtils.notifyRendererOfExpensiveFrame(
+ mNotificationShadeWindowView, "onShadeVisibilityChanged");
+ } else {
mGutsManager.closeAndSaveGuts(true /* removeLeavebehind */, true /* force */,
true /* removeControls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
}
@@ -3780,8 +3664,9 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces {
if (userSetup != mUserSetup) {
mUserSetup = userSetup;
- if (!mUserSetup) {
- animateCollapseQuickSettings();
+ if (!mUserSetup && mState == StatusBarState.SHADE) {
+ mShadeSurface.collapse(true /* animate */, false /* delayed */,
+ 1.0f /* speedUpFactor */);
}
updateQsExpansionEnabled();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index 00ac3f4cdc11..b67ec581f8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -3,6 +3,8 @@ package com.android.systemui.statusbar.phone
import android.view.View
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.shade.ShadeViewController
import com.android.systemui.statusbar.NotificationShadeWindowController
/**
@@ -11,7 +13,8 @@ import com.android.systemui.statusbar.NotificationShadeWindowController
*/
class StatusBarLaunchAnimatorController(
private val delegate: ActivityLaunchAnimator.Controller,
- private val centralSurfaces: CentralSurfaces,
+ private val shadeViewController: ShadeViewController,
+ private val shadeController: ShadeController,
private val notificationShadeWindowController: NotificationShadeWindowController,
private val isLaunchForActivity: Boolean = true
) : ActivityLaunchAnimator.Controller by delegate {
@@ -23,25 +26,25 @@ class StatusBarLaunchAnimatorController(
override fun onIntentStarted(willAnimate: Boolean) {
delegate.onIntentStarted(willAnimate)
if (willAnimate) {
- centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(true)
+ shadeViewController.setIsLaunchAnimationRunning(true)
} else {
- centralSurfaces.collapsePanelOnMainThread()
+ shadeController.collapseOnMainThread()
}
}
override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationStart(isExpandingFullyAbove)
- centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(true)
+ shadeViewController.setIsLaunchAnimationRunning(true)
if (!isExpandingFullyAbove) {
- centralSurfaces.shadeViewController.collapseWithDuration(
+ shadeViewController.collapseWithDuration(
ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
}
}
override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
- centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(false)
- centralSurfaces.onLaunchAnimationEnd(isExpandingFullyAbove)
+ shadeViewController.setIsLaunchAnimationRunning(false)
+ shadeController.onLaunchAnimationEnd(isExpandingFullyAbove)
}
override fun onLaunchAnimationProgress(
@@ -50,12 +53,12 @@ class StatusBarLaunchAnimatorController(
linearProgress: Float
) {
delegate.onLaunchAnimationProgress(state, progress, linearProgress)
- centralSurfaces.shadeViewController.applyLaunchAnimationProgress(linearProgress)
+ shadeViewController.applyLaunchAnimationProgress(linearProgress)
}
override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
delegate.onLaunchAnimationCancelled()
- centralSurfaces.shadeViewController.setIsLaunchAnimationRunning(false)
- centralSurfaces.onLaunchAnimationCancelled(isLaunchForActivity)
+ shadeViewController.setIsLaunchAnimationRunning(false)
+ shadeController.onLaunchAnimationCancelled(isLaunchForActivity)
}
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 28ec1ac5e593..f79a08173bf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -122,7 +122,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
private final CentralSurfaces mCentralSurfaces;
private final NotificationPresenter mPresenter;
- private final ShadeViewController mNotificationPanel;
+ private final ShadeViewController mShadeViewController;
private final NotificationShadeWindowController mNotificationShadeWindowController;
private final ActivityLaunchAnimator mActivityLaunchAnimator;
private final NotificationLaunchAnimatorControllerProvider mNotificationAnimationProvider;
@@ -158,7 +158,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
OnUserInteractionCallback onUserInteractionCallback,
CentralSurfaces centralSurfaces,
NotificationPresenter presenter,
- ShadeViewController panel,
+ ShadeViewController shadeViewController,
NotificationShadeWindowController notificationShadeWindowController,
ActivityLaunchAnimator activityLaunchAnimator,
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
@@ -193,7 +193,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
// TODO: use KeyguardStateController#isOccluded to remove this dependency
mCentralSurfaces = centralSurfaces;
mPresenter = presenter;
- mNotificationPanel = panel;
+ mShadeViewController = shadeViewController;
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
mUserTracker = userTracker;
@@ -237,7 +237,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
&& mActivityIntentHelper.wouldPendingLaunchResolverActivity(intent,
mLockscreenUserManager.getCurrentUserId());
final boolean animate = !willLaunchResolverActivity
- && mCentralSurfaces.shouldAnimateLaunch(isActivityIntent);
+ && mActivityStarter.shouldAnimateLaunch(isActivityIntent);
boolean showOverLockscreen = mKeyguardStateController.isShowing() && intent != null
&& mActivityIntentHelper.wouldPendingShowOverLockscreen(intent,
mLockscreenUserManager.getCurrentUserId());
@@ -288,7 +288,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
}
// Always defer the keyguard dismiss when animating.
- return animate || !mNotificationPanel.isFullyCollapsed();
+ return animate || !mShadeViewController.isFullyCollapsed();
}
private void handleNotificationClickAfterPanelCollapsed(
@@ -323,7 +323,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
removeHunAfterClick(row);
// Show work challenge, do not run PendingIntent and
// remove notification
- collapseOnMainThread();
+ mShadeController.collapseOnMainThread();
return;
}
}
@@ -440,7 +440,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
ActivityLaunchAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row, null),
- mCentralSurfaces,
+ mShadeViewController,
+ mShadeController,
mNotificationShadeWindowController,
isActivityIntent);
mActivityLaunchAnimator.startPendingIntentWithAnimation(
@@ -472,7 +473,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
@Override
public void startNotificationGutsIntent(final Intent intent, final int appUid,
ExpandableNotificationRow row) {
- boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
+ boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -480,7 +481,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
ActivityLaunchAnimator.Controller animationController =
new StatusBarLaunchAnimatorController(
mNotificationAnimationProvider.getAnimatorController(row),
- mCentralSurfaces,
+ mShadeViewController,
+ mShadeController,
mNotificationShadeWindowController,
true /* isActivityIntent */);
@@ -507,7 +509,7 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
@Override
public void startHistoryIntent(View view, boolean showHistory) {
- boolean animate = mCentralSurfaces.shouldAnimateLaunch(true /* isActivityIntent */);
+ boolean animate = mActivityStarter.shouldAnimateLaunch(true /* isActivityIntent */);
ActivityStarter.OnDismissAction onDismissAction = new ActivityStarter.OnDismissAction() {
@Override
public boolean onDismiss() {
@@ -529,7 +531,8 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
viewController == null ? null
: new StatusBarLaunchAnimatorController(
viewController,
- mCentralSurfaces,
+ mShadeViewController,
+ mShadeController,
mNotificationShadeWindowController,
true /* isActivityIntent */);
@@ -630,11 +633,4 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
return true;
}
- private void collapseOnMainThread() {
- if (Looper.getMainLooper().isCurrentThread()) {
- mShadeController.collapseShade();
- } else {
- mMainThreadHandler.post(mShadeController::collapseShade);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
index d585163aa223..02b7e9176cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -21,7 +21,6 @@ import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_STANDARD;
import android.app.ActivityManager;
-import android.app.Notification;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
@@ -47,7 +46,6 @@ import android.view.OnReceiveContentListener;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewGroup;
-import android.view.ViewGroupOverlay;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowInsetsAnimation;
@@ -58,6 +56,7 @@ import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
+import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -436,25 +435,23 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
if (animate && parent != null && mIsFocusAnimationFlagActive) {
ViewGroup grandParent = (ViewGroup) parent.getParent();
- ViewGroupOverlay overlay = parent.getOverlay();
View actionsContainer = getActionsContainerLayout();
int actionsContainerHeight =
actionsContainer != null ? actionsContainer.getHeight() : 0;
- // After adding this RemoteInputView to the overlay of the parent (and thus removing
- // it from the parent itself), the parent will shrink in height. This causes the
- // overlay to be moved. To correct the position of the overlay we need to offset it.
- int overlayOffsetY = actionsContainerHeight - getHeight();
- overlay.add(this);
+ // When defocusing, the notification needs to shrink. Therefore, we need to free
+ // up the space that was needed for the RemoteInputView. This is done by setting
+ // a negative top margin of the height difference of the RemoteInputView and its
+ // sibling (the actions_container_layout containing the Reply button etc.)
+ final int heightToShrink = actionsContainerHeight - getHeight();
+ setTopMargin(heightToShrink);
if (grandParent != null) grandParent.setClipChildren(false);
- Animator animator = getDefocusAnimator(actionsContainer, overlayOffsetY);
- View self = this;
+ final Animator animator = getDefocusAnimator(actionsContainer);
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- overlay.remove(self);
- parent.addView(self);
+ setTopMargin(0);
if (grandParent != null) grandParent.setClipChildren(true);
setVisibility(GONE);
if (mWrapper != null) {
@@ -499,6 +496,13 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
}
}
+ private void setTopMargin(int topMargin) {
+ if (!(getLayoutParams() instanceof FrameLayout.LayoutParams)) return;
+ final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams();
+ layoutParams.topMargin = topMargin;
+ setLayoutParams(layoutParams);
+ }
+
@VisibleForTesting
protected void setViewRootImpl(ViewRootImpl viewRoot) {
mTestableViewRootImpl = viewRoot;
@@ -674,12 +678,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
mProgressBar.setVisibility(INVISIBLE);
mResetting = true;
mSending = false;
+ mController.removeSpinning(mEntry.getKey(), mToken);
onDefocus(true /* animate */, false /* logClose */, () -> {
mEntry.remoteInputTextWhenReset = SpannedString.valueOf(mEditText.getText());
mEditText.getText().clear();
mEditText.setEnabled(isAggregatedVisible());
mSendButton.setVisibility(VISIBLE);
- mController.removeSpinning(mEntry.getKey(), mToken);
updateSendButton();
setAttachment(null);
mResetting = false;
@@ -874,7 +878,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(FOCUS_ANIMATION_MIN_SCALE, 1f);
scaleAnimator.addUpdateListener(valueAnimator -> {
- setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), 0);
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
});
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
@@ -901,9 +905,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
* Creates an animator for the defocus animation.
*
* @param fadeInView View that will be faded in during the defocus animation.
- * @param offsetY The RemoteInputView will be offset by offsetY during the animation
*/
- private Animator getDefocusAnimator(@Nullable View fadeInView, int offsetY) {
+ private Animator getDefocusAnimator(@Nullable View fadeInView) {
final AnimatorSet animatorSet = new AnimatorSet();
final Animator alphaAnimator = ObjectAnimator.ofFloat(this, View.ALPHA, 1f, 0f);
@@ -913,14 +916,14 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
ValueAnimator scaleAnimator = ValueAnimator.ofFloat(1f, FOCUS_ANIMATION_MIN_SCALE);
scaleAnimator.addUpdateListener(valueAnimator -> {
- setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue(), offsetY);
+ setFocusAnimationScaleY((float) scaleAnimator.getAnimatedValue());
});
scaleAnimator.setDuration(FOCUS_ANIMATION_TOTAL_DURATION);
scaleAnimator.setInterpolator(InterpolatorsAndroidX.FAST_OUT_SLOW_IN);
scaleAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation, boolean isReverse) {
- setFocusAnimationScaleY(1f /* scaleY */, 0 /* verticalOffset */);
+ setFocusAnimationScaleY(1f /* scaleY */);
}
});
@@ -942,9 +945,8 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
* Sets affected view properties for a vertical scale animation
*
* @param scaleY desired vertical view scale
- * @param verticalOffset vertical offset to apply to the RemoteInputView during the animation
*/
- private void setFocusAnimationScaleY(float scaleY, int verticalOffset) {
+ private void setFocusAnimationScaleY(float scaleY) {
int verticalBoundOffset = (int) ((1f - scaleY) * 0.5f * mContentView.getHeight());
Rect contentBackgroundBounds = new Rect(0, verticalBoundOffset, mContentView.getWidth(),
mContentView.getHeight() - verticalBoundOffset);
@@ -955,7 +957,7 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene
} else {
mContentBackgroundBounds = contentBackgroundBounds;
}
- setTranslationY(verticalBoundOffset + verticalOffset);
+ setTranslationY(verticalBoundOffset);
}
/** Handler for button click on send action in IME. */
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
index 8214822f0335..1e73cb3b9b24 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldHapticsPlayer.kt
@@ -1,6 +1,7 @@
package com.android.systemui.unfold
import android.os.SystemProperties
+import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.os.Vibrator
import com.android.systemui.dagger.qualifiers.Main
@@ -22,6 +23,8 @@ constructor(
) : TransitionProgressListener {
private var isFirstAnimationAfterUnfold = false
+ private val touchVibrationAttributes =
+ VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK)
init {
if (vibrator != null) {
@@ -71,7 +74,7 @@ constructor(
}
private fun playHaptics() {
- vibrator?.vibrate(effect)
+ vibrator?.vibrate(effect, touchVibrationAttributes)
}
private val hapticsScale: Float
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 7236e0fd134a..59f2cdb745ca 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -224,7 +224,7 @@ object UserSwitcherViewBinder {
}
sectionView.removeAllViewsInLayout()
- for (viewModel in section) {
+ section.onEachIndexed { index, viewModel ->
val view =
layoutInflater.inflate(
R.layout.user_switcher_fullscreen_popup_item,
@@ -237,6 +237,13 @@ object UserSwitcherViewBinder {
view.resources.getString(viewModel.textResourceId)
view.setOnClickListener { viewModel.onClicked() }
sectionView.addView(view)
+ // Ensure that the first item in the first section gets accessibility focus.
+ // Request for focus with a delay when view is inflated an added to the listview.
+ if (index == 0 && position == 0) {
+ view.postDelayed({
+ view.requestAccessibilityFocus()
+ }, 200)
+ }
}
return sectionView
}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b24a69292186..b848d2e84faf 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -2006,14 +2006,14 @@ public class VolumeDialogImpl implements VolumeDialog, Dumpable,
if (row.anim == null) {
row.anim = ObjectAnimator.ofInt(row.slider, "progress", progress, newProgress);
row.anim.setInterpolator(new DecelerateInterpolator());
+ row.anim.addListener(
+ getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
} else {
row.anim.cancel();
row.anim.setIntValues(progress, newProgress);
}
row.animTargetProgress = newProgress;
row.anim.setDuration(UPDATE_ANIMATION_DURATION);
- row.anim.addListener(
- getJankListener(row.view, TYPE_UPDATE, UPDATE_ANIMATION_DURATION));
row.anim.start();
} else {
// update slider directly to clamped value
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index 14ad3acf7fb0..263d3750c657 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -26,18 +26,17 @@ import android.text.TextPaint
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
-
-import kotlin.math.ceil
+import org.mockito.Mockito.`when`
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -56,15 +55,13 @@ class TextAnimatorTest : SysuiTestCase() {
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
- textAnimator.setTextStyle(
- weight = 400,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 400, animate = true)
// If animation is requested, the base state should be rebased and the target state should
// be updated.
@@ -88,15 +85,13 @@ class TextAnimatorTest : SysuiTestCase() {
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
- textAnimator.setTextStyle(
- weight = 400,
- animate = false
- )
+ textAnimator.setTextStyle(weight = 400, animate = false)
// If animation is not requested, the progress should be 1 which is end of animation and the
// base state is rebased to target state by calling rebase.
@@ -118,15 +113,16 @@ class TextAnimatorTest : SysuiTestCase() {
`when`(textInterpolator.targetPaint).thenReturn(paint)
val animationEndCallback = mock(Runnable::class.java)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
textAnimator.setTextStyle(
- weight = 400,
- animate = true,
- onAnimationEnd = animationEndCallback
+ weight = 400,
+ animate = true,
+ onAnimationEnd = animationEndCallback
)
// Verify animationEnd callback has been added.
@@ -144,34 +140,27 @@ class TextAnimatorTest : SysuiTestCase() {
val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
- val paint = TextPaint().apply {
- typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
- }
+ val paint =
+ TextPaint().apply {
+ typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
+ }
`when`(textInterpolator.targetPaint).thenReturn(paint)
- val textAnimator = TextAnimator(layout, {}).apply {
- this.textInterpolator = textInterpolator
- this.animator = valueAnimator
- }
+ val textAnimator =
+ TextAnimator(layout, null, {}).apply {
+ this.textInterpolator = textInterpolator
+ this.animator = valueAnimator
+ }
- textAnimator.setTextStyle(
- weight = 400,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 400, animate = true)
val prevTypeface = paint.typeface
- textAnimator.setTextStyle(
- weight = 700,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 700, animate = true)
assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface)
- textAnimator.setTextStyle(
- weight = 400,
- animate = true
- )
+ textAnimator.setTextStyle(weight = 400, animate = true)
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index b9f92a064bc8..b4a4a11a81a1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -954,6 +954,25 @@ public class AuthControllerTest extends SysuiTestCase {
eq(null) /* credentialAttestation */);
}
+ @Test
+ public void testShowDialog_whenOwnerNotInForeground() {
+ PromptInfo promptInfo = createTestPromptInfo();
+ promptInfo.setAllowBackgroundAuthentication(false);
+ switchTask("other_package");
+ mAuthController.showAuthenticationDialog(promptInfo,
+ mReceiver /* receiver */,
+ new int[]{1} /* sensorIds */,
+ false /* credentialAllowed */,
+ true /* requireConfirmation */,
+ 0 /* userId */,
+ 0 /* operationId */,
+ "testPackage",
+ REQUEST_ID);
+
+ assertNull(mAuthController.mCurrentDialog);
+ verify(mDialog1, never()).show(any(), any());
+ }
+
private void showDialog(int[] sensorIds, boolean credentialAllowed) {
mAuthController.showAuthenticationDialog(createTestPromptInfo(),
mReceiver /* receiver */,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
index ca6282c66a17..461ec653d819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java
@@ -28,6 +28,7 @@ import androidx.lifecycle.Observer;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamLogger;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.flags.FakeFeatureFlags;
import com.android.systemui.flags.Flags;
@@ -56,6 +57,8 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
private FakeFeatureFlags mFeatureFlags;
@Mock
private Observer mObserver;
+ @Mock
+ private DreamLogger mLogger;
@Before
public void setUp() {
@@ -66,7 +69,8 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
mStateController = new DreamOverlayStateController(
mExecutor,
/* overlayEnabled= */ true,
- mFeatureFlags);
+ mFeatureFlags,
+ mLogger);
mLiveData = new ComplicationCollectionLiveData(mStateController);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index cfd51e3901bd..c97eedbec45d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.dreams;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
@@ -512,4 +514,23 @@ public class DreamOverlayServiceTest extends SysuiTestCase {
mMainExecutor.runAllReady();
verify(mDreamOverlayContainerViewController, never()).wakeUp(callback, mMainExecutor);
}
+
+ @Test
+ public void testSystemFlagShowForAllUsersSetOnWindow() throws RemoteException {
+ final IDreamOverlayClient client = getClient();
+
+ // Inform the overlay service of dream starting. Do not show dream complications.
+ client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
+ false /*shouldShowComplication*/);
+ mMainExecutor.runAllReady();
+
+ final ArgumentCaptor<WindowManager.LayoutParams> paramsCaptor =
+ ArgumentCaptor.forClass(WindowManager.LayoutParams.class);
+
+ // Verify that a new window is added.
+ verify(mWindowManager).addView(any(), paramsCaptor.capture());
+
+ assertThat((paramsCaptor.getValue().privateFlags & SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+ == SYSTEM_FLAG_SHOW_FOR_ALL_USERS).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index 7b4160551c82..2c1ebe4121af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -58,6 +58,9 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
@Mock
private FeatureFlags mFeatureFlags;
+ @Mock
+ private DreamLogger mLogger;
+
final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock());
@Before
@@ -405,6 +408,6 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
}
private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
- return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags);
+ return new DreamOverlayStateController(mExecutor, overlayEnabled, mFeatureFlags, mLogger);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index d16b7570e2c3..5dc0e55632fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -113,6 +113,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
DreamOverlayStateController mDreamOverlayStateController;
@Mock
UserTracker mUserTracker;
+ @Mock
+ DreamLogger mLogger;
@Captor
private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
@@ -146,7 +148,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
- mUserTracker);
+ mUserTracker,
+ mLogger);
}
@Test
@@ -289,7 +292,8 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
mStatusBarWindowStateController,
mDreamOverlayStatusBarItemsProvider,
mDreamOverlayStateController,
- mUserTracker);
+ mUserTracker,
+ mLogger);
controller.onViewAttached();
verify(mView, never()).showIcon(
eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
index e8cbdf3db327..2830476874ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpHandlerTest.kt
@@ -22,7 +22,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.ProtoDumpable
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.LogBuffer
-import com.android.systemui.shared.system.UncaughtExceptionPreHandlerManager
+import com.android.systemui.log.table.TableLogBuffer
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.google.common.truth.Truth.assertThat
@@ -45,8 +45,6 @@ class DumpHandlerTest : SysuiTestCase() {
@Mock
private lateinit var logBufferEulogizer: LogBufferEulogizer
- @Mock
- private lateinit var exceptionHandlerManager: UncaughtExceptionPreHandlerManager
@Mock
private lateinit var pw: PrintWriter
@@ -70,6 +68,11 @@ class DumpHandlerTest : SysuiTestCase() {
@Mock
private lateinit var buffer2: LogBuffer
+ @Mock
+ private lateinit var table1: TableLogBuffer
+ @Mock
+ private lateinit var table2: TableLogBuffer
+
private val dumpManager = DumpManager()
@Before
@@ -83,21 +86,22 @@ class DumpHandlerTest : SysuiTestCase() {
mutableMapOf(
EmptyCoreStartable::class.java to Provider { EmptyCoreStartable() }
),
- exceptionHandlerManager
)
}
@Test
fun testDumpablesCanBeDumpedSelectively() {
// GIVEN a variety of registered dumpables and buffers
- dumpManager.registerDumpable("dumpable1", dumpable1)
- dumpManager.registerDumpable("dumpable2", dumpable2)
- dumpManager.registerDumpable("dumpable3", dumpable3)
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
// WHEN some of them are dumped explicitly
- val args = arrayOf("dumpable1", "dumpable3", "buffer2")
+ val args = arrayOf("dumpable1", "dumpable3", "buffer2", "table2")
dumpHandler.dump(fd, pw, args)
// THEN only the requested ones have their dump() method called
@@ -108,12 +112,14 @@ class DumpHandlerTest : SysuiTestCase() {
verify(dumpable3).dump(pw, args)
verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
verify(buffer2).dump(pw, 0)
+ verify(table1, never()).dump(any(), any())
+ verify(table2).dump(pw, args)
}
@Test
fun testDumpableMatchingIsBasedOnEndOfTag() {
// GIVEN a dumpable registered to the manager
- dumpManager.registerDumpable("com.android.foo.bar.dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
// WHEN that module is dumped
val args = arrayOf("dumpable1")
@@ -131,6 +137,8 @@ class DumpHandlerTest : SysuiTestCase() {
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
// WHEN a critical dump is requested
val args = arrayOf("--dump-priority", "CRITICAL")
@@ -144,6 +152,8 @@ class DumpHandlerTest : SysuiTestCase() {
any(Array<String>::class.java))
verify(buffer1, never()).dump(any(PrintWriter::class.java), anyInt())
verify(buffer2, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(table1, never()).dump(any(), any())
+ verify(table2, never()).dump(any(), any())
}
@Test
@@ -154,6 +164,8 @@ class DumpHandlerTest : SysuiTestCase() {
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
// WHEN a normal dump is requested
val args = arrayOf("--dump-priority", "NORMAL")
@@ -169,6 +181,8 @@ class DumpHandlerTest : SysuiTestCase() {
verify(dumpable3).dump(pw, args)
verify(buffer1).dump(pw, 0)
verify(buffer2).dump(pw, 0)
+ verify(table1).dump(pw, args)
+ verify(table2).dump(pw, args)
}
@Test
@@ -184,6 +198,81 @@ class DumpHandlerTest : SysuiTestCase() {
}
@Test
+ fun testDumpBuffers() {
+ // GIVEN a variety of registered dumpables and buffers and tables
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
+
+ // WHEN a buffer dump is requested
+ val args = arrayOf("buffers", "--tail", "1")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN all buffers are dumped (and no dumpables or tables)
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1).dump(pw, tailLength = 1)
+ verify(buffer2).dump(pw, tailLength = 1)
+ verify(table1, never()).dump(any(), any())
+ verify(table2, never()).dump(any(), any())
+ }
+
+ @Test
+ fun testDumpDumpables() {
+ // GIVEN a variety of registered dumpables and buffers and tables
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
+
+ // WHEN a dumpable dump is requested
+ val args = arrayOf("dumpables")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+ verify(dumpable1).dump(pw, args)
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3).dump(pw, args)
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
+ verify(table1, never()).dump(any(), any())
+ verify(table2, never()).dump(any(), any())
+ }
+
+ @Test
+ fun testDumpTables() {
+ // GIVEN a variety of registered dumpables and buffers and tables
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
+
+ // WHEN a dumpable dump is requested
+ val args = arrayOf("tables")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
+ verify(table1).dump(pw, args)
+ verify(table2).dump(pw, args)
+ }
+
+ @Test
fun testDumpAllProtoDumpables() {
dumpManager.registerDumpable("protoDumpable1", protoDumpable1)
dumpManager.registerDumpable("protoDumpable2", protoDumpable2)
@@ -207,6 +296,123 @@ class DumpHandlerTest : SysuiTestCase() {
verify(protoDumpable2, never()).dumpProto(any(), any())
}
+ @Test
+ fun testDumpTarget_selectsShortestNamedDumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
+ dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
+ dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf("dumpable")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN the matching dumpable with the shorter name is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3, never()).dump(any(), any())
+ }
+
+ @Test
+ fun testDumpTarget_selectsShortestNamedBuffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerBuffer("first-buffer", buffer1)
+ dumpManager.registerBuffer("scnd-buffer", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf("buffer", "--tail", "14")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN the matching buffer with the shorter name is dumped
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2).dump(pw, tailLength = 14)
+ }
+
+ @Test
+ fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("big-buffer1", buffer1)
+ dumpManager.registerBuffer("big-buffer2", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf("2")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN the matching dumpable with the shorter name is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2).dump(pw, args)
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
+ }
+
+ @Test
+ fun testDumpTarget_selectsShortestNamedMatch_buffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
+ dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
+ dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf("2", "--tail", "14")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN the matching buffer with the shorter name is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2).dump(pw, tailLength = 14)
+ }
+
+ @Test
+ fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("d1x", dumpable1)
+ dumpManager.registerCriticalDumpable("d2x", dumpable2)
+ dumpManager.registerCriticalDumpable("a3x", dumpable3)
+ dumpManager.registerBuffer("ab1x", buffer1)
+ dumpManager.registerBuffer("b2x", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf("x")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3).dump(pw, args)
+ verify(buffer1, never()).dump(any(), anyInt())
+ verify(buffer2, never()).dump(any(), anyInt())
+ }
+
+ @Test
+ fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
+ // GIVEN a variety of registered dumpables and buffers
+ dumpManager.registerCriticalDumpable("d1x", dumpable1)
+ dumpManager.registerCriticalDumpable("d2x", dumpable2)
+ dumpManager.registerCriticalDumpable("az1x", dumpable3)
+ dumpManager.registerBuffer("b1x", buffer1)
+ dumpManager.registerBuffer("b2x", buffer2)
+
+ // WHEN a dumpable is dumped by a suffix that matches multiple options
+ val args = arrayOf("x", "--tail", "14")
+ dumpHandler.dump(fd, pw, args)
+
+ // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
+ verify(dumpable1, never()).dump(any(), any())
+ verify(dumpable2, never()).dump(any(), any())
+ verify(dumpable3, never()).dump(any(), any())
+ verify(buffer1).dump(pw, tailLength = 14)
+ verify(buffer2, never()).dump(any(), anyInt())
+ }
+
+
private class EmptyCoreStartable : CoreStartable {
override fun start() {}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
index 02555cfa783a..6d5226f35e97 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/DumpManagerTest.kt
@@ -20,21 +20,17 @@ import androidx.test.filters.SmallTest
import com.android.systemui.Dumpable
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.LogBuffer
-import com.android.systemui.util.mockito.any
-import java.io.PrintWriter
+import com.android.systemui.log.table.TableLogBuffer
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@SmallTest
class DumpManagerTest : SysuiTestCase() {
- @Mock private lateinit var pw: PrintWriter
-
@Mock private lateinit var dumpable1: Dumpable
@Mock private lateinit var dumpable2: Dumpable
@Mock private lateinit var dumpable3: Dumpable
@@ -42,6 +38,9 @@ class DumpManagerTest : SysuiTestCase() {
@Mock private lateinit var buffer1: LogBuffer
@Mock private lateinit var buffer2: LogBuffer
+ @Mock private lateinit var table1: TableLogBuffer
+ @Mock private lateinit var table2: TableLogBuffer
+
private val dumpManager = DumpManager()
@Before
@@ -50,276 +49,144 @@ class DumpManagerTest : SysuiTestCase() {
}
@Test
- fun testDumpTarget_dumpable() {
- // GIVEN a variety of registered dumpables and buffers
+ fun testRegisterUnregister_dumpables() {
+ // GIVEN a variety of registered dumpables
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
- dumpManager.registerBuffer("buffer1", buffer1)
- dumpManager.registerBuffer("buffer2", buffer2)
+ dumpManager.registerNormalDumpable("dumpable3", dumpable3)
- // WHEN a dumpable is dumped explicitly
- val args = arrayOf<String>()
- dumpManager.dumpTarget("dumpable2", pw, args, tailLength = 0)
+ // WHEN the collection is requested
+ var dumpables = dumpManager.getDumpables().map { it.dumpable }
- // THEN only the requested one has their dump() method called
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2).dump(pw, args)
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2, never()).dump(any(), anyInt())
- }
+ // THEN it contains the registered entries
+ assertThat(dumpables).containsExactly(dumpable1, dumpable2, dumpable3)
- @Test
- fun testDumpTarget_buffer() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
- dumpManager.registerBuffer("buffer1", buffer1)
- dumpManager.registerBuffer("buffer2", buffer2)
+ // WHEN the dumpables are unregistered
+ dumpManager.unregisterDumpable("dumpable2")
+ dumpManager.unregisterDumpable("dumpable3")
- // WHEN a buffer is dumped explicitly
- val args = arrayOf<String>()
- dumpManager.dumpTarget("buffer1", pw, args, tailLength = 14)
+ // WHEN the dumpable collection is requests
+ dumpables = dumpManager.getDumpables().map { it.dumpable }
- // THEN only the requested one has their dump() method called
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1).dump(pw, tailLength = 14)
- verify(buffer2, never()).dump(any(), anyInt())
+ // THEN it contains only the currently-registered entry
+ assertThat(dumpables).containsExactly(dumpable1)
}
@Test
- fun testDumpableMatchingIsBasedOnEndOfTag() {
- // GIVEN a dumpable registered to the manager
- dumpManager.registerCriticalDumpable("com.android.foo.bar.dumpable1", dumpable1)
+ fun testRegister_buffers() {
+ // GIVEN a set of registered buffers
+ dumpManager.registerBuffer("buffer1", buffer1)
+ dumpManager.registerBuffer("buffer2", buffer2)
- // WHEN that module is dumped
- val args = arrayOf<String>()
- dumpManager.dumpTarget("dumpable1", pw, arrayOf(), tailLength = 14)
+ // WHEN the collection is requested
+ val dumpables = dumpManager.getLogBuffers().map { it.buffer }
- // THEN its dump() method is called
- verify(dumpable1).dump(pw, args)
+ // THEN it contains the registered entries
+ assertThat(dumpables).containsExactly(buffer1, buffer2)
}
@Test
- fun testDumpTarget_selectsShortestNamedDumpable() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("first-dumpable", dumpable1)
- dumpManager.registerCriticalDumpable("scnd-dumpable", dumpable2)
- dumpManager.registerCriticalDumpable("third-dumpable", dumpable3)
-
- // WHEN a dumpable is dumped by a suffix that matches multiple options
- val args = arrayOf<String>()
- dumpManager.dumpTarget("dumpable", pw, args, tailLength = 0)
-
- // THEN the matching dumpable with the shorter name is dumped
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2).dump(pw, args)
- verify(dumpable3, never()).dump(any(), any())
- }
+ fun testRegister_tableLogBuffers() {
+ // GIVEN a set of registered buffers
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
- @Test
- fun testDumpTarget_selectsShortestNamedBuffer() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerBuffer("first-buffer", buffer1)
- dumpManager.registerBuffer("scnd-buffer", buffer2)
-
- // WHEN a dumpable is dumped by a suffix that matches multiple options
- val args = arrayOf<String>()
- dumpManager.dumpTarget("buffer", pw, args, tailLength = 14)
-
- // THEN the matching buffer with the shorter name is dumped
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2).dump(pw, tailLength = 14)
- }
+ // WHEN the collection is requested
+ val tables = dumpManager.getTableLogBuffers().map { it.table }
- @Test
- fun testDumpTarget_selectsShortestNamedMatch_dumpable() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
- dumpManager.registerBuffer("big-buffer1", buffer1)
- dumpManager.registerBuffer("big-buffer2", buffer2)
-
- // WHEN a dumpable is dumped by a suffix that matches multiple options
- val args = arrayOf<String>()
- dumpManager.dumpTarget("2", pw, args, tailLength = 14)
-
- // THEN the matching dumpable with the shorter name is dumped
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2).dump(pw, args)
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2, never()).dump(any(), anyInt())
+ // THEN it contains the registered entries
+ assertThat(tables).containsExactly(table1, table2)
}
@Test
- fun testDumpTarget_selectsShortestNamedMatch_buffer() {
- // GIVEN a variety of registered dumpables and buffers
+ fun registerDumpable_throwsWhenNameCannotBeAssigned() {
+ // GIVEN dumpable1 and buffer1 and table1 are registered
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerCriticalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
- dumpManager.registerBuffer("buffer2", buffer2)
-
- // WHEN a dumpable is dumped by a suffix that matches multiple options
- val args = arrayOf<String>()
- dumpManager.dumpTarget("2", pw, args, tailLength = 14)
-
- // THEN the matching buffer with the shorter name is dumped
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2).dump(pw, tailLength = 14)
+ dumpManager.registerTableLogBuffer("table1", table1)
+
+ // THEN an exception is thrown when trying to re-register a new dumpable under the same key
+ assertThrows(IllegalArgumentException::class.java) {
+ dumpManager.registerCriticalDumpable("dumpable1", dumpable2)
+ }
+ assertThrows(IllegalArgumentException::class.java) {
+ dumpManager.registerBuffer("buffer1", buffer2)
+ }
+ assertThrows(IllegalArgumentException::class.java) {
+ dumpManager.registerTableLogBuffer("table1", table2)
+ }
}
@Test
- fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_dumpable() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("d1x", dumpable1)
- dumpManager.registerCriticalDumpable("d2x", dumpable2)
- dumpManager.registerCriticalDumpable("a3x", dumpable3)
- dumpManager.registerBuffer("ab1x", buffer1)
- dumpManager.registerBuffer("b2x", buffer2)
-
- // WHEN a dumpable is dumped by a suffix that matches multiple options
- val args = arrayOf<String>()
- dumpManager.dumpTarget("x", pw, args, tailLength = 14)
-
- // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3).dump(pw, args)
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2, never()).dump(any(), anyInt())
- }
-
- @Test
- fun testDumpTarget_selectsTheAlphabeticallyFirstShortestMatch_buffer() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("d1x", dumpable1)
- dumpManager.registerCriticalDumpable("d2x", dumpable2)
- dumpManager.registerCriticalDumpable("az1x", dumpable3)
- dumpManager.registerBuffer("b1x", buffer1)
- dumpManager.registerBuffer("b2x", buffer2)
-
- // WHEN a dumpable is dumped by a suffix that matches multiple options
- val args = arrayOf<String>()
- dumpManager.dumpTarget("x", pw, args, tailLength = 14)
-
- // THEN the alphabetically first dumpable/buffer (of the 3 letter names) is dumped
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1).dump(pw, tailLength = 14)
- verify(buffer2, never()).dump(any(), anyInt())
- }
-
- @Test
- fun testDumpDumpables() {
- // GIVEN a variety of registered dumpables and buffers
+ fun registerDumpable_doesNotThrowWhenReRegistering() {
+ // GIVEN dumpable1 and buffer1 are registered
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerNormalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
- dumpManager.registerBuffer("buffer2", buffer2)
- // WHEN a dumpable dump is requested
- val args = arrayOf<String>()
- dumpManager.dumpDumpables(pw, args)
-
- // THEN all dumpables are dumped (both critical and normal) (and no dumpables)
- verify(dumpable1).dump(pw, args)
- verify(dumpable2).dump(pw, args)
- verify(dumpable3).dump(pw, args)
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2, never()).dump(any(), anyInt())
- }
-
- @Test
- fun testDumpBuffers() {
- // GIVEN a variety of registered dumpables and buffers
+ // THEN no exception is thrown when trying to re-register a new dumpable under the same key
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerNormalDumpable("dumpable3", dumpable3)
dumpManager.registerBuffer("buffer1", buffer1)
- dumpManager.registerBuffer("buffer2", buffer2)
-
- // WHEN a buffer dump is requested
- dumpManager.dumpBuffers(pw, tailLength = 1)
- // THEN all buffers are dumped (and no dumpables)
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1).dump(pw, tailLength = 1)
- verify(buffer2).dump(pw, tailLength = 1)
+ // No exception thrown
}
@Test
- fun testCriticalDump() {
- // GIVEN a variety of registered dumpables and buffers
+ fun getDumpables_returnsSafeCollection() {
+ // GIVEN a variety of registered dumpables
dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
dumpManager.registerNormalDumpable("dumpable3", dumpable3)
- dumpManager.registerBuffer("buffer1", buffer1)
- dumpManager.registerBuffer("buffer2", buffer2)
- // WHEN a critical dump is requested
- val args = arrayOf<String>()
- dumpManager.dumpCritical(pw, args)
+ // WHEN the collection is retrieved
+ val dumpables = dumpManager.getDumpables()
- // THEN only critical modules are dumped (and no buffers)
- verify(dumpable1).dump(pw, args)
- verify(dumpable2).dump(pw, args)
- verify(dumpable3, never()).dump(any(), any())
- verify(buffer1, never()).dump(any(), anyInt())
- verify(buffer2, never()).dump(any(), anyInt())
+ // WHEN the collection changes from underneath
+ dumpManager.unregisterDumpable("dumpable1")
+ dumpManager.unregisterDumpable("dumpable2")
+ dumpManager.unregisterDumpable("dumpable3")
+
+ // THEN new collections are empty
+ assertThat(dumpManager.getDumpables()).isEmpty()
+
+ // AND the collection is still safe to use
+ assertThat(dumpables).hasSize(3)
}
@Test
- fun testNormalDump() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ fun getBuffers_returnsSafeCollection() {
+ // GIVEN a set of registered buffers
dumpManager.registerBuffer("buffer1", buffer1)
dumpManager.registerBuffer("buffer2", buffer2)
- // WHEN a normal dump is requested
- val args = arrayOf<String>()
- dumpManager.dumpNormal(pw, args, tailLength = 2)
+ // WHEN the collection is requested
+ val buffers = dumpManager.getLogBuffers()
+
+ // WHEN the collection changes
+ dumpManager.registerBuffer("buffer3", buffer1)
- // THEN the normal module and all buffers are dumped
- verify(dumpable1, never()).dump(any(), any())
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3).dump(pw, args)
- verify(buffer1).dump(pw, tailLength = 2)
- verify(buffer2).dump(pw, tailLength = 2)
+ // THEN the new entry is represented
+ assertThat(dumpManager.getLogBuffers()).hasSize(3)
+
+ // AND the previous collection is unchanged
+ assertThat(buffers).hasSize(2)
}
@Test
- fun testUnregister() {
- // GIVEN a variety of registered dumpables and buffers
- dumpManager.registerCriticalDumpable("dumpable1", dumpable1)
- dumpManager.registerCriticalDumpable("dumpable2", dumpable2)
- dumpManager.registerNormalDumpable("dumpable3", dumpable3)
+ fun getTableBuffers_returnsSafeCollection() {
+ // GIVEN a set of registered buffers
+ dumpManager.registerTableLogBuffer("table1", table1)
+ dumpManager.registerTableLogBuffer("table2", table2)
- dumpManager.unregisterDumpable("dumpable2")
- dumpManager.unregisterDumpable("dumpable3")
+ // WHEN the collection is requested
+ val tables = dumpManager.getTableLogBuffers()
+
+ // WHEN the collection changes
+ dumpManager.registerTableLogBuffer("table3", table1)
- // WHEN a dumpables dump is requested
- val args = arrayOf<String>()
- dumpManager.dumpDumpables(pw, args)
+ // THEN the new entry is represented
+ assertThat(dumpManager.getTableLogBuffers()).hasSize(3)
- // THEN the unregistered dumpables (both normal and critical) are not dumped
- verify(dumpable1).dump(pw, args)
- verify(dumpable2, never()).dump(any(), any())
- verify(dumpable3, never()).dump(any(), any())
+ // AND the previous collection is unchanged
+ assertThat(tables).hasSize(2)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
index cb38846a0514..3ff72028d5ca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/dump/LogEulogizerTest.kt
@@ -18,20 +18,14 @@ package com.android.systemui.dump
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpHandler.Companion.dump
+import com.android.systemui.log.LogBuffer
import com.android.systemui.util.io.FakeBasicFileAttributes
import com.android.systemui.util.io.Files
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
import com.android.systemui.util.time.FakeSystemClock
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
import java.io.BufferedWriter
import java.io.ByteArrayOutputStream
import java.io.IOException
@@ -42,17 +36,29 @@ import java.nio.file.OpenOption
import java.nio.file.Paths
import java.nio.file.attribute.BasicFileAttributes
import java.util.Arrays
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
@SmallTest
class LogEulogizerTest : SysuiTestCase() {
lateinit var eulogizer: LogBufferEulogizer
- @Mock
- lateinit var dumpManager: DumpManager
+ @Mock lateinit var dumpManager: DumpManager
+ @Mock lateinit var logBuffer1: LogBuffer
+ lateinit var logBufferEntry1: DumpsysEntry.LogBufferEntry
+ @Mock lateinit var logBuffer2: LogBuffer
+ lateinit var logBufferEntry2: DumpsysEntry.LogBufferEntry
- @Mock
- lateinit var files: Files
+ @Mock lateinit var files: Files
private val clock = FakeSystemClock()
@@ -67,37 +73,47 @@ class LogEulogizerTest : SysuiTestCase() {
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
+ logBufferEntry1 = DumpsysEntry.LogBufferEntry(logBuffer1, "logbuffer1")
+ logBufferEntry2 = DumpsysEntry.LogBufferEntry(logBuffer2, "logbuffer2")
- eulogizer =
- LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
+ eulogizer = LogBufferEulogizer(dumpManager, clock, files, path, MIN_WRITE_GAP, MAX_READ_AGE)
Mockito.`when`(files.newBufferedWriter(eq(path), any(OpenOption::class.java)))
- .thenReturn(fileWriter)
+ .thenReturn(fileWriter)
Mockito.`when`(
- files.readAttributes(eq(path),
- eq(BasicFileAttributes::class.java),
- any(LinkOption::class.java))
- ).thenReturn(fileAttrs)
+ files.readAttributes(
+ eq(path),
+ eq(BasicFileAttributes::class.java),
+ any(LinkOption::class.java)
+ )
+ )
+ .thenReturn(fileAttrs)
Mockito.`when`(files.lines(eq(path))).thenReturn(Arrays.stream(FAKE_LINES))
+
+ whenever(dumpManager.getLogBuffers()).thenReturn(listOf(logBufferEntry1, logBufferEntry2))
}
@Test
fun testFileIsCreated() {
// GIVEN that the log file doesn't already exist
Mockito.`when`(
- files.readAttributes(eq(path),
- eq(BasicFileAttributes::class.java),
- any(LinkOption::class.java))
- ).thenThrow(IOException("File not found"))
+ files.readAttributes(
+ eq(path),
+ eq(BasicFileAttributes::class.java),
+ any(LinkOption::class.java)
+ )
+ )
+ .thenThrow(IOException("File not found"))
// WHEN .record() is called
val exception = RuntimeException("Something bad happened")
assertEquals(exception, eulogizer.record(exception))
// THEN the buffers are dumped to the file
- verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+ verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
+ verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
assertTrue(fileStream.toString().isNotEmpty())
}
@@ -111,7 +127,8 @@ class LogEulogizerTest : SysuiTestCase() {
assertEquals(exception, eulogizer.record(exception))
// THEN the buffers are dumped to the file
- verify(dumpManager).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+ verify(logBuffer1).dump(any(PrintWriter::class.java), anyInt())
+ verify(logBuffer2).dump(any(PrintWriter::class.java), anyInt())
assertTrue(fileStream.toString().isNotEmpty())
}
@@ -125,7 +142,8 @@ class LogEulogizerTest : SysuiTestCase() {
assertEquals(exception, eulogizer.record(exception))
// THEN the file isn't written to
- verify(dumpManager, never()).dumpBuffers(any(PrintWriter::class.java), Mockito.anyInt())
+ verify(logBuffer1, never()).dump(any(PrintWriter::class.java), anyInt())
+ verify(logBuffer2, never()).dump(any(PrintWriter::class.java), anyInt())
assertTrue(fileStream.toString().isEmpty())
}
@@ -161,9 +179,4 @@ class LogEulogizerTest : SysuiTestCase() {
private const val MIN_WRITE_GAP = 10L
private const val MAX_READ_AGE = 100L
-private val FAKE_LINES =
- arrayOf(
- "First line",
- "Second line",
- "Third line"
- )
+private val FAKE_LINES = arrayOf("First line", "Second line", "Third line")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
index c9ee1e8ef5b9..6aa5a00c36da 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -67,6 +67,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.plugins.GlobalActions;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.phone.CentralSurfaces;
@@ -128,6 +129,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
@Mock private UserContextProvider mUserContextProvider;
@Mock private VibratorHelper mVibratorHelper;
@Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private ShadeController mShadeController;
@Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
@Mock private DialogLaunchAnimator mDialogLaunchAnimator;
@Mock private OnBackInvokedDispatcher mOnBackInvokedDispatcher;
@@ -177,6 +179,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
mHandler,
mPackageManager,
Optional.of(mCentralSurfaces),
+ mShadeController,
mKeyguardUpdateMonitor,
mDialogLaunchAnimator);
mGlobalActionsDialogLite.setZeroDialogPressDelayForTesting();
@@ -317,7 +320,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
gestureListener.onFling(start, end, 0, 1000);
verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
- verify(mCentralSurfaces).animateExpandSettingsPanel(null);
+ verify(mShadeController).animateExpandQs();
}
@Test
@@ -341,7 +344,7 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
MotionEvent end = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 500, 0);
gestureListener.onFling(start, end, 0, 1000);
verifyLogPosted(GlobalActionsDialogLite.GlobalActionsEvent.GA_CLOSE_TAP_OUTSIDE);
- verify(mCentralSurfaces).animateExpandNotificationsPanel();
+ verify(mShadeController).animateExpandShade();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
index 477e076669b7..22308414547a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt
@@ -1,6 +1,7 @@
package com.android.systemui.keyguard
import android.app.ActivityManager
+import android.app.WallpaperManager
import android.app.WindowConfiguration
import android.graphics.Point
import android.graphics.Rect
@@ -21,6 +22,7 @@ import com.android.systemui.statusbar.NotificationShadeWindowController
import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.phone.BiometricUnlockController
import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argThat
import com.android.systemui.util.mockito.whenever
import junit.framework.Assert.assertEquals
@@ -32,6 +34,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
+import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
@@ -64,6 +67,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
private lateinit var notificationShadeWindowController: NotificationShadeWindowController
@Mock
private lateinit var powerManager: PowerManager
+ @Mock
+ private lateinit var wallpaperManager: WallpaperManager
@Mock
private lateinit var launcherUnlockAnimationController: ILauncherUnlockAnimationController.Stub
@@ -94,13 +99,14 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
keyguardUnlockAnimationController = KeyguardUnlockAnimationController(
context, keyguardStateController, { keyguardViewMediator }, keyguardViewController,
featureFlags, { biometricUnlockController }, statusBarStateController,
- notificationShadeWindowController, powerManager
+ notificationShadeWindowController, powerManager, wallpaperManager
)
keyguardUnlockAnimationController.setLauncherUnlockController(
launcherUnlockAnimationController)
whenever(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java))
whenever(powerManager.isInteractive).thenReturn(true)
+ whenever(wallpaperManager.isLockscreenLiveWallpaperEnabled).thenReturn(false)
// All of these fields are final, so we can't mock them, but are needed so that the surface
// appear amount setter doesn't short circuit.
@@ -173,6 +179,46 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() {
false /* cancelled */)
}
+ @Test
+ fun onWakeAndUnlock_notifiesListenerWithTrue() {
+ whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+ whenever(biometricUnlockController.mode).thenReturn(
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+
+ val listener = mock(
+ KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
+
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
+ remoteAnimationTargets,
+ wallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
+ )
+
+ verify(listener).onUnlockAnimationStarted(any(), eq(true), any(), any())
+ }
+
+ @Test
+ fun onWakeAndUnlockFromDream_notifiesListenerWithFalse() {
+ whenever(biometricUnlockController.isWakeAndUnlock).thenReturn(true)
+ whenever(biometricUnlockController.mode).thenReturn(
+ BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+ val listener = mock(
+ KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener::class.java)
+ keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(listener)
+
+ keyguardUnlockAnimationController.notifyStartSurfaceBehindRemoteAnimation(
+ remoteAnimationTargets,
+ wallpaperTargets,
+ 0 /* startTime */,
+ false /* requestedShowSurfaceBehindKeyguard */
+ )
+
+ verify(listener).onUnlockAnimationStarted(any(), eq(false), any(), any())
+ }
+
/**
* If we requested that the surface behind be made visible, and we're not flinging away the
* keyguard, it means that we're swiping to unlock and want the surface visible so it can follow
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index 111b8e83a984..d36e77889810 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -92,8 +92,8 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
@Test
- fun affordance_walletNotEnabled_modelIsNone() = runBlockingTest {
- setUpState(isWalletEnabled = false)
+ fun affordance_walletFeatureNotEnabled_modelIsNone() = runBlockingTest {
+ setUpState(isWalletFeatureAvailable = false)
var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
@@ -165,7 +165,7 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
@Test
fun getPickerScreenState_disabledWhenTheFeatureIsNotEnabled() = runTest {
setUpState(
- isWalletEnabled = false,
+ isWalletFeatureAvailable = false,
)
assertThat(underTest.getPickerScreenState())
@@ -183,16 +183,15 @@ class QuickAccessWalletKeyguardQuickAffordanceConfigTest : SysuiTestCase() {
}
private fun setUpState(
- isWalletEnabled: Boolean = true,
+ isWalletFeatureAvailable: Boolean = true,
isWalletServiceAvailable: Boolean = true,
isWalletQuerySuccessful: Boolean = true,
hasSelectedCard: Boolean = true,
) {
- whenever(walletController.isWalletEnabled).thenReturn(isWalletEnabled)
-
val walletClient: QuickAccessWalletClient = mock()
whenever(walletClient.tileIcon).thenReturn(ICON)
whenever(walletClient.isWalletServiceAvailable).thenReturn(isWalletServiceAvailable)
+ whenever(walletClient.isWalletFeatureAvailable).thenReturn(isWalletFeatureAvailable)
whenever(walletController.walletClient).thenReturn(walletClient)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
index 705b485ce1b4..9dba9b5b3c3e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialogTest.java
@@ -16,6 +16,8 @@
package com.android.systemui.media.dialog;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.mock;
@@ -33,6 +35,10 @@ import android.media.session.MediaSessionManager;
import android.os.PowerExemptionManager;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
import androidx.test.filters.SmallTest;
@@ -44,6 +50,7 @@ import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
import com.android.settingslib.media.BluetoothMediaDevice;
import com.android.settingslib.media.LocalMediaManager;
import com.android.settingslib.media.MediaDevice;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
@@ -53,11 +60,14 @@ import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.google.common.base.Strings;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -68,6 +78,9 @@ import java.util.Optional;
public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
private static final String TEST_PACKAGE = "test_package";
+ private static final String BROADCAST_NAME_TEST = "Broadcast_name_test";
+ private static final String BROADCAST_CODE_TEST = "112233";
+ private static final String BROADCAST_CODE_UPDATE_TEST = "11223344";
// Mock
private final MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
@@ -106,6 +119,9 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(null);
+ when(mLocalBluetoothLeBroadcast.getProgramInfo()).thenReturn(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothLeBroadcast.getBroadcastCode()).thenReturn(
+ BROADCAST_CODE_TEST.getBytes(StandardCharsets.UTF_8));
mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
mMediaSessionManager, mLocalBluetoothManager, mStarter,
@@ -194,4 +210,152 @@ public class MediaOutputBroadcastDialogTest extends SysuiTestCase {
verify(mLocalBluetoothLeBroadcastAssistant, times(1)).addSource(any(), any(), anyBoolean());
}
+
+ @Test
+ public void handleLeBroadcastMetadataChanged_checkBroadcastName() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ final TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_summary);
+
+ mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged();
+
+ assertThat(broadcastName.getText().toString()).isEqualTo(BROADCAST_NAME_TEST);
+ }
+
+ @Test
+ public void handleLeBroadcastMetadataChanged_checkBroadcastCode() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+
+ final TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_summary);
+
+ mMediaOutputBroadcastDialog.handleLeBroadcastMetadataChanged();
+
+ assertThat(broadcastCode.getText().toString()).isEqualTo(BROADCAST_CODE_TEST);
+ }
+
+ @Test
+ public void updateBroadcastInfo_stopBroadcastFailed_handleFailedUi() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
+ broadcastCodeEdit.callOnClick();
+
+ mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+ assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(1);
+
+ mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+ assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(2);
+
+ // It will be the MAX Retry Count = 3
+ mMediaOutputBroadcastDialog.updateBroadcastInfo(true, BROADCAST_CODE_UPDATE_TEST);
+ assertThat(mMediaOutputBroadcastDialog.getRetryCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void afterTextChanged_nameLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 3);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_enterValidNameAfterLengthMoreThanMax_noErrorMessage() {
+ ImageView broadcastNameEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_name_edit);
+ TextView broadcastName = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_name_summary);
+ broadcastName.setText(BROADCAST_NAME_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastNameEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String testString = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH + 2);
+ editText.setText(testString);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+
+ // input the valid text
+ testString = Strings.repeat("b",
+ MediaOutputBroadcastDialog.BROADCAST_NAME_MAX_LENGTH - 100);
+ editText.setText(testString);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthMoreThanMax_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MAX_LENGTH + 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void afterTextChanged_codeLengthLessThanMin_showErrorMessage() {
+ ImageView broadcastCodeEdit = mMediaOutputBroadcastDialog.mDialogView
+ .requireViewById(R.id.broadcast_code_edit);
+ TextView broadcastCode = mMediaOutputBroadcastDialog.mDialogView.requireViewById(
+ R.id.broadcast_code_summary);
+ broadcastCode.setText(BROADCAST_CODE_UPDATE_TEST);
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ broadcastCodeEdit.callOnClick();
+ EditText editText = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_edit_text);
+ TextView broadcastErrorMessage = mMediaOutputBroadcastDialog.mAlertDialog.findViewById(
+ R.id.broadcast_error_message);
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.INVISIBLE);
+
+ // input the invalid text
+ String moreThanMax = Strings.repeat("a",
+ MediaOutputBroadcastDialog.BROADCAST_CODE_MIN_LENGTH - 1);
+ editText.setText(moreThanMax);
+
+ assertThat(broadcastErrorMessage.getVisibility()).isEqualTo(View.VISIBLE);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
index 810ab344e7d8..d98bcee1e01a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -67,8 +67,8 @@ import com.android.systemui.qs.logging.QSLogger;
import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.settings.UserFileManager;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.ShadeController;
import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.FakeSharedPreferences;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -86,7 +86,6 @@ import org.mockito.stubbing.Answer;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
-import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -110,7 +109,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private Provider<AutoTileManager> mAutoTiles;
@Mock
- private CentralSurfaces mCentralSurfaces;
+ private ShadeController mShadeController;
@Mock
private QSLogger mQSLogger;
@Mock
@@ -161,7 +160,7 @@ public class QSTileHostTest extends SysuiTestCase {
mSecureSettings = new FakeSettings();
saveSetting("");
mQSTileHost = new TestQSTileHost(mContext, mDefaultFactory, mMainExecutor,
- mPluginManager, mTunerService, mAutoTiles, mCentralSurfaces,
+ mPluginManager, mTunerService, mAutoTiles, mShadeController,
mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
mTileLifecycleManagerFactory, mUserFileManager, mFeatureFlags);
@@ -682,13 +681,13 @@ public class QSTileHostTest extends SysuiTestCase {
QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles,
- CentralSurfaces centralSurfaces, QSLogger qsLogger,
+ ShadeController shadeController, QSLogger qsLogger,
UserTracker userTracker, SecureSettings secureSettings,
CustomTileStatePersister customTileStatePersister,
TileLifecycleManager.Factory tileLifecycleManagerFactory,
UserFileManager userFileManager, FeatureFlags featureFlags) {
super(context, defaultFactory, mainExecutor, pluginManager,
- tunerService, autoTiles, Optional.of(centralSurfaces), qsLogger,
+ tunerService, autoTiles, shadeController, qsLogger,
userTracker, secureSettings, customTileStatePersister,
tileLifecycleManagerFactory, userFileManager, featureFlags);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 45783abe9ee4..6556cfd22901 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -18,8 +18,7 @@ package com.android.systemui.qs.pipeline.domain.interactor
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.statusbar.phone.CentralSurfaces
-import java.util.Optional
+import com.android.systemui.shade.ShadeController
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -31,7 +30,7 @@ import org.mockito.MockitoAnnotations
@SmallTest
class PanelInteractorImplTest : SysuiTestCase() {
- @Mock private lateinit var centralSurfaces: CentralSurfaces
+ @Mock private lateinit var shadeController: ShadeController
@Before
fun setup() {
@@ -40,37 +39,28 @@ class PanelInteractorImplTest : SysuiTestCase() {
@Test
fun openPanels_callsCentralSurfaces() {
- val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+ val underTest = PanelInteractorImpl(shadeController)
underTest.openPanels()
- verify(centralSurfaces).postAnimateOpenPanels()
+ verify(shadeController).postAnimateExpandQs()
}
@Test
fun collapsePanels_callsCentralSurfaces() {
- val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+ val underTest = PanelInteractorImpl(shadeController)
underTest.collapsePanels()
- verify(centralSurfaces).postAnimateCollapsePanels()
+ verify(shadeController).postAnimateCollapseShade()
}
@Test
fun forceCollapsePanels_callsCentralSurfaces() {
- val underTest = PanelInteractorImpl(Optional.of(centralSurfaces))
+ val underTest = PanelInteractorImpl(shadeController)
underTest.forceCollapsePanels()
- verify(centralSurfaces).postAnimateForceCollapsePanels()
- }
-
- @Test
- fun whenOptionalEmpty_doesnThrow() {
- val underTest = PanelInteractorImpl(Optional.empty())
-
- underTest.openPanels()
- underTest.collapsePanels()
- underTest.forceCollapsePanels()
+ verify(shadeController).postAnimateForceCollapseShade()
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
index f2812b5857b7..59b595393749 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -226,6 +226,17 @@ public class RecordingServiceTest extends SysuiTestCase {
}
@Test
+ public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_showsErrorNotification()
+ throws IOException {
+ doReturn(true).when(mController).isRecording();
+ doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+
+ mRecordingService.onStopped();
+
+ verify(mRecordingService).createErrorNotification();
+ }
+
+ @Test
public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
throws IOException {
doReturn(true).when(mController).isRecording();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
new file mode 100644
index 000000000000..fbb77cdc3049
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SaveImageInBackgroundTaskTest.kt
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot
+
+import android.app.Notification
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Intent
+import android.graphics.Bitmap
+import android.graphics.drawable.Icon
+import android.net.Uri
+import android.os.UserHandle
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.screenshot.ScreenshotController.SaveImageInBackgroundData
+import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import java.util.concurrent.CompletableFuture
+import java.util.function.Supplier
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Test
+
+@SmallTest
+class SaveImageInBackgroundTaskTest : SysuiTestCase() {
+ private val imageExporter = mock<ImageExporter>()
+ private val smartActions = mock<ScreenshotSmartActions>()
+ private val smartActionsProvider = mock<ScreenshotNotificationSmartActionsProvider>()
+ private val saveImageData = SaveImageInBackgroundData()
+ private val sharedTransitionSupplier =
+ mock<Supplier<ScreenshotController.SavedImageData.ActionTransition>>()
+ private val testScreenshotId: String = "testScreenshotId"
+ private val testBitmap = mock<Bitmap>()
+ private val testUser = UserHandle.getUserHandleForUid(0)
+ private val testIcon = mock<Icon>()
+ private val testImageTime = 1234.toLong()
+ private val flags = FakeFeatureFlags()
+
+ private val smartActionsUriFuture = mock<CompletableFuture<List<Notification.Action>>>()
+ private val smartActionsFuture = mock<CompletableFuture<List<Notification.Action>>>()
+
+ private val testUri: Uri = Uri.parse("testUri")
+ private val intent =
+ Intent(Intent.ACTION_SEND)
+ .setComponent(
+ ComponentName.unflattenFromString(
+ "com.google.android.test/com.google.android.test.TestActivity"
+ )
+ )
+ private val immutablePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+ private val mutablePendingIntent =
+ PendingIntent.getBroadcast(
+ mContext,
+ 0,
+ intent,
+ PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_MUTABLE
+ )
+
+ private val saveImageTask =
+ SaveImageInBackgroundTask(
+ mContext,
+ flags,
+ imageExporter,
+ smartActions,
+ saveImageData,
+ sharedTransitionSupplier,
+ smartActionsProvider,
+ )
+
+ @Before
+ fun setup() {
+ whenever(
+ smartActions.getSmartActionsFuture(
+ eq(testScreenshotId),
+ any(Uri::class.java),
+ eq(testBitmap),
+ eq(smartActionsProvider),
+ any(ScreenshotSmartActionType::class.java),
+ any(Boolean::class.java),
+ eq(testUser)
+ )
+ )
+ .thenReturn(smartActionsUriFuture)
+ whenever(
+ smartActions.getSmartActionsFuture(
+ eq(testScreenshotId),
+ eq(null),
+ eq(testBitmap),
+ eq(smartActionsProvider),
+ any(ScreenshotSmartActionType::class.java),
+ any(Boolean::class.java),
+ eq(testUser)
+ )
+ )
+ .thenReturn(smartActionsFuture)
+ }
+
+ @Test
+ fun testQueryQuickShare_noAction() {
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(ArrayList<Notification.Action>())
+
+ val quickShareAction =
+ saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testQueryQuickShare_withActions() {
+ val actions = ArrayList<Notification.Action>()
+ actions.add(constructAction("Action One", mutablePendingIntent))
+ actions.add(constructAction("Action Two", mutablePendingIntent))
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.queryQuickShareAction(testScreenshotId, testBitmap, testUser, testUri)!!
+
+ assertEquals("Action One", quickShareAction.title)
+ assertEquals(mutablePendingIntent, quickShareAction.actionIntent)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_originalWasNull_returnsNull() {
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ null,
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser
+ )
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_immutableIntentDifferentAction_returnsNull() {
+ val actions = ArrayList<Notification.Action>()
+ actions.add(constructAction("New Test Action", immutablePendingIntent))
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+ val origAction = constructAction("Old Test Action", immutablePendingIntent)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ origAction,
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser,
+ )
+
+ assertNull(quickShareAction)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_mutableIntent_returnsSafeIntent() {
+ val actions = ArrayList<Notification.Action>()
+ val action = constructAction("Action One", mutablePendingIntent)
+ actions.add(action)
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ constructAction("Test Action", mutablePendingIntent),
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser
+ )
+ val quickSharePendingIntent =
+ quickShareAction.actionIntent.intent.extras!!.getParcelable(
+ ScreenshotController.EXTRA_ACTION_INTENT,
+ PendingIntent::class.java
+ )
+
+ assertEquals("Test Action", quickShareAction.title)
+ assertEquals(mutablePendingIntent, quickSharePendingIntent)
+ }
+
+ @Test
+ fun testCreateQuickShareAction_immutableIntent_returnsSafeIntent() {
+ val actions = ArrayList<Notification.Action>()
+ val action = constructAction("Test Action", immutablePendingIntent)
+ actions.add(action)
+ whenever(
+ smartActions.getSmartActions(
+ eq(testScreenshotId),
+ eq(smartActionsUriFuture),
+ any(Int::class.java),
+ eq(smartActionsProvider),
+ eq(ScreenshotSmartActionType.QUICK_SHARE_ACTION)
+ )
+ )
+ .thenReturn(actions)
+
+ val quickShareAction =
+ saveImageTask.createQuickShareAction(
+ constructAction("Test Action", immutablePendingIntent),
+ testScreenshotId,
+ testUri,
+ testImageTime,
+ testBitmap,
+ testUser,
+ )!!
+
+ assertEquals("Test Action", quickShareAction.title)
+ assertEquals(
+ immutablePendingIntent,
+ quickShareAction.actionIntent.intent.extras!!.getParcelable(
+ ScreenshotController.EXTRA_ACTION_INTENT,
+ PendingIntent::class.java
+ )
+ )
+ }
+
+ private fun constructAction(title: String, intent: PendingIntent): Notification.Action {
+ return Notification.Action.Builder(testIcon, title, intent).build()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index b1f8475f7d74..470c824eb60f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -1100,6 +1100,13 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo
}
@Test
+ public void onShadeFlingEnd_mExpandImmediateShouldBeReset() {
+ mNotificationPanelViewController.onFlingEnd(false);
+
+ verify(mQsController).setExpandImmediate(false);
+ }
+
+ @Test
public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
mStatusBarStateController.setState(SHADE);
enableSplitShade(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
new file mode 100644
index 000000000000..ef66756790d8
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ShadeControllerImplTest.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.WindowManager
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.assist.AssistManager
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.policy.DeviceProvisionedController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.window.StatusBarWindowController
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ShadeControllerImplTest : SysuiTestCase() {
+ @Mock private lateinit var commandQueue: CommandQueue
+ @Mock private lateinit var keyguardStateController: KeyguardStateController
+ @Mock private lateinit var statusBarStateController: StatusBarStateController
+ @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock private lateinit var statusBarWindowController: StatusBarWindowController
+ @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
+ @Mock private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock private lateinit var windowManager: WindowManager
+ @Mock private lateinit var assistManager: AssistManager
+ @Mock private lateinit var gutsManager: NotificationGutsManager
+ @Mock private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock private lateinit var display: Display
+
+ private lateinit var shadeController: ShadeControllerImpl
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(windowManager.defaultDisplay).thenReturn(display)
+ whenever(deviceProvisionedController.isCurrentUserSetup).thenReturn(true)
+ shadeController =
+ ShadeControllerImpl(
+ commandQueue,
+ FakeExecutor(FakeSystemClock()),
+ keyguardStateController,
+ statusBarStateController,
+ statusBarKeyguardViewManager,
+ statusBarWindowController,
+ deviceProvisionedController,
+ notificationShadeWindowController,
+ windowManager,
+ Lazy { assistManager },
+ Lazy { gutsManager },
+ )
+ shadeController.setNotificationPanelViewController(notificationPanelViewController)
+ }
+
+ @Test
+ fun testDisableNotificationShade() {
+ whenever(commandQueue.panelsEnabled()).thenReturn(false)
+
+ // Trying to open it does nothing.
+ shadeController.animateExpandShade()
+ verify(notificationPanelViewController, never()).expandToNotifications()
+ shadeController.animateExpandQs()
+ verify(notificationPanelViewController, never()).expand(ArgumentMatchers.anyBoolean())
+ }
+
+ @Test
+ fun testEnableNotificationShade() {
+ whenever(commandQueue.panelsEnabled()).thenReturn(true)
+
+ // Can now be opened.
+ shadeController.animateExpandShade()
+ verify(notificationPanelViewController).expandToNotifications()
+ shadeController.animateExpandQs()
+ verify(notificationPanelViewController).expandToQs()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
index 109f185c625e..22c9e45d48af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java
@@ -76,5 +76,6 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID);
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+ verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
index ea822aa00429..a3ecde0fe976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java
@@ -75,5 +75,6 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
mKeyboardShortcuts.toggle(mContext, DEVICE_ID);
verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt());
+ verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
index 39ed5535ff3b..914301f2e830 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/events/SystemStatusAnimationSchedulerImplTest.kt
@@ -60,9 +60,13 @@ import org.mockito.MockitoAnnotations
class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
@Mock private lateinit var systemEventCoordinator: SystemEventCoordinator
+
@Mock private lateinit var statusBarWindowController: StatusBarWindowController
+
@Mock private lateinit var statusBarContentInsetProvider: StatusBarContentInsetsProvider
+
@Mock private lateinit var dumpManager: DumpManager
+
@Mock private lateinit var listener: SystemStatusAnimationCallback
private lateinit var systemClock: FakeSystemClock
@@ -380,6 +384,32 @@ class SystemStatusAnimationSchedulerImplTest : SysuiTestCase() {
}
@Test
+ fun testPrivacyDot_isRemovedDuringChipDisappearAnimation() = runTest {
+ // Instantiate class under test with TestScope from runTest
+ initializeSystemStatusAnimationScheduler(testScope = this)
+
+ // create and schedule high priority event
+ createAndScheduleFakePrivacyEvent()
+
+ // fast forward to ANIMATING_OUT state
+ fastForwardAnimationToState(ANIMATING_OUT)
+ assertEquals(ANIMATING_OUT, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onSystemStatusAnimationTransitionToPersistentDot(any())
+
+ // remove persistent dot
+ systemStatusAnimationScheduler.removePersistentDot()
+ testScheduler.runCurrent()
+
+ // skip disappear animation
+ animatorTestRule.advanceTimeBy(DISAPPEAR_ANIMATION_DURATION)
+ testScheduler.runCurrent()
+
+ // verify that animationState changes to IDLE and onHidePersistentDot callback is invoked
+ assertEquals(IDLE, systemStatusAnimationScheduler.getAnimationState())
+ verify(listener, times(1)).onHidePersistentDot()
+ }
+
+ @Test
fun testPrivacyEvent_forceVisibleIsUpdated_whenRescheduledDuringQueuedState() = runTest {
// Instantiate class under test with TestScope from runTest
initializeSystemStatusAnimationScheduler(testScope = this)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
index 2fbe87158eba..ea70e9e44c66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -32,7 +32,6 @@ import com.android.systemui.keyguard.shared.model.TransitionState
import com.android.systemui.keyguard.shared.model.TransitionStep
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.StatusBarState
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
@@ -46,11 +45,14 @@ import com.android.systemui.statusbar.notification.interruption.KeyguardNotifica
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
+import java.util.function.Consumer
+import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.TestCoroutineScheduler
@@ -62,9 +64,8 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.same
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import java.util.function.Consumer
-import kotlin.time.Duration.Companion.seconds
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -75,7 +76,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
private val keyguardRepository = FakeKeyguardRepository()
private val keyguardTransitionRepository = FakeKeyguardTransitionRepository()
- private val notifPipelineFlags: NotifPipelineFlags = mock()
private val notifPipeline: NotifPipeline = mock()
private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
private val statusBarStateController: StatusBarStateController = mock()
@@ -136,13 +136,8 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
)
testScheduler.runCurrent()
- // WHEN: The shade is expanded
- whenever(statusBarStateController.isExpanded).thenReturn(true)
- statusBarStateListener.onExpandedChanged(true)
- testScheduler.runCurrent()
-
- // THEN: The notification is still treated as "unseen" and is not filtered out.
- assertThat(unseenFilter.shouldFilterOut(fakeEntry, 0L)).isFalse()
+ // THEN: We are no longer listening for shade expansions
+ verify(statusBarStateController, never()).addCallback(any())
}
}
@@ -152,6 +147,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(false)
runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+
// WHEN: A notification is posted
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
@@ -162,6 +161,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
// WHEN: The keyguard is now showing
keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+ )
testScheduler.runCurrent()
// THEN: The notification is recognized as "seen" and is filtered out.
@@ -169,6 +171,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
// WHEN: The keyguard goes away
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.GONE)
+ )
testScheduler.runCurrent()
// THEN: The notification is shown regardless
@@ -182,9 +187,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
- val fakeEntry = NotificationEntryBuilder()
+ val fakeEntry =
+ NotificationEntryBuilder()
.setNotification(Notification.Builder(mContext, "id").setOngoing(true).build())
- .build()
+ .build()
collectionListener.onEntryAdded(fakeEntry)
// WHEN: The keyguard is now showing
@@ -202,11 +208,13 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
keyguardRepository.setKeyguardShowing(false)
whenever(statusBarStateController.isExpanded).thenReturn(true)
runKeyguardCoordinatorTest {
- val fakeEntry = NotificationEntryBuilder().build().apply {
- row = mock<ExpandableNotificationRow>().apply {
- whenever(isMediaRow).thenReturn(true)
+ val fakeEntry =
+ NotificationEntryBuilder().build().apply {
+ row =
+ mock<ExpandableNotificationRow>().apply {
+ whenever(isMediaRow).thenReturn(true)
+ }
}
- }
collectionListener.onEntryAdded(fakeEntry)
// WHEN: The keyguard is now showing
@@ -299,14 +307,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
runKeyguardCoordinatorTest {
// WHEN: A new notification is posted
val fakeSummary = NotificationEntryBuilder().build()
- val fakeChild = NotificationEntryBuilder()
+ val fakeChild =
+ NotificationEntryBuilder()
.setGroup(context, "group")
.setGroupSummary(context, false)
.build()
- GroupEntryBuilder()
- .setSummary(fakeSummary)
- .addChild(fakeChild)
- .build()
+ GroupEntryBuilder().setSummary(fakeSummary).addChild(fakeChild).build()
collectionListener.onEntryAdded(fakeSummary)
collectionListener.onEntryAdded(fakeChild)
@@ -331,6 +337,10 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
runKeyguardCoordinatorTest {
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.AOD, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
// WHEN: five seconds have passed
testScheduler.advanceTimeBy(5.seconds)
@@ -338,10 +348,16 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
testScheduler.runCurrent()
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.AOD)
+ )
testScheduler.runCurrent()
// THEN: The notification is now recognized as "seen" and is filtered out.
@@ -354,11 +370,17 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
// GIVEN: Keyguard is showing, unseen notification is present
keyguardRepository.setKeyguardShowing(true)
runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
val fakeEntry = NotificationEntryBuilder().build()
collectionListener.onEntryAdded(fakeEntry)
// WHEN: Keyguard is no longer showing
keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
// WHEN: Keyguard is shown again
keyguardRepository.setKeyguardShowing(true)
@@ -369,14 +391,212 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
}
}
+ @Test
+ fun unseenNotificationIsNotMarkedAsSeenIfNotOnKeyguardLongEnough() {
+ // GIVEN: Keyguard is showing, not dozing, unseen notification is present
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ val firstEntry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(firstEntry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: another unseen notification is posted
+ val secondEntry = NotificationEntryBuilder().setId(2).build()
+ collectionListener.onEntryAdded(secondEntry)
+ testScheduler.runCurrent()
+
+ // WHEN: four more seconds have passed
+ testScheduler.advanceTimeBy(4.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The first notification is considered seen and is filtered out.
+ assertThat(unseenFilter.shouldFilterOut(firstEntry, 0L)).isTrue()
+
+ // THEN: The second notification is still considered unseen and is not filtered out
+ assertThat(unseenFilter.shouldFilterOut(secondEntry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedAfterThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: five more seconds have passed
+ testScheduler.advanceTimeBy(5.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is removed
+ collectionListener.onEntryRemoved(entry, 0)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is re-posted
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one more second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfRemovedBeforeThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is removed
+ collectionListener.onEntryRemoved(entry, 0)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is re-posted
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one more second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
+ @Test
+ fun unseenNotificationOnKeyguardNotMarkedAsSeenIfUpdatedBeforeThreshold() {
+ // GIVEN: Keyguard is showing, not dozing
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardRepository.setIsDozing(false)
+ runKeyguardCoordinatorTest {
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: a new notification is posted
+ val entry = NotificationEntryBuilder().setId(1).build()
+ collectionListener.onEntryAdded(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: one second has passed
+ testScheduler.advanceTimeBy(1.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the notification is updated
+ collectionListener.onEntryUpdated(entry)
+ testScheduler.runCurrent()
+
+ // WHEN: four more seconds have passed
+ testScheduler.advanceTimeBy(4.seconds)
+ testScheduler.runCurrent()
+
+ // WHEN: the keyguard is no longer showing
+ keyguardRepository.setKeyguardShowing(false)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GONE)
+ )
+ testScheduler.runCurrent()
+
+ // WHEN: Keyguard is shown again
+ keyguardRepository.setKeyguardShowing(true)
+ keyguardTransitionRepository.sendTransitionStep(
+ TransitionStep(from = KeyguardState.GONE, to = KeyguardState.LOCKSCREEN)
+ )
+ testScheduler.runCurrent()
+
+ // THEN: The notification is considered unseen and is not filtered out.
+ assertThat(unseenFilter.shouldFilterOut(entry, 0L)).isFalse()
+ }
+ }
+
private fun runKeyguardCoordinatorTest(
testBlock: suspend KeyguardCoordinatorTestScope.() -> Unit
) {
val testDispatcher = UnconfinedTestDispatcher()
val testScope = TestScope(testDispatcher)
- val fakeSettings = FakeSettings().apply {
- putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
- }
+ val fakeSettings =
+ FakeSettings().apply {
+ putInt(Settings.Secure.LOCK_SCREEN_SHOW_ONLY_UNSEEN_NOTIFICATIONS, 1)
+ }
val seenNotificationsProvider = SeenNotificationsProviderImpl()
val keyguardCoordinator =
KeyguardCoordinator(
@@ -387,7 +607,6 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
keyguardRepository,
keyguardTransitionRepository,
mock<KeyguardCoordinatorLogger>(),
- notifPipelineFlags,
testScope.backgroundScope,
sectionHeaderVisibilityProvider,
fakeSettings,
@@ -397,11 +616,12 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
keyguardCoordinator.attach(notifPipeline)
testScope.runTest(dispatchTimeoutMs = 1.seconds.inWholeMilliseconds) {
KeyguardCoordinatorTestScope(
- keyguardCoordinator,
- testScope,
- seenNotificationsProvider,
- fakeSettings,
- ).testBlock()
+ keyguardCoordinator,
+ testScope,
+ seenNotificationsProvider,
+ fakeSettings,
+ )
+ .testBlock()
}
}
@@ -414,10 +634,9 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
val testScheduler: TestCoroutineScheduler
get() = scope.testScheduler
- val onStateChangeListener: Consumer<String> =
- withArgCaptor {
- verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
- }
+ val onStateChangeListener: Consumer<String> = withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
val unseenFilter: NotifFilter
get() = keyguardCoordinator.unseenNotifFilter
@@ -426,11 +645,11 @@ class KeyguardCoordinatorTest : SysuiTestCase() {
verify(notifPipeline).addCollectionListener(capture())
}
- val onHeadsUpChangedListener: OnHeadsUpChangedListener get() =
- withArgCaptor { verify(headsUpManager).addListener(capture()) }
+ val onHeadsUpChangedListener: OnHeadsUpChangedListener
+ get() = withArgCaptor { verify(headsUpManager).addListener(capture()) }
- val statusBarStateListener: StatusBarStateController.StateListener get() =
- withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
+ val statusBarStateListener: StatusBarStateController.StateListener
+ get() = withArgCaptor { verify(statusBarStateController).addCallback(capture()) }
var showOnlyUnseenNotifsOnKeyguardSetting: Boolean
get() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
index 915924f13197..bdc8135707bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowDragControllerTest.java
@@ -108,7 +108,7 @@ public class ExpandableNotificationRowDragControllerTest extends SysuiTestCase {
mRow.doDragCallback(0, 0);
verify(controller).startDragAndDrop(mRow);
- verify(mShadeController).animateCollapsePanels(eq(0), eq(true),
+ verify(mShadeController).animateCollapseShade(eq(0), eq(true),
eq(false), anyFloat());
verify(mNotificationPanelLogger, times(1)).logNotificationDrag(any());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 9938fa8700fd..0e966dc655f7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -35,6 +35,7 @@ import static org.mockito.Mockito.when;
import android.content.res.Resources;
import android.metrics.LogMaker;
import android.testing.AndroidTestingRunner;
+import android.view.View;
import androidx.test.filters.SmallTest;
@@ -71,6 +72,7 @@ import com.android.systemui.statusbar.notification.collection.render.GroupExpans
import com.android.systemui.statusbar.notification.collection.render.NotifStats;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController.NotificationPanelEvent;
@@ -106,6 +108,7 @@ import java.util.Optional;
public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private NotificationGutsManager mNotificationGutsManager;
+ @Mock private NotificationsController mNotificationsController;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
@Mock private HeadsUpManagerPhone mHeadsUpManager;
@Mock private NotificationRoundnessManager mNotificationRoundnessManager;
@@ -432,6 +435,84 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
verify(mNotificationStackScrollLayout).setStatusBarState(KEYGUARD);
}
+ @Test
+ public void updateImportantForAccessibility_noChild_onKeyGuard_notImportantForA11y() {
+ // GIVEN: Controller is attached, active notifications is empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is true
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
+ mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should not be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ @Test
+ public void updateImportantForAccessibility_hasChild_onKeyGuard_importantForA11y() {
+ // GIVEN: Controller is attached, active notifications is not empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is true
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(true);
+ mController.getNotifStackController().setNotifStats(
+ new NotifStats(
+ /* numActiveNotifs = */ 1,
+ /* hasNonClearableAlertingNotifs = */ false,
+ /* hasClearableAlertingNotifs = */ false,
+ /* hasNonClearableSilentNotifs = */ false,
+ /* hasClearableSilentNotifs = */ false)
+ );
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ @Test
+ public void updateImportantForAccessibility_hasChild_notOnKeyGuard_importantForA11y() {
+ // GIVEN: Controller is attached, active notifications is not empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is false
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
+ mController.getNotifStackController().setNotifStats(
+ new NotifStats(
+ /* numActiveNotifs = */ 1,
+ /* hasNonClearableAlertingNotifs = */ false,
+ /* hasClearableAlertingNotifs = */ false,
+ /* hasNonClearableSilentNotifs = */ false,
+ /* hasClearableSilentNotifs = */ false)
+ );
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
+ @Test
+ public void updateImportantForAccessibility_noChild_notOnKeyGuard_importantForA11y() {
+ // GIVEN: Controller is attached, active notifications is empty,
+ // and mNotificationStackScrollLayout.onKeyguard() is false
+ initController(/* viewIsAttached= */ true);
+ when(mNotificationStackScrollLayout.onKeyguard()).thenReturn(false);
+ mController.getNotifStackController().setNotifStats(NotifStats.getEmpty());
+
+ // WHEN: call updateImportantForAccessibility
+ mController.updateImportantForAccessibility();
+
+ // THEN: mNotificationStackScrollLayout should be important for A11y
+ verify(mNotificationStackScrollLayout)
+ .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
+
private LogMaker logMatcher(int category, int type) {
return argThat(new LogMatcher(category, type));
}
@@ -455,6 +536,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mNotificationStackScrollLayout,
true,
mNotificationGutsManager,
+ mNotificationsController,
mVisibilityProvider,
mHeadsUpManager,
mNotificationRoundnessManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
index a4ee349f5b71..ee02a7b6e090 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutTest.java
@@ -41,6 +41,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
@@ -79,9 +80,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupExpansionManager;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
+import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.FooterView;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
@@ -114,7 +115,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
private TestableResources mTestableResources;
@Rule public MockitoRule mockito = MockitoJUnit.rule();
- @Mock private CentralSurfaces mCentralSurfaces;
+ @Mock private NotificationsController mNotificationsController;
@Mock private SysuiStatusBarStateController mBarState;
@Mock private GroupMembershipManager mGroupMembershipManger;
@Mock private GroupExpansionManager mGroupExpansionManager;
@@ -181,7 +182,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mNotificationStackSizeCalculator);
mStackScroller = spy(mStackScrollerInternal);
mStackScroller.setShelfController(notificationShelfController);
- mStackScroller.setCentralSurfaces(mCentralSurfaces);
+ mStackScroller.setNotificationsController(mNotificationsController);
mStackScroller.setEmptyShadeView(mEmptyShadeView);
when(mStackScrollLayoutController.isHistoryEnabled()).thenReturn(true);
when(mStackScrollLayoutController.getNotificationRoundnessManager())
@@ -575,10 +576,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.inflateFooterView();
// add notification
- ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
- NotificationEntry entry = mock(NotificationEntry.class);
- when(row.getEntry()).thenReturn(entry);
- when(entry.isClearable()).thenReturn(true);
+ ExpandableNotificationRow row = createClearableRow();
mStackScroller.addContainerView(row);
mStackScroller.onUpdateRowStates();
@@ -648,6 +646,50 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
+ public void testClearNotifications_clearAllInProgress() {
+ ExpandableNotificationRow row = createClearableRow();
+ when(row.getEntry().hasFinishedInitialization()).thenReturn(true);
+ doReturn(true).when(mStackScroller).isVisible(row);
+ mStackScroller.addContainerView(row);
+
+ mStackScroller.clearNotifications(ROWS_ALL, false);
+
+ assertClearAllInProgress(true);
+ verify(mNotificationRoundnessManager).setClearAllInProgress(true);
+ }
+
+ @Test
+ public void testOnChildAnimationFinished_resetsClearAllInProgress() {
+ mStackScroller.setClearAllInProgress(true);
+
+ mStackScroller.onChildAnimationFinished();
+
+ assertClearAllInProgress(false);
+ verify(mNotificationRoundnessManager).setClearAllInProgress(false);
+ }
+
+ @Test
+ public void testShadeCollapsed_resetsClearAllInProgress() {
+ mStackScroller.setClearAllInProgress(true);
+
+ mStackScroller.setIsExpanded(false);
+
+ assertClearAllInProgress(false);
+ verify(mNotificationRoundnessManager).setClearAllInProgress(false);
+ }
+
+ @Test
+ public void testShadeExpanded_doesntChangeClearAllInProgress() {
+ mStackScroller.setClearAllInProgress(true);
+ clearInvocations(mNotificationRoundnessManager);
+
+ mStackScroller.setIsExpanded(true);
+
+ assertClearAllInProgress(true);
+ verify(mNotificationRoundnessManager, never()).setClearAllInProgress(anyBoolean());
+ }
+
+ @Test
public void testAddNotificationUpdatesSpeedBumpIndex() {
// initial state calculated == 0
assertEquals(0, mStackScroller.getSpeedBumpIndex());
@@ -794,7 +836,7 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
}
@Test
- public void onShadeClosesWithAnimationWillResetSwipeState() {
+ public void onShadeClosesWithAnimationWillResetTouchState() {
// GIVEN shade is expanded
mStackScroller.setIsExpanded(true);
clearInvocations(mNotificationSwipeHelper);
@@ -804,12 +846,12 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setIsExpanded(false);
mStackScroller.onExpansionStopped();
- // VERIFY swipe is reset
- verify(mNotificationSwipeHelper).resetSwipeState();
+ // VERIFY touch is reset
+ verify(mNotificationSwipeHelper).resetTouchState();
}
@Test
- public void onShadeClosesWithoutAnimationWillResetSwipeState() {
+ public void onShadeClosesWithoutAnimationWillResetTouchState() {
// GIVEN shade is expanded
mStackScroller.setIsExpanded(true);
clearInvocations(mNotificationSwipeHelper);
@@ -817,8 +859,8 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
// WHEN closing the shade without the animation
mStackScroller.setIsExpanded(false);
- // VERIFY swipe is reset
- verify(mNotificationSwipeHelper).resetSwipeState();
+ // VERIFY touch is reset
+ verify(mNotificationSwipeHelper).resetTouchState();
}
@Test
@@ -896,6 +938,21 @@ public class NotificationStackScrollLayoutTest extends SysuiTestCase {
mStackScroller.setStatusBarState(state);
}
+ private ExpandableNotificationRow createClearableRow() {
+ ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
+ NotificationEntry entry = mock(NotificationEntry.class);
+ when(row.canViewBeCleared()).thenReturn(true);
+ when(row.getEntry()).thenReturn(entry);
+ when(entry.isClearable()).thenReturn(true);
+
+ return row;
+ }
+
+ private void assertClearAllInProgress(boolean expected) {
+ assertEquals(expected, mStackScroller.getClearAllInProgress());
+ assertEquals(expected, mAmbientState.isClearAllInProgress());
+ }
+
private static void mockBoundsOnScreen(View view, Rect bounds) {
doAnswer(invocation -> {
Rect out = invocation.getArgument(0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index 3870d996d2ae..cb71fb8f703a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -153,12 +152,6 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
verify(mCentralSurfaces).updateQsExpansionEnabled();
verify(mShadeController).animateCollapseShade();
-
- // Trying to open it does nothing.
- mSbcqCallbacks.animateExpandNotificationsPanel();
- verify(mShadeViewController, never()).expandToNotifications();
- mSbcqCallbacks.animateExpandSettingsPanel(null);
- verify(mShadeViewController, never()).expand(anyBoolean());
}
@Test
@@ -171,12 +164,6 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
StatusBarManager.DISABLE2_NONE, false);
verify(mCentralSurfaces).updateQsExpansionEnabled();
verify(mShadeController, never()).animateCollapseShade();
-
- // Can now be opened.
- mSbcqCallbacks.animateExpandNotificationsPanel();
- verify(mShadeViewController).expandToNotifications();
- mSbcqCallbacks.animateExpandSettingsPanel(null);
- verify(mShadeViewController).expandToQs();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 4ed113fe1358..4fb5b0735fcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -431,10 +431,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mShadeController = spy(new ShadeControllerImpl(
mCommandQueue,
+ mMainExecutor,
mKeyguardStateController,
mStatusBarStateController,
mStatusBarKeyguardViewManager,
mStatusBarWindowController,
+ mDeviceProvisionedController,
mNotificationShadeWindowController,
mContext.getSystemService(WindowManager.class),
() -> mAssistManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
index 3dec45b4ff9f..b9c7e6133669 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/UnfoldHapticsPlayerTest.kt
@@ -15,6 +15,7 @@
*/
package com.android.systemui.unfold
+import android.os.VibrationAttributes
import android.os.VibrationEffect
import android.os.Vibrator
import android.testing.AndroidTestingRunner
@@ -53,7 +54,7 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() {
progressProvider.onTransitionProgress(0.5f)
progressProvider.onTransitionFinishing()
- verify(vibrator).vibrate(any<VibrationEffect>())
+ verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
}
@Test
@@ -64,7 +65,7 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() {
progressProvider.onTransitionProgress(0.99f)
progressProvider.onTransitionFinishing()
- verify(vibrator, never()).vibrate(any<VibrationEffect>())
+ verify(vibrator, never()).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
}
@Test
@@ -84,7 +85,7 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() {
progressProvider.onTransitionFinished()
testFoldProvider.onFoldUpdate(isFolded = true)
- verify(vibrator, never()).vibrate(any<VibrationEffect>())
+ verify(vibrator, never()).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
}
@Test
@@ -112,6 +113,6 @@ class UnfoldHapticsPlayerTest : SysuiTestCase() {
progressProvider.onTransitionFinishing()
progressProvider.onTransitionFinished()
- verify(vibrator).vibrate(any<VibrationEffect>())
+ verify(vibrator).vibrate(any<VibrationEffect>(), any<VibrationAttributes>())
}
}
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 6a0c69015df9..21986b73c808 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -958,7 +958,13 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
final ComponentName menuToMigrate =
AccessibilityUtils.getAccessibilityMenuComponentToMigrate(mPackageManager, userId);
if (menuToMigrate != null) {
- mPackageManager.setComponentEnabledSetting(
+ // PackageManager#setComponentEnabledSetting disables the component for only the user
+ // linked to PackageManager's context, but mPackageManager is linked to the system user,
+ // so grab a new PackageManager for the current user to support secondary users.
+ final PackageManager userPackageManager =
+ mContext.createContextAsUser(UserHandle.of(userId), /* flags = */ 0)
+ .getPackageManager();
+ userPackageManager.setComponentEnabledSetting(
menuToMigrate,
PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
PackageManager.DONT_KILL_APP);
@@ -1845,6 +1851,9 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub
// find out a way to detect the device finished the OTA and switch the user.
migrateAccessibilityButtonSettingsIfNecessaryLocked(userState, null,
/* restoreFromSdkInt = */0);
+ // Package components are disabled per user, so secondary users also need their migrated
+ // Accessibility Menu component disabled.
+ disableAccessibilityMenuToMigrateIfNeeded();
if (announceNewUser) {
// Schedule announcement of the current user if needed.
diff --git a/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java
new file mode 100644
index 000000000000..b4aca1530204
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/LogFieldClassificationScoreOnResultListener.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill;
+
+import android.annotation.Nullable;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.service.autofill.FieldClassification;
+import android.service.autofill.FillEventHistory.Event.NoSaveReason;
+import android.util.Slog;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager.AutofillCommitReason;
+
+import java.util.ArrayList;
+
+class LogFieldClassificationScoreOnResultListener implements
+ RemoteCallback.OnResultListener {
+
+ private static final String TAG = "LogFieldClassificationScoreOnResultListener";
+
+ private Session mSession;
+ private final @NoSaveReason int mSaveDialogNotShowReason;
+ private final @AutofillCommitReason int mCommitReason;
+ private final int mViewsSize;
+ private final AutofillId[] mAutofillIds;
+ private final String[] mUserValues;
+ private final String[] mCategoryIds;
+ private final ArrayList<AutofillId> mDetectedFieldIds;
+ private final ArrayList<FieldClassification> mDetectedFieldClassifications;
+ LogFieldClassificationScoreOnResultListener(Session session,
+ int saveDialogNotShowReason,
+ int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues,
+ String[] categoryIds, ArrayList<AutofillId> detectedFieldIds,
+ ArrayList<FieldClassification> detectedFieldClassifications) {
+ this.mSession = session;
+ this.mSaveDialogNotShowReason = saveDialogNotShowReason;
+ this.mCommitReason = commitReason;
+ this.mViewsSize = viewsSize;
+ this.mAutofillIds = autofillIds;
+ this.mUserValues = userValues;
+ this.mCategoryIds = categoryIds;
+ this.mDetectedFieldIds = detectedFieldIds;
+ this.mDetectedFieldClassifications = detectedFieldClassifications;
+ }
+
+ public void onResult(@Nullable Bundle result) {
+ // Create a local copy to safe guard race condition
+ Session session = mSession;
+ if (session == null) {
+ Slog.wtf(TAG, "session is null when calling onResult()");
+ return;
+ }
+ session.handleLogFieldClassificationScore(
+ result,
+ mSaveDialogNotShowReason,
+ mCommitReason,
+ mViewsSize,
+ mAutofillIds,
+ mUserValues,
+ mCategoryIds,
+ mDetectedFieldIds,
+ mDetectedFieldClassifications);
+ mSession = null;
+ }
+}
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 1bfdb435bbb1..a5c4ac750458 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -184,6 +184,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@@ -3129,76 +3130,91 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
}
// Then use the results, asynchronously
- final RemoteCallback callback = new RemoteCallback((result) -> {
- if (result == null) {
- if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
- logContextCommitted(null, null, saveDialogNotShowReason, commitReason);
- return;
- }
- final Scores scores = result.getParcelable(EXTRA_SCORES, android.service.autofill.AutofillFieldClassificationService.Scores.class);
- if (scores == null) {
- Slog.w(TAG, "No field classification score on " + result);
- return;
- }
- int i = 0, j = 0;
- try {
- // Iteract over all autofill fields first
- for (i = 0; i < viewsSize; i++) {
- final AutofillId autofillId = autofillIds[i];
-
- // Search the best scores for each category (as some categories could have
- // multiple user values
- ArrayMap<String, Float> scoresByField = null;
- for (j = 0; j < userValues.length; j++) {
- final String categoryId = categoryIds[j];
- final float score = scores.scores[i][j];
- if (score > 0) {
- if (scoresByField == null) {
- scoresByField = new ArrayMap<>(userValues.length);
- }
- final Float currentScore = scoresByField.get(categoryId);
- if (currentScore != null && currentScore > score) {
- if (sVerbose) {
- Slog.v(TAG, "skipping score " + score
- + " because it's less than " + currentScore);
- }
- continue;
- }
+ final RemoteCallback callback = new RemoteCallback(
+ new LogFieldClassificationScoreOnResultListener(
+ this,
+ saveDialogNotShowReason,
+ commitReason,
+ viewsSize,
+ autofillIds,
+ userValues,
+ categoryIds,
+ detectedFieldIds,
+ detectedFieldClassifications));
+
+ fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
+ defaultAlgorithm, defaultArgs, algorithms, args);
+ }
+
+ void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason,
+ int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues,
+ String[] categoryIds, ArrayList<AutofillId> detectedFieldIds,
+ ArrayList<FieldClassification> detectedFieldClassifications) {
+ if (result == null) {
+ if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results");
+ logContextCommitted(null, null, saveDialogNotShowReason, commitReason);
+ return;
+ }
+ final Scores scores = result.getParcelable(EXTRA_SCORES,
+ android.service.autofill.AutofillFieldClassificationService.Scores.class);
+ if (scores == null) {
+ Slog.w(TAG, "No field classification score on " + result);
+ return;
+ }
+ int i = 0, j = 0;
+ try {
+ // Iteract over all autofill fields first
+ for (i = 0; i < viewsSize; i++) {
+ final AutofillId autofillId = autofillIds[i];
+
+ // Search the best scores for each category (as some categories could have
+ // multiple user values
+ ArrayMap<String, Float> scoresByField = null;
+ for (j = 0; j < userValues.length; j++) {
+ final String categoryId = categoryIds[j];
+ final float score = scores.scores[i][j];
+ if (score > 0) {
+ if (scoresByField == null) {
+ scoresByField = new ArrayMap<>(userValues.length);
+ }
+ final Float currentScore = scoresByField.get(categoryId);
+ if (currentScore != null && currentScore > score) {
if (sVerbose) {
- Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
- + autofillId);
+ Slog.v(TAG, "skipping score " + score
+ + " because it's less than " + currentScore);
}
- scoresByField.put(categoryId, score);
- } else if (sVerbose) {
- Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
+ continue;
}
+ if (sVerbose) {
+ Slog.v(TAG, "adding score " + score + " at index " + j + " and id "
+ + autofillId);
+ }
+ scoresByField.put(categoryId, score);
+ } else if (sVerbose) {
+ Slog.v(TAG, "skipping score 0 at index " + j + " and id " + autofillId);
}
- if (scoresByField == null) {
- if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
- continue;
- }
-
- // Then create the matches for that autofill id
- final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
- for (j = 0; j < scoresByField.size(); j++) {
- final String fieldId = scoresByField.keyAt(j);
- final float score = scoresByField.valueAt(j);
- matches.add(new Match(fieldId, score));
- }
- detectedFieldIds.add(autofillId);
- detectedFieldClassifications.add(new FieldClassification(matches));
- } // for i
- } catch (ArrayIndexOutOfBoundsException e) {
- wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
- return;
- }
-
- logContextCommitted(detectedFieldIds, detectedFieldClassifications,
- saveDialogNotShowReason, commitReason);
- });
+ }
+ if (scoresByField == null) {
+ if (sVerbose) Slog.v(TAG, "no score for autofillId=" + autofillId);
+ continue;
+ }
- fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds,
- defaultAlgorithm, defaultArgs, algorithms, args);
+ // Then create the matches for that autofill id
+ final ArrayList<Match> matches = new ArrayList<>(scoresByField.size());
+ for (j = 0; j < scoresByField.size(); j++) {
+ final String fieldId = scoresByField.keyAt(j);
+ final float score = scoresByField.valueAt(j);
+ matches.add(new Match(fieldId, score));
+ }
+ detectedFieldIds.add(autofillId);
+ detectedFieldClassifications.add(new FieldClassification(matches));
+ } // for i
+ } catch (ArrayIndexOutOfBoundsException e) {
+ wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e);
+ return;
+ }
+ logContextCommitted(detectedFieldIds, detectedFieldClassifications,
+ saveDialogNotShowReason, commitReason);
}
/**
@@ -4925,16 +4941,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return null;
}
- final boolean isWhitelisted = mService
+ final boolean isAllowlisted = mService
.isWhitelistedForAugmentedAutofillLocked(mComponentName);
- if (!isWhitelisted) {
+ if (!isAllowlisted) {
if (sVerbose) {
Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
+ ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
}
logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
- mCurrentViewId, isWhitelisted, /* isInline= */ null);
+ mCurrentViewId, isAllowlisted, /* isInline= */ null);
return null;
}
@@ -4967,32 +4983,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
final AutofillId focusedId = mCurrentViewId;
- final Function<InlineFillUi, Boolean> inlineSuggestionsResponseCallback =
- response -> {
- synchronized (mLock) {
- return mInlineSessionController.setInlineFillUiLocked(response);
- }
- };
final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
- (inlineSuggestionsRequest) -> {
- synchronized (mLock) {
- logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
- focusedId, isWhitelisted, inlineSuggestionsRequest != null);
- remoteService.onRequestAutofillLocked(id, mClient,
- taskId, mComponentName, mActivityToken,
- AutofillId.withoutSession(focusedId), currentValue,
- inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
- /*onErrorCallback=*/ () -> {
- synchronized (mLock) {
- cancelAugmentedAutofillLocked();
-
- // Also cancel augmented in IME
- mInlineSessionController.setInlineFillUiLocked(
- InlineFillUi.emptyUi(mCurrentViewId));
- }
- }, mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
- }
- };
+ new AugmentedAutofillInlineSuggestionRequestConsumer(
+ this, focusedId, isAllowlisted, mode, currentValue);
// When the inline suggestion render service is available and the view is focused, there
// are 3 cases when augmented autofill should ask IME for inline suggestion request,
@@ -5010,14 +5003,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
|| mSessionFlags.mExpiredResponse)
&& (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) {
if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
- remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
- (extras) -> {
- synchronized (mLock) {
- mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
- focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
- extras);
- }
- }, mHandler));
+ remoteRenderService.getInlineSuggestionsRendererInfo(
+ new RemoteCallback(
+ new AugmentedAutofillInlineSuggestionRendererOnResultListener(
+ this, focusedId, requestAugmentedAutofill),
+ mHandler));
} else {
requestAugmentedAutofill.accept(
mInlineSessionController.getInlineSuggestionsRequestLocked().orElse(null));
@@ -5028,6 +5018,169 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState
return mAugmentedAutofillDestroyer;
}
+ private static class AugmentedAutofillInlineSuggestionRendererOnResultListener
+ implements RemoteCallback.OnResultListener {
+
+ WeakReference<Session> mSessionWeakRef;
+ final AutofillId mFocusedId;
+ Consumer<InlineSuggestionsRequest> mRequestAugmentedAutofill;
+
+ AugmentedAutofillInlineSuggestionRendererOnResultListener(
+ Session session,
+ AutofillId focussedId,
+ Consumer<InlineSuggestionsRequest> requestAugmentedAutofill) {
+ mSessionWeakRef = new WeakReference<>(session);
+ mFocusedId = focussedId;
+ mRequestAugmentedAutofill = requestAugmentedAutofill;
+ }
+
+ @Override
+ public void onResult(@Nullable Bundle result) {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(
+ session, "AugmentedAutofillInlineSuggestionRendererOnResultListener:")) {
+ return;
+ }
+ synchronized (session.mLock) {
+ session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+ mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill,
+ result);
+ }
+ }
+ }
+
+ private static class AugmentedAutofillInlineSuggestionRequestConsumer
+ implements Consumer<InlineSuggestionsRequest> {
+
+ WeakReference<Session> mSessionWeakRef;
+ final AutofillId mFocusedId;
+ final boolean mIsAllowlisted;
+ final int mMode;
+ final AutofillValue mCurrentValue;
+
+ AugmentedAutofillInlineSuggestionRequestConsumer(
+ Session session,
+ AutofillId focussedId,
+ boolean isAllowlisted,
+ int mode,
+ AutofillValue currentValue) {
+ mSessionWeakRef = new WeakReference<>(session);
+ mFocusedId = focussedId;
+ mIsAllowlisted = isAllowlisted;
+ mMode = mode;
+ mCurrentValue = currentValue;
+
+ }
+ @Override
+ public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(
+ session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) {
+ return;
+ }
+ session.onAugmentedAutofillInlineSuggestionAccept(
+ inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue);
+
+ }
+ }
+
+ private static class AugmentedAutofillInlineSuggestionsResponseCallback
+ implements Function<InlineFillUi, Boolean> {
+
+ WeakReference<Session> mSessionWeakRef;
+
+ AugmentedAutofillInlineSuggestionsResponseCallback(Session session) {
+ this.mSessionWeakRef = new WeakReference<>(session);
+ }
+
+ @Override
+ public Boolean apply(InlineFillUi inlineFillUi) {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(
+ session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) {
+ return false;
+ }
+
+ synchronized (session.mLock) {
+ return session.mInlineSessionController.setInlineFillUiLocked(inlineFillUi);
+ }
+ }
+ }
+
+ private static class AugmentedAutofillErrorCallback implements Runnable {
+
+ WeakReference<Session> mSessionWeakRef;
+
+ AugmentedAutofillErrorCallback(Session session) {
+ this.mSessionWeakRef = new WeakReference<>(session);
+ }
+
+ @Override
+ public void run() {
+ Session session = mSessionWeakRef.get();
+
+ if (logIfSessionNull(session, "AugmentedAutofillErrorCallback:")) {
+ return;
+ }
+ session.onAugmentedAutofillErrorCallback();
+ }
+ }
+
+ /**
+ * If the session is null or has been destroyed, log the error msg, and return true.
+ * This is a helper function intended to be called when de-referencing from a weak reference.
+ * @param session
+ * @param logPrefix
+ * @return true if the session is null, false otherwise.
+ */
+ private static boolean logIfSessionNull(Session session, String logPrefix) {
+ if (session == null) {
+ Slog.wtf(TAG, logPrefix + " Session null");
+ return true;
+ }
+ if (session.mDestroyed) {
+ // TODO: Update this to return in this block. We aren't doing this to preserve the
+ // behavior, but can be modified once we have more time to soak the changes.
+ Slog.w(TAG, logPrefix + " Session destroyed, but following through");
+ // Follow-through
+ }
+ return false;
+ }
+
+ private void onAugmentedAutofillInlineSuggestionAccept(
+ InlineSuggestionsRequest inlineSuggestionsRequest,
+ AutofillId focussedId,
+ boolean isAllowlisted,
+ int mode,
+ AutofillValue currentValue) {
+ synchronized (mLock) {
+ final RemoteAugmentedAutofillService remoteService =
+ mService.getRemoteAugmentedAutofillServiceLocked();
+ logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+ focussedId, isAllowlisted, inlineSuggestionsRequest != null);
+ remoteService.onRequestAutofillLocked(id, mClient,
+ taskId, mComponentName, mActivityToken,
+ AutofillId.withoutSession(focussedId), currentValue,
+ inlineSuggestionsRequest,
+ new AugmentedAutofillInlineSuggestionsResponseCallback(this),
+ new AugmentedAutofillErrorCallback(this),
+ mService.getRemoteInlineSuggestionRenderServiceLocked(), userId);
+ }
+ }
+
+ private void onAugmentedAutofillErrorCallback() {
+ synchronized (mLock) {
+ cancelAugmentedAutofillLocked();
+
+ // Also cancel augmented in IME
+ mInlineSessionController.setInlineFillUiLocked(
+ InlineFillUi.emptyUi(mCurrentViewId));
+ }
+ }
+
@GuardedBy("mLock")
private void cancelAugmentedAutofillLocked() {
final RemoteAugmentedAutofillService remoteService = mService
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index 51359add8fd1..cfd9f16541f2 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -50,6 +50,7 @@ import android.content.Context;
import android.content.pm.ActivityPresentationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
import android.database.ContentObserver;
import android.os.Binder;
@@ -69,6 +70,7 @@ import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
import android.provider.Settings;
import android.service.contentcapture.ActivityEvent.ActivityEventType;
+import android.service.contentcapture.ContentCaptureServiceInfo;
import android.service.contentcapture.IDataShareCallback;
import android.service.contentcapture.IDataShareReadAdapter;
import android.service.voice.VoiceInteractionManagerInternal;
@@ -79,6 +81,7 @@ import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.view.contentcapture.ContentCaptureCondition;
+import android.view.contentcapture.ContentCaptureEvent;
import android.view.contentcapture.ContentCaptureHelper;
import android.view.contentcapture.ContentCaptureManager;
import android.view.contentcapture.DataRemovalRequest;
@@ -88,11 +91,15 @@ import android.view.contentcapture.IContentCaptureOptionsCallback;
import android.view.contentcapture.IDataShareWriteAdapter;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.infra.AbstractRemoteService;
import com.android.internal.infra.GlobalWhitelistState;
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.DumpUtils;
import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.ContentProtectionPackageManager;
+import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.infra.AbstractMasterSystemService;
import com.android.server.infra.FrameworkResourcesServiceNameResolver;
@@ -117,7 +124,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
* with other sources to provide contextual data in other areas of the system
* such as Autofill.
*/
-public final class ContentCaptureManagerService extends
+public class ContentCaptureManagerService extends
AbstractMasterSystemService<ContentCaptureManagerService, ContentCapturePerUserService> {
private static final String TAG = ContentCaptureManagerService.class.getSimpleName();
@@ -205,6 +212,10 @@ public final class ContentCaptureManagerService extends
final GlobalContentCaptureOptions mGlobalContentCaptureOptions =
new GlobalContentCaptureOptions();
+ @Nullable private final ComponentName mContentProtectionServiceComponentName;
+
+ @Nullable private final ContentProtectionBlocklistManager mContentProtectionBlocklistManager;
+
public ContentCaptureManagerService(@NonNull Context context) {
super(context, new FrameworkResourcesServiceNameResolver(context,
com.android.internal.R.string.config_defaultContentCaptureService),
@@ -242,6 +253,20 @@ public final class ContentCaptureManagerService extends
mServiceNameResolver.getServiceName(userId),
mServiceNameResolver.isTemporary(userId));
}
+
+ if (getEnableContentProtectionReceiverLocked()) {
+ mContentProtectionServiceComponentName = getContentProtectionServiceComponentName();
+ if (mContentProtectionServiceComponentName != null) {
+ mContentProtectionBlocklistManager = createContentProtectionBlocklistManager();
+ mContentProtectionBlocklistManager.updateBlocklist(
+ mDevCfgContentProtectionAppsBlocklistSize);
+ } else {
+ mContentProtectionBlocklistManager = null;
+ }
+ } else {
+ mContentProtectionServiceComponentName = null;
+ mContentProtectionBlocklistManager = null;
+ }
}
@Override // from AbstractMasterSystemService
@@ -397,7 +422,9 @@ public final class ContentCaptureManagerService extends
}
}
- private void setFineTuneParamsFromDeviceConfig() {
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ protected void setFineTuneParamsFromDeviceConfig() {
synchronized (mLock) {
mDevCfgMaxBufferSize =
DeviceConfig.getInt(
@@ -443,6 +470,8 @@ public final class ContentCaptureManagerService extends
ContentCaptureManager
.DEVICE_CONFIG_PROPERTY_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE,
ContentCaptureManager.DEFAULT_CONTENT_PROTECTION_APPS_BLOCKLIST_SIZE);
+ // mContentProtectionBlocklistManager.updateBlocklist not called on purpose here to keep
+ // it immutable at this point
mDevCfgContentProtectionBufferSize =
DeviceConfig.getInt(
DeviceConfig.NAMESPACE_CONTENT_CAPTURE,
@@ -754,6 +783,98 @@ public final class ContentCaptureManagerService extends
mGlobalContentCaptureOptions.dump(prefix2, pw);
}
+ /**
+ * Used by the constructor in order to be able to override the value in the tests.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @GuardedBy("mLock")
+ protected boolean getEnableContentProtectionReceiverLocked() {
+ return mDevCfgEnableContentProtectionReceiver;
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @NonNull
+ protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
+ return new ContentProtectionBlocklistManager(
+ new ContentProtectionPackageManager(getContext()));
+ }
+
+ @Nullable
+ private ComponentName getContentProtectionServiceComponentName() {
+ String flatComponentName = getContentProtectionServiceFlatComponentName();
+ ComponentName componentName = ComponentName.unflattenFromString(flatComponentName);
+ if (componentName == null) {
+ return null;
+ }
+
+ // Check permissions by trying to construct {@link ContentCaptureServiceInfo}
+ try {
+ createContentProtectionServiceInfo(componentName);
+ } catch (Exception ex) {
+ // Swallow, exception was already logged
+ return null;
+ }
+
+ return componentName;
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @Nullable
+ protected String getContentProtectionServiceFlatComponentName() {
+ return getContext()
+ .getString(com.android.internal.R.string.config_defaultContentProtectionService);
+ }
+
+ /**
+ * Can also throw runtime exceptions such as {@link SecurityException}.
+ *
+ * @hide
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @NonNull
+ protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
+ @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+ return new ContentCaptureServiceInfo(
+ getContext(), componentName, /* isTemp= */ false, UserHandle.getCallingUserId());
+ }
+
+ @Nullable
+ private RemoteContentProtectionService createRemoteContentProtectionService() {
+ if (mContentProtectionServiceComponentName == null) {
+ // This case should not be possible but make sure
+ return null;
+ }
+ synchronized (mLock) {
+ if (!mDevCfgEnableContentProtectionReceiver) {
+ return null;
+ }
+ }
+ return createRemoteContentProtectionService(mContentProtectionServiceComponentName);
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @NonNull
+ protected RemoteContentProtectionService createRemoteContentProtectionService(
+ @NonNull ComponentName componentName) {
+ return new RemoteContentProtectionService(
+ getContext(),
+ componentName,
+ UserHandle.getCallingUserId(),
+ isBindInstantServiceAllowed());
+ }
+
+ /** @hide */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ @NonNull
+ protected ContentCaptureManagerServiceStub getContentCaptureManagerServiceStub() {
+ return mContentCaptureManagerServiceStub;
+ }
+
final class ContentCaptureManagerServiceStub extends IContentCaptureManager.Stub {
@Override
@@ -987,6 +1108,19 @@ public final class ContentCaptureManagerService extends
public void setDefaultServiceEnabled(@UserIdInt int userId, boolean enabled) {
ContentCaptureManagerService.this.setDefaultServiceEnabled(userId, enabled);
}
+
+ @Override
+ public void onLoginDetected(@NonNull ParceledListSlice<ContentCaptureEvent> events) {
+ RemoteContentProtectionService service = createRemoteContentProtectionService();
+ if (service == null) {
+ return;
+ }
+ try {
+ service.onLoginDetected(events);
+ } catch (Exception ex) {
+ Slog.e(TAG, "Failed to call remote service", ex);
+ }
+ }
}
private final class LocalService extends ContentCaptureManagerInternal {
@@ -1075,14 +1209,21 @@ public final class ContentCaptureManagerService extends
@GuardedBy("mGlobalWhitelistStateLock")
public ContentCaptureOptions getOptions(@UserIdInt int userId,
@NonNull String packageName) {
- boolean packageWhitelisted;
+ boolean isContentCaptureReceiverEnabled;
+ boolean isContentProtectionReceiverEnabled;
ArraySet<ComponentName> whitelistedComponents = null;
+
synchronized (mGlobalWhitelistStateLock) {
- packageWhitelisted = isWhitelisted(userId, packageName);
- if (!packageWhitelisted) {
- // Full package is not allowlisted: check individual components first
+ isContentCaptureReceiverEnabled =
+ isContentCaptureReceiverEnabled(userId, packageName);
+ isContentProtectionReceiverEnabled =
+ isContentProtectionReceiverEnabled(packageName);
+
+ if (!isContentCaptureReceiverEnabled) {
+ // Full package is not allowlisted: check individual components next
whitelistedComponents = getWhitelistedComponents(userId, packageName);
- if (whitelistedComponents == null
+ if (!isContentProtectionReceiverEnabled
+ && whitelistedComponents == null
&& packageName.equals(mServicePackages.get(userId))) {
// No components allowlisted either, but let it go because it's the
// service's own package
@@ -1101,7 +1242,9 @@ public final class ContentCaptureManagerService extends
}
}
- if (!packageWhitelisted && whitelistedComponents == null) {
+ if (!isContentCaptureReceiverEnabled
+ && !isContentProtectionReceiverEnabled
+ && whitelistedComponents == null) {
// No can do!
if (verbose) {
Slog.v(TAG, "getOptionsForPackage(" + packageName + "): not whitelisted");
@@ -1118,9 +1261,9 @@ public final class ContentCaptureManagerService extends
mDevCfgTextChangeFlushingFrequencyMs,
mDevCfgLogHistorySize,
mDevCfgDisableFlushForViewTreeAppearing,
- /* enableReceiver= */ true,
+ isContentCaptureReceiverEnabled || whitelistedComponents != null,
new ContentCaptureOptions.ContentProtectionOptions(
- mDevCfgEnableContentProtectionReceiver,
+ isContentProtectionReceiverEnabled,
mDevCfgContentProtectionBufferSize),
whitelistedComponents);
if (verbose) Slog.v(TAG, "getOptionsForPackage(" + packageName + "): " + options);
@@ -1141,6 +1284,36 @@ public final class ContentCaptureManagerService extends
}
}
}
+
+ @Override // from GlobalWhitelistState
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull String packageName) {
+ return isContentCaptureReceiverEnabled(userId, packageName)
+ || isContentProtectionReceiverEnabled(packageName);
+ }
+
+ @Override // from GlobalWhitelistState
+ public boolean isWhitelisted(@UserIdInt int userId, @NonNull ComponentName componentName) {
+ return super.isWhitelisted(userId, componentName)
+ || isContentProtectionReceiverEnabled(componentName.getPackageName());
+ }
+
+ private boolean isContentCaptureReceiverEnabled(
+ @UserIdInt int userId, @NonNull String packageName) {
+ return super.isWhitelisted(userId, packageName);
+ }
+
+ private boolean isContentProtectionReceiverEnabled(@NonNull String packageName) {
+ if (mContentProtectionServiceComponentName == null
+ || mContentProtectionBlocklistManager == null) {
+ return false;
+ }
+ synchronized (mLock) {
+ if (!mDevCfgEnableContentProtectionReceiver) {
+ return false;
+ }
+ }
+ return mContentProtectionBlocklistManager.isAllowed(packageName);
+ }
}
private static class DataShareCallbackDelegate extends IDataShareCallback.Stub {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
index 715cf9a8807e..a0fd28b3f279 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionBlocklistManager.java
@@ -35,7 +35,7 @@ import java.util.stream.Collectors;
*
* @hide
*/
-class ContentProtectionBlocklistManager {
+public class ContentProtectionBlocklistManager {
private static final String TAG = "ContentProtectionBlocklistManager";
@@ -46,7 +46,7 @@ class ContentProtectionBlocklistManager {
@Nullable private Set<String> mPackageNameBlocklist;
- protected ContentProtectionBlocklistManager(
+ public ContentProtectionBlocklistManager(
@NonNull ContentProtectionPackageManager contentProtectionPackageManager) {
mContentProtectionPackageManager = contentProtectionPackageManager;
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
index 1847e5d708a3..4ebac07ec3ea 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/ContentProtectionPackageManager.java
@@ -43,7 +43,7 @@ public class ContentProtectionPackageManager {
@NonNull private final PackageManager mPackageManager;
- ContentProtectionPackageManager(@NonNull Context context) {
+ public ContentProtectionPackageManager(@NonNull Context context) {
mPackageManager = context.getPackageManager();
}
diff --git a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
index 7b34e737b58c..f5e5a431e3dd 100644
--- a/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
+++ b/services/contentcapture/java/com/android/server/contentprotection/RemoteContentProtectionService.java
@@ -37,7 +37,8 @@ import java.time.Duration;
*
* @hide
*/
-class RemoteContentProtectionService extends ServiceConnector.Impl<IContentCaptureDirectManager> {
+public class RemoteContentProtectionService
+ extends ServiceConnector.Impl<IContentCaptureDirectManager> {
private static final String TAG = RemoteContentProtectionService.class.getSimpleName();
@@ -45,7 +46,7 @@ class RemoteContentProtectionService extends ServiceConnector.Impl<IContentCaptu
@NonNull private final ComponentName mComponentName;
- protected RemoteContentProtectionService(
+ public RemoteContentProtectionService(
@NonNull Context context,
@NonNull ComponentName componentName,
int userId,
diff --git a/services/core/java/com/android/server/BootReceiver.java b/services/core/java/com/android/server/BootReceiver.java
index 7d016c82adc5..26421b75c297 100644
--- a/services/core/java/com/android/server/BootReceiver.java
+++ b/services/core/java/com/android/server/BootReceiver.java
@@ -313,6 +313,11 @@ public class BootReceiver extends BroadcastReceiver {
private static final DropboxRateLimiter sDropboxRateLimiter = new DropboxRateLimiter();
+ /** Initialize the rate limiter. */
+ public static void initDropboxRateLimiter() {
+ sDropboxRateLimiter.init();
+ }
+
/**
* Reset the dropbox rate limiter.
*/
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 96b660ea03f1..d4845aa99810 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -70,6 +70,7 @@ import static android.os.PowerExemptionManager.REASON_INSTR_BACKGROUND_FGS_PERMI
import static android.os.PowerExemptionManager.REASON_OPT_OUT_REQUESTED;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_PLATFORM_VPN;
import static android.os.PowerExemptionManager.REASON_OP_ACTIVATE_VPN;
+import static android.os.PowerExemptionManager.REASON_OTHER;
import static android.os.PowerExemptionManager.REASON_PACKAGE_INSTALLER;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT;
import static android.os.PowerExemptionManager.REASON_PROC_STATE_PERSISTENT_UI;
@@ -85,7 +86,6 @@ import static android.os.PowerExemptionManager.REASON_SYSTEM_MODULE;
import static android.os.PowerExemptionManager.REASON_SYSTEM_UID;
import static android.os.PowerExemptionManager.REASON_TEMP_ALLOWED_WHILE_IN_USE;
import static android.os.PowerExemptionManager.REASON_UID_VISIBLE;
-import static android.os.PowerExemptionManager.REASON_UNKNOWN;
import static android.os.PowerExemptionManager.TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED;
import static android.os.PowerExemptionManager.getReasonCodeFromProcState;
import static android.os.PowerExemptionManager.reasonCodeToString;
@@ -7429,33 +7429,34 @@ public final class ActiveServices {
boolean isStartService) {
// Check DeviceConfig flag.
if (!mAm.mConstants.mFlagBackgroundFgsStartRestrictionEnabled) {
+ if (!r.mAllowWhileInUsePermissionInFgs) {
+ // BGFGS start restrictions are disabled. We're allowing while-in-use permissions.
+ // Note REASON_OTHER since there's no other suitable reason.
+ r.mAllowWhileInUsePermissionInFgsReason = REASON_OTHER;
+ }
r.mAllowWhileInUsePermissionInFgs = true;
}
- final @ReasonCode int allowWhileInUse;
-
// Either (or both) mAllowWhileInUsePermissionInFgs or mAllowStartForeground is
// newly allowed?
boolean newlyAllowed = false;
if (!r.mAllowWhileInUsePermissionInFgs
|| (r.mAllowStartForeground == REASON_DENIED)) {
- allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
+ @ReasonCode final int allowWhileInUse = shouldAllowFgsWhileInUsePermissionLocked(
callingPackage, callingPid, callingUid, r.app, backgroundStartPrivileges,
isBindService);
// We store them to compare the old and new while-in-use logics to each other.
// (They're not used for any other purposes.)
if (!r.mAllowWhileInUsePermissionInFgs) {
r.mAllowWhileInUsePermissionInFgs = (allowWhileInUse != REASON_DENIED);
+ r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
}
if (r.mAllowStartForeground == REASON_DENIED) {
r.mAllowStartForeground = shouldAllowFgsStartForegroundWithBindingCheckLocked(
allowWhileInUse, callingPackage, callingPid, callingUid, intent, r,
backgroundStartPrivileges, isBindService);
}
- } else {
- allowWhileInUse = REASON_UNKNOWN;
}
- r.mAllowWhileInUsePermissionInFgsReason = allowWhileInUse;
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 44e198b53761..ee7791461cc5 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -1345,6 +1345,8 @@ final class ActivityManagerConstants extends ContentObserver {
// The following read from Settings.
updateActivityStartsLoggingEnabled();
updateForegroundServiceStartsLoggingEnabled();
+ // Read DropboxRateLimiter params from flags.
+ mService.initDropboxRateLimiter();
}
private void loadDeviceConfigConstants() {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index a6debf6f3d51..31a9e9205ecf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -9206,6 +9206,11 @@ public class ActivityManagerService extends IActivityManager.Stub
private final DropboxRateLimiter mDropboxRateLimiter = new DropboxRateLimiter();
+ /** Initializes the Dropbox Rate Limiter parameters from flags. */
+ public void initDropboxRateLimiter() {
+ mDropboxRateLimiter.init();
+ }
+
/**
* Write a description of an error (crash, WTF, ANR) to the drop box.
* @param eventType to include in the drop box tag ("crash", "wtf", etc.)
@@ -19527,7 +19532,7 @@ public class ActivityManagerService extends IActivityManager.Stub
for (Display display : allDisplays) {
int displayId = display.getDisplayId();
// TODO(b/247592632): check other properties like isSecure or proper display type
- if (display.isValid()
+ if (display.isValid() && ((display.getFlags() & Display.FLAG_PRIVATE) == 0)
&& (allowOnDefaultDisplay || displayId != Display.DEFAULT_DISPLAY)) {
displayIds[numberValidDisplays++] = displayId;
}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index 0b5b1cb2902e..4b6d32427d68 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -25,6 +25,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UptimeMillisLong;
+import android.app.BroadcastOptions;
import android.content.Intent;
import android.content.pm.ResolveInfo;
import android.os.SystemClock;
@@ -257,7 +258,10 @@ class BroadcastProcessQueue {
deferredStatesApplyConsumer.accept(record, recordIndex);
}
- if (record.isReplacePending()) {
+ // Ignore FLAG_RECEIVER_REPLACE_PENDING if the sender specified the policy using the
+ // BroadcastOptions delivery group APIs.
+ if (record.isReplacePending()
+ && record.getDeliveryGroupPolicy() == BroadcastOptions.DELIVERY_GROUP_POLICY_ALL) {
final BroadcastRecord replacedBroadcastRecord = replaceBroadcast(record, recordIndex);
if (replacedBroadcastRecord != null) {
return replacedBroadcastRecord;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index f6004d7d2b7f..c6165cd3220e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -339,7 +339,7 @@ public class BroadcastQueueImpl extends BroadcastQueue {
private BroadcastRecord replaceBroadcastLocked(ArrayList<BroadcastRecord> queue,
BroadcastRecord r, String typeForLogging) {
final Intent intent = r.intent;
- for (int i = queue.size() - 1; i > 0; i--) {
+ for (int i = queue.size() - 1; i >= 0; i--) {
final BroadcastRecord old = queue.get(i);
if (old.userId == r.userId && intent.filterEquals(old.intent)) {
if (DEBUG_BROADCAST) {
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d8c7eefdfcc6..5356fdfe9a4c 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -719,11 +719,12 @@ class BroadcastQueueModernImpl extends BroadcastQueue {
private void skipAndCancelReplacedBroadcasts(ArraySet<BroadcastRecord> replacedBroadcasts) {
for (int i = 0; i < replacedBroadcasts.size(); ++i) {
final BroadcastRecord r = replacedBroadcasts.valueAt(i);
- r.resultCode = Activity.RESULT_CANCELED;
- r.resultData = null;
- r.resultExtras = null;
- scheduleResultTo(r);
- notifyFinishBroadcast(r);
+ // Skip all the receivers in the replaced broadcast
+ for (int rcvrIdx = 0; rcvrIdx < r.receivers.size(); ++rcvrIdx) {
+ if (!isDeliveryStateTerminal(r.getDeliveryState(rcvrIdx))) {
+ mBroadcastConsumerSkipAndCanceled.accept(r, rcvrIdx);
+ }
+ }
}
}
diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java
index b5c7215df899..003e6141aeb0 100644
--- a/services/core/java/com/android/server/am/DropboxRateLimiter.java
+++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java
@@ -17,6 +17,7 @@
package com.android.server.am;
import android.os.SystemClock;
+import android.provider.DeviceConfig;
import android.text.format.DateUtils;
import android.util.ArrayMap;
import android.util.Slog;
@@ -30,17 +31,26 @@ public class DropboxRateLimiter {
// After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of
// process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has
// elapsed, after which the current count for this breakdown will be reset.
- private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS;
+ private static final long RATE_LIMIT_BUFFER_DURATION_DEFAULT = 10 * DateUtils.MINUTE_IN_MILLIS;
// Indicated how many buffer durations to wait before the rate limit buffer will be cleared.
// E.g. if set to 3 will wait 3xRATE_LIMIT_BUFFER_DURATION before clearing the buffer.
- private static final long RATE_LIMIT_BUFFER_EXPIRY_FACTOR = 3;
+ private static final long RATE_LIMIT_BUFFER_EXPIRY_FACTOR_DEFAULT = 3;
// The number of entries to keep per breakdown of process/eventType.
- private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6;
+ private static final int RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT = 6;
// If a process is rate limited twice in a row we consider it crash-looping and rate limit it
// more aggressively.
- private static final int STRICT_RATE_LIMIT_ALLOWED_ENTRIES = 1;
- private static final long STRICT_RATE_LIMIT_BUFFER_DURATION = 20 * DateUtils.MINUTE_IN_MILLIS;
+ private static final int STRICT_RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT = 1;
+ private static final long STRICT_RATE_LIMIT_BUFFER_DURATION_DEFAULT =
+ 20 * DateUtils.MINUTE_IN_MILLIS;
+
+ private static final String FLAG_NAMESPACE = "dropbox";
+
+ private long mRateLimitBufferDuration;
+ private long mRateLimitBufferExpiryFactor;
+ private int mRateLimitAllowedEntries;
+ private int mStrictRatelimitAllowedEntries;
+ private long mStrictRateLimitBufferDuration;
@GuardedBy("mErrorClusterRecords")
private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>();
@@ -54,6 +64,36 @@ public class DropboxRateLimiter {
public DropboxRateLimiter(Clock clock) {
mClock = clock;
+
+ mRateLimitBufferDuration = RATE_LIMIT_BUFFER_DURATION_DEFAULT;
+ mRateLimitBufferExpiryFactor = RATE_LIMIT_BUFFER_EXPIRY_FACTOR_DEFAULT;
+ mRateLimitAllowedEntries = RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT;
+ mStrictRatelimitAllowedEntries = STRICT_RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT;
+ mStrictRateLimitBufferDuration = STRICT_RATE_LIMIT_BUFFER_DURATION_DEFAULT;
+ }
+
+ /** Initializes the rate limiter parameters from flags. */
+ public void init() {
+ mRateLimitBufferDuration = DeviceConfig.getLong(
+ FLAG_NAMESPACE,
+ "DropboxRateLimiter__rate_limit_buffer_duration",
+ RATE_LIMIT_BUFFER_DURATION_DEFAULT);
+ mRateLimitBufferExpiryFactor = DeviceConfig.getLong(
+ FLAG_NAMESPACE,
+ "DropboxRateLimiter__rate_limit_buffer_expiry_factor",
+ RATE_LIMIT_BUFFER_EXPIRY_FACTOR_DEFAULT);
+ mRateLimitAllowedEntries = DeviceConfig.getInt(
+ FLAG_NAMESPACE,
+ "DropboxRateLimiter__rate_limit_allowed_entries",
+ RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT);
+ mStrictRatelimitAllowedEntries = DeviceConfig.getInt(
+ FLAG_NAMESPACE,
+ "DropboxRateLimiter__strict_rate_limit_allowed_entries",
+ STRICT_RATE_LIMIT_ALLOWED_ENTRIES_DEFAULT);
+ mStrictRateLimitBufferDuration = DeviceConfig.getLong(
+ FLAG_NAMESPACE,
+ "DropboxRateLimiter__strict_rate_limit_buffer_duration",
+ STRICT_RATE_LIMIT_BUFFER_DURATION_DEFAULT);
}
/** The interface clock to use for tracking the time elapsed. */
@@ -116,7 +156,7 @@ public class DropboxRateLimiter {
private void maybeRemoveExpiredRecords(long currentTime) {
if (currentTime - mLastMapCleanUp
- <= RATE_LIMIT_BUFFER_EXPIRY_FACTOR * RATE_LIMIT_BUFFER_DURATION) {
+ <= mRateLimitBufferExpiryFactor * mRateLimitBufferDuration) {
return;
}
@@ -219,15 +259,15 @@ public class DropboxRateLimiter {
}
public int getAllowedEntries() {
- return isRepeated() ? STRICT_RATE_LIMIT_ALLOWED_ENTRIES : RATE_LIMIT_ALLOWED_ENTRIES;
+ return isRepeated() ? mStrictRatelimitAllowedEntries : mRateLimitAllowedEntries;
}
public long getBufferDuration() {
- return isRepeated() ? STRICT_RATE_LIMIT_BUFFER_DURATION : RATE_LIMIT_BUFFER_DURATION;
+ return isRepeated() ? mStrictRateLimitBufferDuration : mRateLimitBufferDuration;
}
public boolean hasExpired(long currentTime) {
- long bufferExpiry = RATE_LIMIT_BUFFER_EXPIRY_FACTOR * getBufferDuration();
+ long bufferExpiry = mRateLimitBufferExpiryFactor * getBufferDuration();
return currentTime - mStartTime > bufferExpiry;
}
}
diff --git a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
index 80406e6f66e4..38e7371e7075 100644
--- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
+++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java
@@ -113,6 +113,11 @@ public class ForegroundServiceTypeLoggerModule {
// We use this to get the duration an API was active after
// the stop call.
final SparseArray<Long> mLastFgsTimeStamp = new SparseArray<>();
+
+ // A map of API types to first FGS start call timestamps
+ // We use this to get the duration an API was active after
+ // the stop call.
+ final SparseArray<Long> mFirstFgsTimeStamp = new SparseArray<>();
}
// SparseArray that tracks all UIDs that have made various
@@ -146,6 +151,7 @@ public class ForegroundServiceTypeLoggerModule {
if (fgsIndex < 0) {
uidState.mRunningFgs.put(apiType, new ArrayMap<>());
fgsIndex = uidState.mRunningFgs.indexOfKey(apiType);
+ uidState.mFirstFgsTimeStamp.put(apiType, System.currentTimeMillis());
}
final ArrayMap<ComponentName, ServiceRecord> fgsList =
uidState.mRunningFgs.valueAt(fgsIndex);
@@ -237,7 +243,7 @@ public class ForegroundServiceTypeLoggerModule {
// there's no more FGS running for this type, just get rid of it
uidState.mRunningFgs.remove(apiType);
// but we need to keep track of the timestamp in case an API stops
- uidState.mLastFgsTimeStamp.put(apiType, record.mFgsExitTime);
+ uidState.mLastFgsTimeStamp.put(apiType, System.currentTimeMillis());
}
}
if (!apisFound.isEmpty()) {
@@ -454,8 +460,17 @@ public class ForegroundServiceTypeLoggerModule {
public void logFgsApiEvent(ServiceRecord r, int fgsState,
@FgsApiState int apiState,
@ForegroundServiceApiType int apiType, long timestamp) {
- final long apiDurationBeforeFgsStart = r.mFgsEnterTime - timestamp;
- final long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ long apiDurationBeforeFgsStart = r.createRealTime - timestamp;
+ long apiDurationAfterFgsEnd = timestamp - r.mFgsExitTime;
+ UidState uidState = mUids.get(r.appInfo.uid);
+ if (uidState != null) {
+ if (uidState.mFirstFgsTimeStamp.contains(apiType)) {
+ apiDurationBeforeFgsStart = uidState.mFirstFgsTimeStamp.get(apiType) - timestamp;
+ }
+ if (uidState.mLastFgsTimeStamp.contains(apiType)) {
+ apiDurationAfterFgsEnd = timestamp - uidState.mLastFgsTimeStamp.get(apiType);
+ }
+ }
final int[] apiTypes = new int[1];
apiTypes[0] = apiType;
final long[] timeStamps = new long[1];
diff --git a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
index 7841b699ec98..ffe5a6e6b958 100644
--- a/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
+++ b/services/core/java/com/android/server/am/ProcessCachedOptimizerRecord.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import android.annotation.UptimeMillisLong;
import android.app.ActivityManagerInternal.OomAdjReason;
+import android.util.TimeUtils;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -320,5 +321,8 @@ final class ProcessCachedOptimizerRecord {
pw.print(prefix); pw.print("isFreezeExempt="); pw.print(mFreezeExempt);
pw.print(" isPendingFreeze="); pw.print(mPendingFreeze);
pw.print(" " + IS_FROZEN + "="); pw.println(mFrozen);
+ pw.print(prefix); pw.print("earliestFreezableTimeMs=");
+ TimeUtils.formatDuration(mEarliestFreezableTimeMillis, nowUptime, pw);
+ pw.println();
}
}
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 8c227f5488d3..9db9e77c82c9 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -72,6 +72,10 @@ public class SettingsToPropertiesMapper {
Settings.Global.NATIVE_FLAGS_HEALTH_CHECK_ENABLED,
};
+ // TODO(b/282593625): Move this constant to DeviceConfig module
+ private static final String NAMESPACE_TETHERING_U_OR_LATER_NATIVE =
+ "tethering_u_or_later_native";
+
// All the flags under the listed DeviceConfig scopes will be synced to native level.
//
// NOTE: please grant write permission system property prefix
@@ -106,7 +110,8 @@ public class SettingsToPropertiesMapper {
DeviceConfig.NAMESPACE_WINDOW_MANAGER_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE_BOOT,
DeviceConfig.NAMESPACE_MEMORY_SAFETY_NATIVE,
- DeviceConfig.NAMESPACE_HDMI_CONTROL
+ DeviceConfig.NAMESPACE_HDMI_CONTROL,
+ NAMESPACE_TETHERING_U_OR_LATER_NATIVE
};
private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/am/UidObserverController.java b/services/core/java/com/android/server/am/UidObserverController.java
index a2582083c409..a6677a5185ca 100644
--- a/services/core/java/com/android/server/am/UidObserverController.java
+++ b/services/core/java/com/android/server/am/UidObserverController.java
@@ -429,21 +429,23 @@ public class UidObserverController {
}
}
- pw.println();
- pw.print(" mUidChangeDispatchCount=");
- pw.print(mUidChangeDispatchCount);
- pw.println();
- pw.println(" Slow UID dispatches:");
- for (int i = 0; i < count; i++) {
- final UidObserverRegistration reg = (UidObserverRegistration)
- mUidObservers.getRegisteredCallbackCookie(i);
- pw.print(" ");
- pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName());
- pw.print(": ");
- pw.print(reg.mSlowDispatchCount);
- pw.print(" / Max ");
- pw.print(reg.mMaxDispatchTime);
- pw.println("ms");
+ if (dumpPackage == null) {
+ pw.println();
+ pw.print(" mUidChangeDispatchCount=");
+ pw.print(mUidChangeDispatchCount);
+ pw.println();
+ pw.println(" Slow UID dispatches:");
+ for (int i = 0; i < count; i++) {
+ final UidObserverRegistration reg = (UidObserverRegistration)
+ mUidObservers.getRegisteredCallbackCookie(i);
+ pw.print(" ");
+ pw.print(mUidObservers.getRegisteredCallbackItem(i).getClass().getTypeName());
+ pw.print(": ");
+ pw.print(reg.mSlowDispatchCount);
+ pw.print(" / Max ");
+ pw.print(reg.mMaxDispatchTime);
+ pw.println("ms");
+ }
}
}
}
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index ad65490bfc8f..2ba522373b10 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -2210,8 +2210,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
mDeviceInventory.setPreferredDevicesForStrategyInt(
mAccessibilityStrategyId, Arrays.asList(defaultDevice));
} else {
- mDeviceInventory.removePreferredDevicesForStrategInt(mCommunicationStrategyId);
- mDeviceInventory.removePreferredDevicesForStrategInt(mAccessibilityStrategyId);
+ mDeviceInventory.removePreferredDevicesForStrategyInt(mCommunicationStrategyId);
+ mDeviceInventory.removePreferredDevicesForStrategyInt(mAccessibilityStrategyId);
}
mDeviceInventory.applyConnectedDevicesRoles();
} else {
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index 7b2e732702a3..d1cae490a31d 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -823,7 +823,7 @@ public class AudioDeviceInventory {
return status;
}
// Only used for internal requests
- /*package*/ int removePreferredDevicesForStrategInt(int strategy) {
+ /*package*/ int removePreferredDevicesForStrategyInt(int strategy) {
return clearDevicesRoleForStrategy(
strategy, AudioSystem.DEVICE_ROLE_PREFERRED, true /*internal */);
}
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index ed66432b9f58..db08bea8dfb1 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3593,16 +3593,15 @@ public class AudioService extends IAudioService.Stub
synchronized (mHdmiClientLock) {
if (mHdmiManager != null) {
// At most one of mHdmiPlaybackClient and mHdmiTvClient should be non-null
- HdmiClient fullVolumeHdmiClient = mHdmiPlaybackClient;
+ HdmiClient hdmiClient = mHdmiPlaybackClient;
if (mHdmiTvClient != null) {
- fullVolumeHdmiClient = mHdmiTvClient;
+ hdmiClient = mHdmiTvClient;
}
- if (fullVolumeHdmiClient != null
+ if (((mHdmiPlaybackClient != null && isFullVolumeDevice(device))
+ || (mHdmiTvClient != null && mHdmiSystemAudioSupported))
&& mHdmiCecVolumeControlEnabled
- && streamTypeAlias == AudioSystem.STREAM_MUSIC
- // vol change on a full volume device
- && isFullVolumeDevice(device)) {
+ && streamTypeAlias == AudioSystem.STREAM_MUSIC) {
int keyCode = KeyEvent.KEYCODE_UNKNOWN;
switch (direction) {
case AudioManager.ADJUST_RAISE:
@@ -3626,14 +3625,14 @@ public class AudioService extends IAudioService.Stub
try {
switch (keyEventMode) {
case AudioDeviceVolumeManager.ADJUST_MODE_NORMAL:
- fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
- fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
+ hdmiClient.sendVolumeKeyEvent(keyCode, true);
+ hdmiClient.sendVolumeKeyEvent(keyCode, false);
break;
case AudioDeviceVolumeManager.ADJUST_MODE_START:
- fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, true);
+ hdmiClient.sendVolumeKeyEvent(keyCode, true);
break;
case AudioDeviceVolumeManager.ADJUST_MODE_END:
- fullVolumeHdmiClient.sendVolumeKeyEvent(keyCode, false);
+ hdmiClient.sendVolumeKeyEvent(keyCode, false);
break;
default:
Log.e(TAG, "Invalid keyEventMode " + keyEventMode);
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9de2578df2ad..1162231b23aa 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -4598,6 +4598,22 @@ public final class DisplayManagerService extends SystemService {
}
@Override
+ public AmbientLightSensorData getAmbientLightSensorData(int displayId) {
+ synchronized (mSyncRoot) {
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
+ if (display == null) {
+ return null;
+ }
+ final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
+ if (device == null) {
+ return null;
+ }
+ SensorData data = device.getDisplayDeviceConfig().getAmbientLightSensor();
+ return new AmbientLightSensorData(data.name, data.type);
+ }
+ }
+
+ @Override
public IntArray getDisplayGroupIds() {
Set<Integer> visitedIds = new ArraySet<>();
IntArray displayGroupIds = new IntArray();
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 1722fc97da0b..dec9f62c8739 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -701,11 +701,15 @@ final class LocalDisplayAdapter extends DisplayAdapter {
maxDisplayMode == null ? mInfo.width : maxDisplayMode.getPhysicalWidth();
final int maxHeight =
maxDisplayMode == null ? mInfo.height : maxDisplayMode.getPhysicalHeight();
- mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
- mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
- mInfo.roundedCorners = RoundedCorners.fromResources(
- res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ // We cannot determine cutouts and rounded corners of external displays.
+ if (mStaticDisplayInfo.isInternal) {
+ mInfo.displayCutout = DisplayCutout.fromResourcesRectApproximation(res,
+ mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ mInfo.roundedCorners = RoundedCorners.fromResources(
+ res, mInfo.uniqueId, maxWidth, maxHeight, mInfo.width, mInfo.height);
+ }
+
mInfo.installOrientation = mStaticDisplayInfo.installOrientation;
mInfo.displayShape = DisplayShape.fromResources(
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 2c54e1cea3fa..3eab4b0b1835 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -1579,6 +1579,10 @@ public class HdmiControlService extends SystemService {
// If the device is not TV, we can't convert path to port-id, so stop here.
return true;
}
+ // Invalidate the physical address if parameters length is too short.
+ if (params.length < offset + 2) {
+ return false;
+ }
int path = HdmiUtils.twoBytesToInt(params, offset);
if (path != Constants.INVALID_PHYSICAL_ADDRESS && path == getPhysicalAddress()) {
return true;
diff --git a/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java b/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java
new file mode 100644
index 000000000000..ce868497b0e4
--- /dev/null
+++ b/services/core/java/com/android/server/input/AmbientKeyboardBacklightController.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.MainThread;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.util.Slog;
+import android.util.TypedValue;
+import android.view.Display;
+import android.view.DisplayInfo;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.display.utils.SensorUtils;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing the keyboard
+ * backlight based on ambient light sensor.
+ */
+final class AmbientKeyboardBacklightController implements DisplayManager.DisplayListener,
+ SensorEventListener {
+
+ private static final String TAG = "KbdBacklightController";
+
+ // To enable these logs, run:
+ // 'adb shell setprop log.tag.KbdBacklightController DEBUG' (requires restart)
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ // Number of light sensor responses required to overcome temporal hysteresis.
+ @VisibleForTesting
+ public static final int HYSTERESIS_THRESHOLD = 2;
+
+ private static final int MSG_BRIGHTNESS_CALLBACK = 0;
+ private static final int MSG_SETUP_DISPLAY_AND_SENSOR = 1;
+
+ private static final Object sAmbientControllerLock = new Object();
+
+ private final Context mContext;
+ private final Handler mHandler;
+
+ @Nullable
+ @GuardedBy("sAmbientControllerLock")
+ private Sensor mLightSensor;
+ @GuardedBy("sAmbientControllerLock")
+ private String mCurrentDefaultDisplayUniqueId;
+
+ // List of currently registered ambient backlight listeners
+ @GuardedBy("sAmbientControllerLock")
+ private final List<AmbientKeyboardBacklightListener> mAmbientKeyboardBacklightListeners =
+ new ArrayList<>();
+
+ private BrightnessStep[] mBrightnessSteps;
+ private int mCurrentBrightnessStepIndex;
+ private HysteresisState mHysteresisState;
+ private int mHysteresisCount = 0;
+ private float mSmoothingConstant;
+ private int mSmoothedLux;
+ private int mSmoothedLuxAtLastAdjustment;
+
+ private enum HysteresisState {
+ // The most-recent mSmoothedLux matched mSmoothedLuxAtLastAdjustment.
+ STABLE,
+ // The most-recent mSmoothedLux was less than mSmoothedLuxAtLastAdjustment.
+ DECREASING,
+ // The most-recent mSmoothedLux was greater than mSmoothedLuxAtLastAdjustment.
+ INCREASING,
+ // The brightness should be adjusted immediately after the next sensor reading.
+ IMMEDIATE,
+ }
+
+ AmbientKeyboardBacklightController(Context context, Looper looper) {
+ mContext = context;
+ mHandler = new Handler(looper, this::handleMessage);
+ initConfiguration();
+ }
+
+ public void systemRunning() {
+ mHandler.sendEmptyMessage(MSG_SETUP_DISPLAY_AND_SENSOR);
+ DisplayManager displayManager = Objects.requireNonNull(
+ mContext.getSystemService(DisplayManager.class));
+ displayManager.registerDisplayListener(this, mHandler);
+ }
+
+ public void registerAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
+ synchronized (sAmbientControllerLock) {
+ if (mAmbientKeyboardBacklightListeners.contains(listener)) {
+ throw new IllegalStateException(
+ "AmbientKeyboardBacklightListener was already registered, listener = "
+ + listener);
+ }
+ if (mAmbientKeyboardBacklightListeners.isEmpty()) {
+ // Add sensor listener when we add the first ambient backlight listener.
+ addSensorListener(mLightSensor);
+ }
+ mAmbientKeyboardBacklightListeners.add(listener);
+ }
+ }
+
+ public void unregisterAmbientBacklightListener(AmbientKeyboardBacklightListener listener) {
+ synchronized (sAmbientControllerLock) {
+ if (!mAmbientKeyboardBacklightListeners.contains(listener)) {
+ throw new IllegalStateException(
+ "AmbientKeyboardBacklightListener was never registered, listener = "
+ + listener);
+ }
+ mAmbientKeyboardBacklightListeners.remove(listener);
+ if (mAmbientKeyboardBacklightListeners.isEmpty()) {
+ removeSensorListener(mLightSensor);
+ }
+ }
+ }
+
+ private void sendBrightnessAdjustment(int brightnessValue) {
+ Message msg = Message.obtain(mHandler, MSG_BRIGHTNESS_CALLBACK, brightnessValue);
+ mHandler.sendMessage(msg);
+ }
+
+ @MainThread
+ private void handleBrightnessCallback(int brightnessValue) {
+ synchronized (sAmbientControllerLock) {
+ for (AmbientKeyboardBacklightListener listener : mAmbientKeyboardBacklightListeners) {
+ listener.onKeyboardBacklightValueChanged(brightnessValue);
+ }
+ }
+ }
+
+ @MainThread
+ private void handleAmbientLuxChange(float rawLux) {
+ if (rawLux < 0) {
+ Slog.w(TAG, "Light sensor doesn't have valid value");
+ return;
+ }
+ updateSmoothedLux(rawLux);
+
+ if (mHysteresisState != HysteresisState.IMMEDIATE
+ && mSmoothedLux == mSmoothedLuxAtLastAdjustment) {
+ mHysteresisState = HysteresisState.STABLE;
+ return;
+ }
+
+ int newStepIndex = Math.max(0, mCurrentBrightnessStepIndex);
+ int numSteps = mBrightnessSteps.length;
+
+ if (mSmoothedLux > mSmoothedLuxAtLastAdjustment) {
+ if (mHysteresisState != HysteresisState.IMMEDIATE
+ && mHysteresisState != HysteresisState.INCREASING) {
+ if (DEBUG) {
+ Slog.d(TAG, "ALS transitioned to brightness increasing state");
+ }
+ mHysteresisState = HysteresisState.INCREASING;
+ mHysteresisCount = 0;
+ }
+ for (; newStepIndex < numSteps; newStepIndex++) {
+ if (mSmoothedLux < mBrightnessSteps[newStepIndex].mIncreaseLuxThreshold) {
+ break;
+ }
+ }
+ } else if (mSmoothedLux < mSmoothedLuxAtLastAdjustment) {
+ if (mHysteresisState != HysteresisState.IMMEDIATE
+ && mHysteresisState != HysteresisState.DECREASING) {
+ if (DEBUG) {
+ Slog.d(TAG, "ALS transitioned to brightness decreasing state");
+ }
+ mHysteresisState = HysteresisState.DECREASING;
+ mHysteresisCount = 0;
+ }
+ for (; newStepIndex >= 0; newStepIndex--) {
+ if (mSmoothedLux > mBrightnessSteps[newStepIndex].mDecreaseLuxThreshold) {
+ break;
+ }
+ }
+ }
+
+ if (mHysteresisState == HysteresisState.IMMEDIATE) {
+ mCurrentBrightnessStepIndex = newStepIndex;
+ mSmoothedLuxAtLastAdjustment = mSmoothedLux;
+ mHysteresisState = HysteresisState.STABLE;
+ mHysteresisCount = 0;
+ sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
+ return;
+ }
+
+ if (newStepIndex == mCurrentBrightnessStepIndex) {
+ return;
+ }
+
+ mHysteresisCount++;
+ if (DEBUG) {
+ Slog.d(TAG, "Incremented hysteresis count to " + mHysteresisCount + " (lux went from "
+ + mSmoothedLuxAtLastAdjustment + " to " + mSmoothedLux + ")");
+ }
+ if (mHysteresisCount >= HYSTERESIS_THRESHOLD) {
+ mCurrentBrightnessStepIndex = newStepIndex;
+ mSmoothedLuxAtLastAdjustment = mSmoothedLux;
+ mHysteresisCount = 1;
+ sendBrightnessAdjustment(mBrightnessSteps[newStepIndex].mBrightnessValue);
+ }
+ }
+
+ @MainThread
+ private void handleDisplayChange() {
+ DisplayManagerInternal displayManagerInternal = LocalServices.getService(
+ DisplayManagerInternal.class);
+ DisplayInfo displayInfo = displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY);
+ synchronized (sAmbientControllerLock) {
+ if (Objects.equals(mCurrentDefaultDisplayUniqueId, displayInfo.uniqueId)) {
+ return;
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Default display changed: resetting the light sensor");
+ }
+ // Keep track of current default display
+ mCurrentDefaultDisplayUniqueId = displayInfo.uniqueId;
+ // Clear all existing sensor listeners
+ if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
+ removeSensorListener(mLightSensor);
+ }
+ mLightSensor = getAmbientLightSensor(
+ displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY));
+ // Re-add sensor listeners if required;
+ if (!mAmbientKeyboardBacklightListeners.isEmpty()) {
+ addSensorListener(mLightSensor);
+ }
+ }
+ }
+
+ private Sensor getAmbientLightSensor(
+ DisplayManagerInternal.AmbientLightSensorData ambientSensor) {
+ SensorManager sensorManager = Objects.requireNonNull(
+ mContext.getSystemService(SensorManager.class));
+ if (DEBUG) {
+ Slog.d(TAG, "Ambient Light sensor data: " + ambientSensor);
+ }
+ return SensorUtils.findSensor(sensorManager, ambientSensor.sensorType,
+ ambientSensor.sensorName, Sensor.TYPE_LIGHT);
+ }
+
+ private void updateSmoothedLux(float rawLux) {
+ // For the first sensor reading, use raw lux value directly without smoothing.
+ if (mHysteresisState == HysteresisState.IMMEDIATE) {
+ mSmoothedLux = (int) rawLux;
+ } else {
+ mSmoothedLux =
+ (int) (mSmoothingConstant * rawLux + (1 - mSmoothingConstant) * mSmoothedLux);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "Current smoothed lux from ALS = " + mSmoothedLux);
+ }
+ }
+
+ @VisibleForTesting
+ public void addSensorListener(@Nullable Sensor sensor) {
+ SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+ if (sensorManager == null || sensor == null) {
+ return;
+ }
+ // Reset values before registering listener
+ reset();
+ sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler);
+ if (DEBUG) {
+ Slog.d(TAG, "Registering ALS listener");
+ }
+ }
+
+ private void removeSensorListener(@Nullable Sensor sensor) {
+ SensorManager sensorManager = mContext.getSystemService(SensorManager.class);
+ if (sensorManager == null || sensor == null) {
+ return;
+ }
+ sensorManager.unregisterListener(this, sensor);
+ if (DEBUG) {
+ Slog.d(TAG, "Unregistering ALS listener");
+ }
+ }
+
+ private void initConfiguration() {
+ Resources res = mContext.getResources();
+ int[] brightnessValueArray = res.getIntArray(
+ com.android.internal.R.array.config_autoKeyboardBacklightBrightnessValues);
+ int[] decreaseThresholdArray = res.getIntArray(
+ com.android.internal.R.array.config_autoKeyboardBacklightDecreaseLuxThreshold);
+ int[] increaseThresholdArray = res.getIntArray(
+ com.android.internal.R.array.config_autoKeyboardBacklightIncreaseLuxThreshold);
+ if (brightnessValueArray.length != decreaseThresholdArray.length
+ || decreaseThresholdArray.length != increaseThresholdArray.length) {
+ throw new IllegalArgumentException(
+ "The config files for auto keyboard backlight brightness must contain arrays "
+ + "of equal lengths");
+ }
+ final int size = brightnessValueArray.length;
+ mBrightnessSteps = new BrightnessStep[size];
+ for (int i = 0; i < size; i++) {
+ int increaseThreshold =
+ increaseThresholdArray[i] < 0 ? Integer.MAX_VALUE : increaseThresholdArray[i];
+ int decreaseThreshold =
+ decreaseThresholdArray[i] < 0 ? Integer.MIN_VALUE : decreaseThresholdArray[i];
+ mBrightnessSteps[i] = new BrightnessStep(brightnessValueArray[i], increaseThreshold,
+ decreaseThreshold);
+ }
+
+ int numSteps = mBrightnessSteps.length;
+ if (numSteps == 0 || mBrightnessSteps[0].mDecreaseLuxThreshold != Integer.MIN_VALUE
+ || mBrightnessSteps[numSteps - 1].mIncreaseLuxThreshold != Integer.MAX_VALUE) {
+ throw new IllegalArgumentException(
+ "The config files for auto keyboard backlight brightness must contain arrays "
+ + "of length > 0 and have -1 or Integer.MIN_VALUE as lower bound for "
+ + "decrease thresholds and -1 or Integer.MAX_VALUE as upper bound for "
+ + "increase thresholds");
+ }
+
+ final TypedValue smoothingConstantValue = new TypedValue();
+ res.getValue(
+ com.android.internal.R.dimen.config_autoKeyboardBrightnessSmoothingConstant,
+ smoothingConstantValue,
+ true /*resolveRefs*/);
+ mSmoothingConstant = smoothingConstantValue.getFloat();
+ if (mSmoothingConstant <= 0.0 || mSmoothingConstant > 1.0) {
+ throw new IllegalArgumentException(
+ "The config files for auto keyboard backlight brightness must contain "
+ + "smoothing constant in range (0.0, 1.0].");
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "Brightness steps: " + Arrays.toString(mBrightnessSteps)
+ + " Smoothing constant = " + mSmoothingConstant);
+ }
+ }
+
+ private void reset() {
+ mHysteresisState = HysteresisState.IMMEDIATE;
+ mSmoothedLux = 0;
+ mSmoothedLuxAtLastAdjustment = 0;
+ mCurrentBrightnessStepIndex = -1;
+ }
+
+ private boolean handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_BRIGHTNESS_CALLBACK:
+ handleBrightnessCallback((int) msg.obj);
+ return true;
+ case MSG_SETUP_DISPLAY_AND_SENSOR:
+ handleDisplayChange();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ handleAmbientLuxChange(event.values[0]);
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onDisplayAdded(int displayId) {
+ handleDisplayChange();
+ }
+
+ @Override
+ public void onDisplayRemoved(int displayId) {
+ handleDisplayChange();
+ }
+
+ @Override
+ public void onDisplayChanged(int displayId) {
+ handleDisplayChange();
+ }
+
+ public interface AmbientKeyboardBacklightListener {
+ /**
+ * @param value between [0, 255] to which keyboard backlight needs to be set according
+ * to Ambient light sensor.
+ */
+ void onKeyboardBacklightValueChanged(int value);
+ }
+
+ private static class BrightnessStep {
+ private final int mBrightnessValue;
+ private final int mIncreaseLuxThreshold;
+ private final int mDecreaseLuxThreshold;
+
+ private BrightnessStep(int brightnessValue, int increaseLuxThreshold,
+ int decreaseLuxThreshold) {
+ mBrightnessValue = brightnessValue;
+ mIncreaseLuxThreshold = increaseLuxThreshold;
+ mDecreaseLuxThreshold = decreaseLuxThreshold;
+ }
+
+ @Override
+ public String toString() {
+ return "BrightnessStep{" + "mBrightnessValue=" + mBrightnessValue
+ + ", mIncreaseThreshold=" + mIncreaseLuxThreshold + ", mDecreaseThreshold="
+ + mDecreaseLuxThreshold + '}';
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
index 7c7f1513bd96..a646d1e9bcb0 100644
--- a/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
+++ b/services/core/java/com/android/server/input/InputFeatureFlagProvider.java
@@ -39,14 +39,21 @@ public final class InputFeatureFlagProvider {
InputProperties.enable_keyboard_backlight_animation().orElse(false);
// To disable Custom keyboard backlight levels support via IDC files run:
- // adb shell setprop persist.input.keyboard_backlight_custom_levels.enabled false (requires
+ // adb shell setprop persist.input.keyboard.backlight_custom_levels.enabled false (requires
// restart)
private static final boolean KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED =
InputProperties.enable_keyboard_backlight_custom_levels().orElse(true);
+ // To disable als based ambient keyboard backlight control run:
+ // adb shell setprop persist.input.keyboard.ambient_backlight_control.enabled false (requires
+ // restart)
+ private static final boolean AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED =
+ InputProperties.enable_ambient_keyboard_backlight_control().orElse(true);
+
private static Optional<Boolean> sKeyboardBacklightControlOverride = Optional.empty();
private static Optional<Boolean> sKeyboardBacklightAnimationOverride = Optional.empty();
private static Optional<Boolean> sKeyboardBacklightCustomLevelsOverride = Optional.empty();
+ private static Optional<Boolean> sAmbientKeyboardBacklightControlOverride = Optional.empty();
public static boolean isKeyboardBacklightControlEnabled() {
return sKeyboardBacklightControlOverride.orElse(KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
@@ -61,6 +68,11 @@ public final class InputFeatureFlagProvider {
KEYBOARD_BACKLIGHT_CUSTOM_LEVELS_ENABLED);
}
+ public static boolean isAmbientKeyboardBacklightControlEnabled() {
+ return sAmbientKeyboardBacklightControlOverride.orElse(
+ AMBIENT_KEYBOARD_BACKLIGHT_CONTROL_ENABLED);
+ }
+
public static void setKeyboardBacklightControlEnabled(boolean enabled) {
sKeyboardBacklightControlOverride = Optional.of(enabled);
}
@@ -73,6 +85,10 @@ public final class InputFeatureFlagProvider {
sKeyboardBacklightCustomLevelsOverride = Optional.of(enabled);
}
+ public static void setAmbientKeyboardBacklightControlEnabled(boolean enabled) {
+ sAmbientKeyboardBacklightControlOverride = Optional.of(enabled);
+ }
+
/**
* Clears all input feature flag overrides.
*/
@@ -80,5 +96,6 @@ public final class InputFeatureFlagProvider {
sKeyboardBacklightControlOverride = Optional.empty();
sKeyboardBacklightAnimationOverride = Optional.empty();
sKeyboardBacklightCustomLevelsOverride = Optional.empty();
+ sAmbientKeyboardBacklightControlOverride = Optional.empty();
}
}
diff --git a/services/core/java/com/android/server/input/KeyboardBacklightController.java b/services/core/java/com/android/server/input/KeyboardBacklightController.java
index 36238a8cfd23..1253b5b5b303 100644
--- a/services/core/java/com/android/server/input/KeyboardBacklightController.java
+++ b/services/core/java/com/android/server/input/KeyboardBacklightController.java
@@ -18,6 +18,7 @@ package com.android.server.input;
import android.animation.ValueAnimator;
import android.annotation.BinderThread;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Color;
import android.hardware.input.IKeyboardBacklightListener;
@@ -105,6 +106,12 @@ final class KeyboardBacklightController implements
private final SparseArray<KeyboardBacklightListenerRecord> mKeyboardBacklightListenerRecords =
new SparseArray<>();
+ private final AmbientKeyboardBacklightController mAmbientController;
+ @Nullable
+ private AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener mAmbientListener;
+
+ private int mAmbientBacklightValue = 0;
+
static {
// Fixed brightness levels to avoid issues when converting back and forth from the
// device brightness range to [0-255]
@@ -128,6 +135,7 @@ final class KeyboardBacklightController implements
mDataStore = dataStore;
mHandler = new Handler(looper, this::handleMessage);
mAnimatorFactory = animatorFactory;
+ mAmbientController = new AmbientKeyboardBacklightController(context, looper);
}
@Override
@@ -151,6 +159,11 @@ final class KeyboardBacklightController implements
}
};
observer.startObserving(UEVENT_KEYBOARD_BACKLIGHT_TAG);
+
+ if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+ // Start ambient backlight controller
+ mAmbientController.systemRunning();
+ }
}
@Override
@@ -183,9 +196,21 @@ final class KeyboardBacklightController implements
if (inputDevice == null || state == null) {
return;
}
- Light keyboardBacklight = state.mLight;
// Follow preset levels of brightness defined in BRIGHTNESS_LEVELS
- final int currBrightnessLevel = state.mBrightnessLevel;
+ final int currBrightnessLevel;
+ if (state.mUseAmbientController) {
+ int index = Arrays.binarySearch(state.mBrightnessValueForLevel, mAmbientBacklightValue);
+ // Set current level to the lower bound of the ambient value in the brightness array.
+ if (index < 0) {
+ int lowerBound = Math.max(0, -(index + 1) - 1);
+ currBrightnessLevel =
+ direction == Direction.DIRECTION_UP ? lowerBound : lowerBound + 1;
+ } else {
+ currBrightnessLevel = index;
+ }
+ } else {
+ currBrightnessLevel = state.mBrightnessLevel;
+ }
final int newBrightnessLevel;
if (direction == Direction.DIRECTION_UP) {
newBrightnessLevel = Math.min(currBrightnessLevel + 1,
@@ -193,33 +218,67 @@ final class KeyboardBacklightController implements
} else {
newBrightnessLevel = Math.max(currBrightnessLevel - 1, 0);
}
- updateBacklightState(deviceId, newBrightnessLevel, true /* isTriggeredByKeyPress */);
+ state.setBrightnessLevel(newBrightnessLevel);
+
+ // Might need to stop listening to ALS since user has manually selected backlight
+ // level through keyboard up/down button
+ updateAmbientLightListener();
+
+ maybeBackupBacklightBrightness(inputDevice, state.mLight,
+ state.mBrightnessValueForLevel[newBrightnessLevel]);
+
+ if (DEBUG) {
+ Slog.d(TAG,
+ "Changing state from " + state.mBrightnessLevel + " to " + newBrightnessLevel);
+ }
+
+ synchronized (mKeyboardBacklightListenerRecords) {
+ for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
+ IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
+ callbackState.brightnessLevel = newBrightnessLevel;
+ callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps();
+ mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
+ deviceId, callbackState, true);
+ }
+ }
+ }
+
+ private void maybeBackupBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight,
+ int brightnessValue) {
+ // Don't back up or restore when ALS based keyboard backlight is enabled
+ if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+ return;
+ }
synchronized (mDataStore) {
try {
mDataStore.setKeyboardBacklightBrightness(inputDevice.getDescriptor(),
keyboardBacklight.getId(),
- state.mBrightnessValueForLevel[newBrightnessLevel]);
+ brightnessValue);
} finally {
mDataStore.saveIfNeeded();
}
}
}
- private void restoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+ private void maybeRestoreBacklightBrightness(InputDevice inputDevice, Light keyboardBacklight) {
+ // Don't back up or restore when ALS based keyboard backlight is enabled
+ if (InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+ return;
+ }
KeyboardBacklightState state = mKeyboardBacklights.get(inputDevice.getId());
OptionalInt brightness;
synchronized (mDataStore) {
brightness = mDataStore.getKeyboardBacklightBrightness(
inputDevice.getDescriptor(), keyboardBacklight.getId());
}
- if (brightness.isPresent()) {
+ if (state != null && brightness.isPresent()) {
int brightnessValue = Math.max(0, Math.min(MAX_BRIGHTNESS, brightness.getAsInt()));
- int index = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
- if (index < 0) {
- index = Math.min(state.getNumBrightnessChangeSteps(), -(index + 1));
+ int newLevel = Arrays.binarySearch(state.mBrightnessValueForLevel, brightnessValue);
+ if (newLevel < 0) {
+ newLevel = Math.min(state.getNumBrightnessChangeSteps(), -(newLevel + 1));
}
- updateBacklightState(inputDevice.getId(), index, false /* isTriggeredByKeyPress */);
+ state.setBrightnessLevel(newLevel);
if (DEBUG) {
Slog.d(TAG, "Restoring brightness level " + brightness.getAsInt());
}
@@ -260,6 +319,16 @@ final class KeyboardBacklightController implements
} else {
handleUserInactivity();
}
+ updateAmbientLightListener();
+ }
+
+ @VisibleForTesting
+ public void handleAmbientLightValueChanged(int brightnessValue) {
+ mAmbientBacklightValue = brightnessValue;
+ for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+ KeyboardBacklightState state = mKeyboardBacklights.valueAt(i);
+ state.onAmbientBacklightValueChanged();
+ }
}
private boolean handleMessage(Message msg) {
@@ -292,12 +361,14 @@ final class KeyboardBacklightController implements
@Override
public void onInputDeviceAdded(int deviceId) {
onInputDeviceChanged(deviceId);
+ updateAmbientLightListener();
}
@VisibleForTesting
@Override
public void onInputDeviceRemoved(int deviceId) {
mKeyboardBacklights.remove(deviceId);
+ updateAmbientLightListener();
}
@VisibleForTesting
@@ -318,7 +389,7 @@ final class KeyboardBacklightController implements
}
// The keyboard backlight was added or changed.
mKeyboardBacklights.put(deviceId, new KeyboardBacklightState(deviceId, keyboardBacklight));
- restoreBacklightBrightness(inputDevice, keyboardBacklight);
+ maybeRestoreBacklightBrightness(inputDevice, keyboardBacklight);
}
private InputDevice getInputDevice(int deviceId) {
@@ -379,30 +450,6 @@ final class KeyboardBacklightController implements
}
}
- private void updateBacklightState(int deviceId, int brightnessLevel,
- boolean isTriggeredByKeyPress) {
- KeyboardBacklightState state = mKeyboardBacklights.get(deviceId);
- if (state == null) {
- return;
- }
-
- state.setBrightnessLevel(brightnessLevel);
-
- synchronized (mKeyboardBacklightListenerRecords) {
- for (int i = 0; i < mKeyboardBacklightListenerRecords.size(); i++) {
- IKeyboardBacklightState callbackState = new IKeyboardBacklightState();
- callbackState.brightnessLevel = brightnessLevel;
- callbackState.maxBrightnessLevel = state.getNumBrightnessChangeSteps();
- mKeyboardBacklightListenerRecords.valueAt(i).notifyKeyboardBacklightChanged(
- deviceId, callbackState, isTriggeredByKeyPress);
- }
- }
-
- if (DEBUG) {
- Slog.d(TAG, "Changing state from " + state.mBrightnessLevel + " to " + brightnessLevel);
- }
- }
-
private void onKeyboardBacklightListenerDied(int pid) {
synchronized (mKeyboardBacklightListenerRecords) {
mKeyboardBacklightListenerRecords.remove(pid);
@@ -420,6 +467,25 @@ final class KeyboardBacklightController implements
}
}
+ private void updateAmbientLightListener() {
+ if (!InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled()) {
+ return;
+ }
+ boolean needToListenAmbientLightSensor = false;
+ for (int i = 0; i < mKeyboardBacklights.size(); i++) {
+ needToListenAmbientLightSensor |= mKeyboardBacklights.valueAt(i).mUseAmbientController;
+ }
+ needToListenAmbientLightSensor &= mIsInteractive;
+ if (needToListenAmbientLightSensor && mAmbientListener == null) {
+ mAmbientListener = this::handleAmbientLightValueChanged;
+ mAmbientController.registerAmbientBacklightListener(mAmbientListener);
+ }
+ if (!needToListenAmbientLightSensor && mAmbientListener != null) {
+ mAmbientController.unregisterAmbientBacklightListener(mAmbientListener);
+ mAmbientListener = null;
+ }
+ }
+
private static boolean isValidBacklightNodePath(String devPath) {
if (TextUtils.isEmpty(devPath)) {
return false;
@@ -485,6 +551,8 @@ final class KeyboardBacklightController implements
private int mBrightnessLevel;
private ValueAnimator mAnimator;
private final int[] mBrightnessValueForLevel;
+ private boolean mUseAmbientController =
+ InputFeatureFlagProvider.isAmbientKeyboardBacklightControlEnabled();
KeyboardBacklightState(int deviceId, Light light) {
mDeviceId = deviceId;
@@ -525,15 +593,25 @@ final class KeyboardBacklightController implements
}
private void onBacklightStateChanged() {
- setBacklightValue(mIsBacklightOn ? mBrightnessValueForLevel[mBrightnessLevel] : 0);
+ int toValue = mUseAmbientController ? mAmbientBacklightValue
+ : mBrightnessValueForLevel[mBrightnessLevel];
+ setBacklightValue(mIsBacklightOn ? toValue : 0);
}
private void setBrightnessLevel(int brightnessLevel) {
+ // Once we manually set level, disregard ambient light controller
+ mUseAmbientController = false;
if (mIsBacklightOn) {
setBacklightValue(mBrightnessValueForLevel[brightnessLevel]);
}
mBrightnessLevel = brightnessLevel;
}
+ private void onAmbientBacklightValueChanged() {
+ if (mIsBacklightOn && mUseAmbientController) {
+ setBacklightValue(mAmbientBacklightValue);
+ }
+ }
+
private void cancelAnimation() {
if (mAnimator != null && mAnimator.isRunning()) {
mAnimator.cancel();
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index 44ae454e7ef2..c212e8e3c82c 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -19,6 +19,7 @@ package com.android.server.inputmethod;
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
+import android.annotation.Nullable;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
@@ -65,8 +66,8 @@ final class InputMethodMenuController {
private boolean mShowImeWithHardKeyboard;
@GuardedBy("ImfLock.class")
- private final InputMethodDialogWindowContext mDialogWindowContext =
- new InputMethodDialogWindowContext();
+ @Nullable
+ private InputMethodDialogWindowContext mDialogWindowContext;
InputMethodMenuController(InputMethodManagerService service) {
mService = service;
@@ -124,11 +125,13 @@ final class InputMethodMenuController {
}
}
+ if (mDialogWindowContext == null) {
+ mDialogWindowContext = new InputMethodDialogWindowContext();
+ }
final Context dialogWindowContext = mDialogWindowContext.get(displayId);
mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
- // TODO(b/277061090): refactor UI components should not be created while holding a lock.
final Context dialogContext = mDialogBuilder.getContext();
final TypedArray a = dialogContext.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
@@ -196,11 +199,10 @@ final class InputMethodMenuController {
attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
attrs.setTitle("Select input method");
w.setAttributes(attrs);
- // TODO(b/277062834) decouple/remove dependency on IMMS
mService.updateSystemUiLocked();
mService.sendOnNavButtonFlagsChangedLocked();
+ mSwitchingDialog.show();
}
- mSwitchingDialog.show();
}
private boolean isScreenLocked() {
@@ -274,7 +276,6 @@ final class InputMethodMenuController {
private final int mTextViewResourceId;
private final List<ImeSubtypeListItem> mItemsList;
public int mCheckedItem;
-
private ImeSubtypeListAdapter(Context context, int textViewResourceId,
List<ImeSubtypeListItem> itemsList, int checkedItem) {
super(context, textViewResourceId, itemsList);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 8e65943cf297..e72fcdfa8283 100755..100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -119,6 +119,7 @@ import static android.service.notification.NotificationListenerService.TRIM_LIGH
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
@@ -223,6 +224,8 @@ import android.os.IInterface;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -234,6 +237,7 @@ import android.os.Trace;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.VibrationEffect;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.Settings;
@@ -559,6 +563,7 @@ public class NotificationManagerService extends SystemService {
private PermissionHelper mPermissionHelper;
private UsageStatsManagerInternal mUsageStatsManagerInternal;
private TelecomManager mTelecomManager;
+ private PowerManager mPowerManager;
private PostNotificationTrackerFactory mPostNotificationTrackerFactory;
final IBinder mForegroundToken = new Binder();
@@ -923,7 +928,7 @@ public class NotificationManagerService extends SystemService {
if (oldFlags != flags) {
summary.getSbn().getNotification().flags = flags;
mHandler.post(new EnqueueNotificationRunnable(userId, summary, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -1457,7 +1462,7 @@ public class NotificationManagerService extends SystemService {
// want to adjust the flag behaviour.
mHandler.post(new EnqueueNotificationRunnable(r.getUser().getIdentifier(),
r, true /* isAppForeground*/,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -1488,7 +1493,7 @@ public class NotificationManagerService extends SystemService {
mHandler.post(
new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r,
/* foreground= */ true,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -1843,7 +1848,7 @@ public class NotificationManagerService extends SystemService {
}
} else if (action.equals(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE)) {
int userHandle = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userHandle >= 0) {
+ if (userHandle >= 0 && !mDpm.isKeepProfilesRunningEnabled()) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
REASON_PROFILE_TURNED_OFF, null);
mSnoozeHelper.clearData(userHandle);
@@ -2233,7 +2238,7 @@ public class NotificationManagerService extends SystemService {
UsageStatsManagerInternal usageStatsManagerInternal,
TelecomManager telecomManager, NotificationChannelLogger channelLogger,
SystemUiSystemPropertiesFlags.FlagResolver flagResolver,
- PermissionManager permissionManager,
+ PermissionManager permissionManager, PowerManager powerManager,
PostNotificationTrackerFactory postNotificationTrackerFactory) {
mHandler = handler;
Resources resources = getContext().getResources();
@@ -2265,6 +2270,7 @@ public class NotificationManagerService extends SystemService {
mDpm = dpm;
mUm = userManager;
mTelecomManager = telecomManager;
+ mPowerManager = powerManager;
mPostNotificationTrackerFactory = postNotificationTrackerFactory;
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
@@ -2568,6 +2574,7 @@ public class NotificationManagerService extends SystemService {
getContext().getSystemService(TelecomManager.class),
new NotificationChannelLoggerImpl(), SystemUiSystemPropertiesFlags.getResolver(),
getContext().getSystemService(PermissionManager.class),
+ getContext().getSystemService(PowerManager.class),
new PostNotificationTrackerFactory() {});
publishBinderService(Context.NOTIFICATION_SERVICE, mService, /* allowIsolated= */ false,
@@ -2684,7 +2691,7 @@ public class NotificationManagerService extends SystemService {
final boolean isAppForeground =
mActivityManager.getPackageImportance(pkg) == IMPORTANCE_FOREGROUND;
mHandler.post(new EnqueueNotificationRunnable(userId, r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
@@ -6574,7 +6581,7 @@ public class NotificationManagerService extends SystemService {
void enqueueNotificationInternal(final String pkg, final String opPkg, final int callingUid,
final int callingPid, final String tag, final int id, final Notification notification,
int incomingUserId, boolean postSilently) {
- PostNotificationTracker tracker = mPostNotificationTrackerFactory.newTracker();
+ PostNotificationTracker tracker = acquireWakeLockForPost(pkg, callingUid);
boolean enqueued = false;
try {
enqueued = enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id,
@@ -6586,6 +6593,22 @@ public class NotificationManagerService extends SystemService {
}
}
+ private PostNotificationTracker acquireWakeLockForPost(String pkg, int uid) {
+ if (mFlagResolver.isEnabled(WAKE_LOCK_FOR_POSTING_NOTIFICATION)) {
+ // The package probably doesn't have WAKE_LOCK permission and should not require it.
+ return Binder.withCleanCallingIdentity(() -> {
+ WakeLock wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+ "NotificationManagerService:post:" + pkg);
+ wakeLock.setWorkSource(new WorkSource(uid, pkg));
+ // TODO(b/275044361): Adjust to a more reasonable number when we have the data.
+ wakeLock.acquire(30_000);
+ return mPostNotificationTrackerFactory.newTracker(wakeLock);
+ });
+ } else {
+ return mPostNotificationTrackerFactory.newTracker(null);
+ }
+ }
+
/**
* @return True if we successfully processed the notification and handed off the task of
* enqueueing it to a background thread; false otherwise.
@@ -7105,7 +7128,7 @@ public class NotificationManagerService extends SystemService {
mHandler.post(
new NotificationManagerService.EnqueueNotificationRunnable(
r.getUser().getIdentifier(), r, isAppForeground,
- mPostNotificationTrackerFactory.newTracker()));
+ mPostNotificationTrackerFactory.newTracker(null)));
}
}
}
@@ -12137,20 +12160,20 @@ public class NotificationManagerService extends SystemService {
}
interface PostNotificationTrackerFactory {
- default PostNotificationTracker newTracker() {
- return new PostNotificationTracker();
+ default PostNotificationTracker newTracker(@Nullable WakeLock optionalWakelock) {
+ return new PostNotificationTracker(optionalWakelock);
}
}
static class PostNotificationTracker {
@ElapsedRealtimeLong private final long mStartTime;
- @Nullable private NotificationRecordLogger.NotificationReported mReport;
+ @Nullable private final WakeLock mWakeLock;
private boolean mOngoing;
@VisibleForTesting
- PostNotificationTracker() {
- // TODO(b/275044361): (Conditionally) receive a wakelock.
+ PostNotificationTracker(@Nullable WakeLock wakeLock) {
mStartTime = SystemClock.elapsedRealtime();
+ mWakeLock = wakeLock;
mOngoing = true;
if (DBG) {
Slog.d(TAG, "PostNotification: Started");
@@ -12168,9 +12191,8 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Cancels the tracker (TODO(b/275044361): releasing the acquired WakeLock). Either
- * {@link #finish} or {@link #cancel} (exclusively) should be called on this object before
- * it's discarded.
+ * Cancels the tracker (releasing the acquired WakeLock). Either {@link #finish} or
+ * {@link #cancel} (exclusively) should be called on this object before it's discarded.
*/
void cancel() {
if (!isOngoing()) {
@@ -12178,9 +12200,9 @@ public class NotificationManagerService extends SystemService {
return;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
long elapsedTime = SystemClock.elapsedRealtime() - mStartTime;
Slog.d(TAG, TextUtils.formatSimple("PostNotification: Abandoned after %d ms",
@@ -12189,9 +12211,9 @@ public class NotificationManagerService extends SystemService {
}
/**
- * Finishes the tracker (TODO(b/275044361): releasing the acquired WakeLock) and returns the
- * time elapsed since the operation started, in milliseconds. Either {@link #finish} or
- * {@link #cancel} (exclusively) should be called on this object before it's discarded.
+ * Finishes the tracker (releasing the acquired WakeLock) and returns the time elapsed since
+ * the operation started, in milliseconds. Either {@link #finish} or {@link #cancel}
+ * (exclusively) should be called on this object before it's discarded.
*/
@DurationMillisLong
long finish() {
@@ -12201,9 +12223,9 @@ public class NotificationManagerService extends SystemService {
return elapsedTime;
}
mOngoing = false;
-
- // TODO(b/275044361): Release wakelock.
-
+ if (mWakeLock != null) {
+ Binder.withCleanCallingIdentity(() -> mWakeLock.release());
+ }
if (DBG) {
Slog.d(TAG,
TextUtils.formatSimple("PostNotification: Finished in %d ms", elapsedTime));
diff --git a/services/core/java/com/android/server/os/NativeTombstoneManager.java b/services/core/java/com/android/server/os/NativeTombstoneManager.java
index d3d1cc5d05cb..f8ee6b04e9a2 100644
--- a/services/core/java/com/android/server/os/NativeTombstoneManager.java
+++ b/services/core/java/com/android/server/os/NativeTombstoneManager.java
@@ -96,6 +96,8 @@ public final class NativeTombstoneManager {
registerForUserRemoval();
registerForPackageRemoval();
+ BootReceiver.initDropboxRateLimiter();
+
// Scan existing tombstones.
mHandler.post(() -> {
final File[] tombstoneFiles = TOMBSTONE_DIR.listFiles();
diff --git a/services/core/java/com/android/server/pm/DumpHelper.java b/services/core/java/com/android/server/pm/DumpHelper.java
index fcaaa90dbc8a..b5647d0092f5 100644
--- a/services/core/java/com/android/server/pm/DumpHelper.java
+++ b/services/core/java/com/android/server/pm/DumpHelper.java
@@ -617,7 +617,9 @@ final class DumpHelper {
pw.println(" --checkin: dump for a checkin");
pw.println(" -f: print details of intent filters");
pw.println(" -h: print this help");
+ pw.println(" ---proto: dump data to proto");
pw.println(" --all-components: include all component names in package dump");
+ pw.println(" --include-apex: includes the apex packages in package dump");
pw.println(" cmd may be one of:");
pw.println(" apex: list active APEXes and APEX session state");
pw.println(" l[ibraries]: list known shared libraries");
@@ -631,7 +633,7 @@ final class DumpHelper {
pw.println(" prov[iders]: dump content providers");
pw.println(" p[ackages]: dump installed packages");
pw.println(" q[ueries]: dump app queryability calculations");
- pw.println(" s[hared-users]: dump shared user IDs");
+ pw.println(" s[hared-users] [noperm]: dump shared user IDs");
pw.println(" m[essages]: print collected runtime messages");
pw.println(" v[erifiers]: print package verifier info");
pw.println(" d[omain-preferred-apps]: print domains preferred apps");
@@ -644,9 +646,12 @@ final class DumpHelper {
pw.println(" dexopt: dump dexopt state");
pw.println(" compiler-stats: dump compiler statistics");
pw.println(" service-permissions: dump permissions required by services");
- pw.println(" snapshot: dump snapshot statistics");
+ pw.println(" snapshot [--full|--brief]: dump snapshot statistics");
pw.println(" protected-broadcasts: print list of protected broadcast actions");
pw.println(" known-packages: dump known packages");
+ pw.println(" changes: dump the packages that have been changed");
+ pw.println(" frozen: dump the frozen packages");
+ pw.println(" volumes: dump the loaded volumes");
pw.println(" <package.name>: info about given package");
}
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 4f00dc37caa6..fa7c063abbe7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -2145,7 +2145,7 @@ final class InstallPackageHelper {
final String pkgName = pkg.getPackageName();
final int[] installedForUsers = installRequest.getOriginUsers();
final int installReason = installRequest.getInstallReason();
- final String installerPackageName = installRequest.getSourceInstallerPackageName();
+ final String installerPackageName = installRequest.getInstallerPackageName();
if (DEBUG_INSTALL) Slog.d(TAG, "New package installed in " + pkg.getPath());
synchronized (mPm.mLock) {
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 95e790450724..34648740d54c 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -366,12 +366,6 @@ final class InstallRequest {
public String getApexModuleName() {
return mApexModuleName;
}
-
- @Nullable
- public String getSourceInstallerPackageName() {
- return mInstallArgs.mInstallSource.mInstallerPackageName;
- }
-
public boolean isRollback() {
return mInstallArgs != null
&& mInstallArgs.mInstallReason == PackageManager.INSTALL_REASON_ROLLBACK;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index b90fe6165b8a..2c0e74d65e7f 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -511,7 +511,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements
}
} catch (FileNotFoundException e) {
// Missing sessions are okay, probably first boot
- } catch (IOException | XmlPullParserException e) {
+ } catch (IOException | XmlPullParserException | ArrayIndexOutOfBoundsException e) {
Slog.wtf(TAG, "Failed reading install sessions", e);
} finally {
IoUtils.closeQuietly(fis);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 41234813ee67..c1709f8faead 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3261,7 +3261,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
}
@Nullable
- private String getDevicePolicyManagementRoleHolderPackageName(int userId) {
+ public String getDevicePolicyManagementRoleHolderPackageName(int userId) {
return Binder.withCleanCallingIdentity(() -> {
RoleManager roleManager = mContext.getSystemService(RoleManager.class);
List<String> roleHolders =
@@ -4110,7 +4110,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
final int livingUserCount = livingUsers.size();
for (int i = 0; i < livingUserCount; i++) {
final int userId = livingUsers.get(i).id;
- if (mSettings.isPermissionUpgradeNeeded(userId)) {
+ final boolean isPermissionUpgradeNeeded = !Objects.equals(
+ mPermissionManager.getDefaultPermissionGrantFingerprint(userId),
+ Build.FINGERPRINT);
+ if (isPermissionUpgradeNeeded) {
grantPermissionsUserIds = ArrayUtils.appendInt(
grantPermissionsUserIds, userId);
}
@@ -4118,6 +4121,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
// If we upgraded grant all default permissions before kicking off.
for (int userId : grantPermissionsUserIds) {
mLegacyPermissionManager.grantDefaultPermissions(userId);
+ mPermissionManager.setDefaultPermissionGrantFingerprint(Build.FINGERPRINT, userId);
}
if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
// If we did not grant default permissions, we preload from this the
@@ -4286,6 +4290,7 @@ public class PackageManagerService implements PackageSender, TestUtilityService
if (!convertedFromPreCreated || !readPermissionStateForUser(userId)) {
mPermissionManager.onUserCreated(userId);
mLegacyPermissionManager.grantDefaultPermissions(userId);
+ mPermissionManager.setDefaultPermissionGrantFingerprint(Build.FINGERPRINT, userId);
mDomainVerificationManager.clearUser(userId);
}
}
@@ -4295,7 +4300,10 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mPermissionManager.writeLegacyPermissionStateTEMP();
mSettings.readPermissionStateForUserSyncLPr(userId);
mPermissionManager.readLegacyPermissionStateTEMP();
- return mSettings.isPermissionUpgradeNeeded(userId);
+ final boolean isPermissionUpgradeNeeded = !Objects.equals(
+ mPermissionManager.getDefaultPermissionGrantFingerprint(userId),
+ Build.FINGERPRINT);
+ return isPermissionUpgradeNeeded;
}
}
diff --git a/services/core/java/com/android/server/pm/ResilientAtomicFile.java b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
index 19aa4f8e8d0b..54ca426a6dc3 100644
--- a/services/core/java/com/android/server/pm/ResilientAtomicFile.java
+++ b/services/core/java/com/android/server/pm/ResilientAtomicFile.java
@@ -230,7 +230,9 @@ final class ResilientAtomicFile implements Closeable {
+ Log.getStackTraceString(e));
}
- mCurrentFile.delete();
+ if (!mCurrentFile.delete()) {
+ throw new IllegalStateException("Failed to remove " + mCurrentFile);
+ }
mCurrentFile = null;
}
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index ba825774daf6..08934c69e099 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -724,6 +724,10 @@ public final class SuspendPackageHelper {
for (PackageInfo info : pkgInfos) {
result.add(info.packageName);
}
+
+ // Role holder may be null, but ArraySet handles it correctly.
+ result.remove(mPm.getDevicePolicyManagementRoleHolderPackageName(userId));
+
return result;
}
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 d108e1487564..d55f85cde5af 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -986,11 +986,14 @@ public class PackageInfoUtils {
}
/** @see ApplicationInfo#privateFlagsExt */
- public static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
+ private static int appInfoPrivateFlagsExt(int pkgWithoutStateFlags,
@Nullable PackageStateInternal pkgSetting) {
// @formatter:off
- // TODO: Add state specific flags
- return pkgWithoutStateFlags;
+ int flags = pkgWithoutStateFlags;
+ if (pkgSetting != null) {
+ flags |= flag(pkgSetting.getCpuAbiOverride() != null, ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE);
+ }
+ return flags;
// @formatter:on
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 956bcf211ee2..30afb0bf8853 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -687,6 +687,18 @@ public class PermissionManagerService extends IPermissionManager.Stub {
mPermissionManagerServiceImpl.writeLegacyPermissionsTEMP(legacyPermissionSettings);
}
+ @Nullable
+ @Override
+ public String getDefaultPermissionGrantFingerprint(@UserIdInt int userId) {
+ return mPermissionManagerServiceImpl.getDefaultPermissionGrantFingerprint(userId);
+ }
+
+ @Override
+ public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint,
+ @UserIdInt int userId) {
+ mPermissionManagerServiceImpl.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+ }
+
@Override
public void onPackageAdded(@NonNull PackageState packageState, boolean isInstantApp,
@Nullable AndroidPackage oldPkg) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index b4e4ce0623a4..a299b56a191b 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -2833,9 +2833,7 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
} else if (!permissionPolicyInitialized
|| (!hardRestricted || restrictionExempt)) {
if ((origPermState != null && origPermState.isGranted())) {
- if (!uidState.grantPermission(bp)) {
- wasChanged = true;
- }
+ uidState.grantPermission(bp);
}
}
if (mIsLeanback && NOTIFICATION_PERMISSIONS.contains(permName)) {
@@ -4599,6 +4597,19 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt
}
}
+ @Nullable
+ @Override
+ public String getDefaultPermissionGrantFingerprint(@UserIdInt int userId) {
+ return mPackageManagerInt.isPermissionUpgradeNeeded(userId) ? null : Build.FINGERPRINT;
+ }
+
+ @Override
+ public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint,
+ @UserIdInt int userId) {
+ // Ignored - default permission grant here shares the same version with runtime permission
+ // upgrade, and the new version is set by that later.
+ }
+
private void onPackageAddedInternal(@NonNull PackageState packageState,
@NonNull AndroidPackage pkg, boolean isInstantApp, @Nullable AndroidPackage oldPkg) {
if (!pkg.getAdoptPermissions().isEmpty()) {
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
index 8d8df966dfab..128f847715ab 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInterface.java
@@ -523,6 +523,17 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte
void writeLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings);
/**
+ * Get the fingerprint for default permission grants.
+ */
+ @Nullable
+ String getDefaultPermissionGrantFingerprint(@UserIdInt int userId);
+
+ /**
+ * Set the fingerprint for default permission grants.
+ */
+ void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, @UserIdInt int userId);
+
+ /**
* Callback when the system is ready.
*/
void onSystemReady();
@@ -603,6 +614,6 @@ public interface PermissionManagerServiceInterface extends PermissionManagerInte
* @param userId the user ID the package is uninstalled for
*/
void onPackageUninstalled(@NonNull String packageName, int appId,
- @NonNull PackageState packageState, @NonNull AndroidPackage pkg,
+ @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
@NonNull List<AndroidPackage> sharedUserPkgs, @UserIdInt int userId);
}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
index 240a73a4d31a..cf2b69cbc512 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceInternal.java
@@ -215,6 +215,17 @@ public interface PermissionManagerServiceInternal extends PermissionManagerInter
void writeLegacyPermissionsTEMP(@NonNull LegacyPermissionSettings legacyPermissionSettings);
/**
+ * Get the fingerprint for default permission grants.
+ */
+ @Nullable
+ String getDefaultPermissionGrantFingerprint(@UserIdInt int userId);
+
+ /**
+ * Set the fingerprint for default permission grants.
+ */
+ void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, @UserIdInt int userId);
+
+ /**
* Callback when the system is ready.
*/
//@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
index 83ddde5245ce..7f98e2163178 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceLoggingDecorator.java
@@ -356,6 +356,20 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag
mService.writeLegacyPermissionsTEMP(legacyPermissionSettings);
}
+ @Nullable
+ @Override
+ public String getDefaultPermissionGrantFingerprint(int userId) {
+ Log.i(LOG_TAG, "getDefaultPermissionGrantFingerprint(userId = " + userId + ")");
+ return mService.getDefaultPermissionGrantFingerprint(userId);
+ }
+
+ @Override
+ public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint, int userId) {
+ Log.i(LOG_TAG, "setDefaultPermissionGrantFingerprint(fingerprint = " + fingerprint
+ + ", userId = " + userId + ")");
+ mService.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+ }
+
@Override
public void onSystemReady() {
Log.i(LOG_TAG, "onSystemReady()");
@@ -412,7 +426,7 @@ public class PermissionManagerServiceLoggingDecorator implements PermissionManag
@Override
public void onPackageUninstalled(@NonNull String packageName, int appId,
- @NonNull PackageState packageState, @NonNull AndroidPackage pkg,
+ @NonNull PackageState packageState, @Nullable AndroidPackage pkg,
@NonNull List<AndroidPackage> sharedUserPkgs, int userId) {
Log.i(LOG_TAG, "onPackageUninstalled(packageName = " + packageName + ", appId = " + appId
+ ", packageState = " + packageState + ", pkg = " + pkg + ", sharedUserPkgs = "
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
index 317fbe77ba6a..d4c6d42deeaa 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceTestingShim.java
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
import android.content.pm.permission.SplitPermissionInfoParcelable;
+import android.os.Build;
import android.permission.IOnPermissionsChangeListener;
import com.android.server.pm.pkg.AndroidPackage;
@@ -483,6 +484,26 @@ public class PermissionManagerServiceTestingShim implements PermissionManagerSer
mNewImplementation.writeLegacyPermissionsTEMP(legacyPermissionSettings);
}
+ @Nullable
+ @Override
+ public String getDefaultPermissionGrantFingerprint(@UserIdInt int userId) {
+ String oldVal = mOldImplementation.getDefaultPermissionGrantFingerprint(userId);
+ String newVal = mNewImplementation.getDefaultPermissionGrantFingerprint(userId);
+
+ if (Objects.equals(oldVal, Build.FINGERPRINT)
+ != Objects.equals(newVal, Build.FINGERPRINT)) {
+ signalImplDifference("getDefaultPermissionGrantFingerprint");
+ }
+ return newVal;
+ }
+
+ @Override
+ public void setDefaultPermissionGrantFingerprint(@NonNull String fingerprint,
+ @UserIdInt int userId) {
+ mOldImplementation.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+ mNewImplementation.setDefaultPermissionGrantFingerprint(fingerprint, userId);
+ }
+
@Override
public void onSystemReady() {
mOldImplementation.onSystemReady();
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 9ff5431bbdf9..f86d68a1cd9c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -5933,7 +5933,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case HapticFeedbackConstants.CONTEXT_CLICK:
case HapticFeedbackConstants.GESTURE_END:
case HapticFeedbackConstants.GESTURE_THRESHOLD_ACTIVATE:
- case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
+ case HapticFeedbackConstants.SCROLL_TICK:
case HapticFeedbackConstants.SEGMENT_TICK:
return VibrationEffect.get(VibrationEffect.EFFECT_TICK);
@@ -5958,8 +5958,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
case HapticFeedbackConstants.CALENDAR_DATE:
case HapticFeedbackConstants.CONFIRM:
case HapticFeedbackConstants.GESTURE_START:
- case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
- case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+ case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.SCROLL_LIMIT:
return VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
case HapticFeedbackConstants.LONG_PRESS:
@@ -6037,9 +6037,9 @@ public class PhoneWindowManager implements WindowManagerPolicy {
return PHYSICAL_EMULATION_VIBRATION_ATTRIBUTES;
case HapticFeedbackConstants.ASSISTANT_BUTTON:
case HapticFeedbackConstants.LONG_PRESS_POWER_BUTTON:
- case HapticFeedbackConstants.ROTARY_SCROLL_TICK:
- case HapticFeedbackConstants.ROTARY_SCROLL_ITEM_FOCUS:
- case HapticFeedbackConstants.ROTARY_SCROLL_LIMIT:
+ case HapticFeedbackConstants.SCROLL_TICK:
+ case HapticFeedbackConstants.SCROLL_ITEM_FOCUS:
+ case HapticFeedbackConstants.SCROLL_LIMIT:
return HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES;
default:
return TOUCH_VIBRATION_ATTRIBUTES;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index d0fb25a5f773..3caeeaeedd1b 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -40,13 +40,17 @@ import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.app.AppOpsManager;
import android.app.SynchronousUserSwitchObserver;
+import android.app.compat.CompatChanges;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledSince;
+import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.PermissionChecker;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.database.ContentObserver;
@@ -62,6 +66,7 @@ import android.os.BatteryManager;
import android.os.BatteryManagerInternal;
import android.os.BatterySaverPolicyConfig;
import android.os.Binder;
+import android.os.Build;
import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.IBinder;
@@ -277,6 +282,18 @@ public final class PowerManagerService extends SystemService
*/
private static final long ENHANCED_DISCHARGE_PREDICTION_BROADCAST_MIN_DELAY_MS = 60 * 1000L;
+ /**
+ * Apps targeting Android U and above need to define
+ * {@link android.Manifest.permission#TURN_SCREEN_ON} in their manifest for
+ * {@link android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP} to have any effect.
+ * Note that most applications should use {@link android.R.attr#turnScreenOn} or
+ * {@link android.app.Activity#setTurnScreenOn(boolean)} instead, as this prevents the
+ * previous foreground app from being resumed first when the screen turns on.
+ */
+ @ChangeId
+ @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+ public static final long REQUIRE_TURN_SCREEN_ON_PERMISSION = 216114297L;
+
/** Reason ID for holding display suspend blocker. */
private static final String HOLDING_DISPLAY_SUSPEND_BLOCKER = "holding display";
@@ -304,8 +321,9 @@ public final class PowerManagerService extends SystemService
private final SystemPropertiesWrapper mSystemProperties;
private final Clock mClock;
private final Injector mInjector;
+ private final PermissionCheckerWrapper mPermissionCheckerWrapper;
+ private final PowerPropertiesWrapper mPowerPropertiesWrapper;
- private AppOpsManager mAppOpsManager;
private LightsManager mLightsManager;
private BatteryManagerInternal mBatteryManagerInternal;
private DisplayManagerInternal mDisplayManagerInternal;
@@ -1029,11 +1047,66 @@ public final class PowerManagerService extends SystemService
return new LowPowerStandbyController(context, looper);
}
- AppOpsManager createAppOpsManager(Context context) {
- return context.getSystemService(AppOpsManager.class);
+ PermissionCheckerWrapper createPermissionCheckerWrapper() {
+ return PermissionChecker::checkPermissionForDataDelivery;
+ }
+
+ PowerPropertiesWrapper createPowerPropertiesWrapper() {
+ return new PowerPropertiesWrapper() {
+ @Override
+ public boolean waive_target_sdk_check_for_turn_screen_on() {
+ return PowerProperties.waive_target_sdk_check_for_turn_screen_on().orElse(
+ false);
+ }
+
+ @Override
+ public boolean permissionless_turn_screen_on() {
+ return PowerProperties.permissionless_turn_screen_on().orElse(false);
+ }
+ };
}
}
+ /** Interface for checking an app op permission */
+ @VisibleForTesting
+ interface PermissionCheckerWrapper {
+ /**
+ * Checks whether a given data access chain described by the given {@link AttributionSource}
+ * has a given permission and whether the app op that corresponds to this permission
+ * is allowed.
+ * See {@link PermissionChecker#checkPermissionForDataDelivery} for more details.
+ *
+ * @param context Context for accessing resources.
+ * @param permission The permission to check.
+ * @param pid The process id for which to check. Use {@link PermissionChecker#PID_UNKNOWN}
+ * if the PID is not known.
+ * @param attributionSource the permission identity
+ * @param message A message describing the reason the permission was checked
+ * @return The permission check result which is any of
+ * {@link PermissionChecker#PERMISSION_GRANTED},
+ * {@link PermissionChecker#PERMISSION_SOFT_DENIED},
+ * or {@link PermissionChecker#PERMISSION_HARD_DENIED}.
+ */
+ int checkPermissionForDataDelivery(@NonNull Context context, @NonNull String permission,
+ int pid, @NonNull AttributionSource attributionSource, @Nullable String message);
+ }
+
+ /** Interface for querying {@link PowerProperties} */
+ @VisibleForTesting
+ interface PowerPropertiesWrapper {
+ /**
+ * Waives the minimum target-sdk check for android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP
+ * and only allows the flag for apps holding android.permission.TURN_SCREEN_ON
+ */
+ boolean waive_target_sdk_check_for_turn_screen_on();
+
+ /**
+ * Allows apps to turn the screen on with android.os.PowerManager#ACQUIRE_CAUSES_WAKEUP
+ * without being granted android.app.AppOpsManager#OP_TURN_SCREEN_ON.
+ */
+ boolean permissionless_turn_screen_on();
+ }
+
final Constants mConstants;
private native void nativeInit();
@@ -1086,8 +1159,8 @@ public final class PowerManagerService extends SystemService
Looper.getMainLooper());
mInattentiveSleepWarningOverlayController =
mInjector.createInattentiveSleepWarningController();
-
- mAppOpsManager = injector.createAppOpsManager(mContext);
+ mPermissionCheckerWrapper = mInjector.createPermissionCheckerWrapper();
+ mPowerPropertiesWrapper = mInjector.createPowerPropertiesWrapper();
mPowerGroupWakefulnessChangeListener = new PowerGroupWakefulnessChangeListener();
@@ -1562,19 +1635,29 @@ public final class PowerManagerService extends SystemService
}
@RequiresPermission(value = android.Manifest.permission.TURN_SCREEN_ON, conditional = true)
- private boolean isAcquireCausesWakeupFlagAllowed(String opPackageName, int opUid) {
+ private boolean isAcquireCausesWakeupFlagAllowed(String opPackageName, int opUid, int opPid) {
if (opPackageName == null) {
return false;
}
- if (mAppOpsManager.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON, opUid, opPackageName)
- == AppOpsManager.MODE_ALLOWED) {
- if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TURN_SCREEN_ON)
- == PackageManager.PERMISSION_GRANTED) {
- Slog.i(TAG, "Allowing device wake-up from app " + opPackageName);
- return true;
- }
+ if (PermissionChecker.PERMISSION_GRANTED
+ == mPermissionCheckerWrapper.checkPermissionForDataDelivery(mContext,
+ android.Manifest.permission.TURN_SCREEN_ON, opPid,
+ new AttributionSource(opUid, opPackageName, /* attributionTag= */ null),
+ /* message= */ "ACQUIRE_CAUSES_WAKEUP for " + opPackageName)) {
+ Slog.i(TAG, "Allowing device wake-up from app " + opPackageName);
+ return true;
+ }
+ // CompatChanges#isChangeEnabled() returns false for apps with targetSdk < UDC and ensures
+ // backwards compatibility.
+ // waive_target_sdk_check_for_turn_screen_on() returns false by default and may be set to
+ // true on form factors with a more strict policy (e.g. TV)
+ if (!CompatChanges.isChangeEnabled(REQUIRE_TURN_SCREEN_ON_PERMISSION, opUid)
+ && !mPowerPropertiesWrapper.waive_target_sdk_check_for_turn_screen_on()) {
+ Slog.i(TAG, "Allowing device wake-up without android.permission.TURN_SCREEN_ON for "
+ + opPackageName);
+ return true;
}
- if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
+ if (mPowerPropertiesWrapper.permissionless_turn_screen_on()) {
Slog.d(TAG, "Device wake-up allowed by debug.power.permissionless_turn_screen_on");
return true;
}
@@ -1589,6 +1672,7 @@ public final class PowerManagerService extends SystemService
&& isScreenLock(wakeLock)) {
String opPackageName;
int opUid;
+ int opPid = PermissionChecker.PID_UNKNOWN;
if (wakeLock.mWorkSource != null && !wakeLock.mWorkSource.isEmpty()) {
WorkSource workSource = wakeLock.mWorkSource;
WorkChain workChain = getFirstNonEmptyWorkChain(workSource);
@@ -1603,10 +1687,12 @@ public final class PowerManagerService extends SystemService
} else {
opPackageName = wakeLock.mPackageName;
opUid = wakeLock.mOwnerUid;
+ opPid = wakeLock.mOwnerPid;
}
Integer powerGroupId = wakeLock.getPowerGroupId();
// powerGroupId is null if the wakelock associated display is no longer available
- if (powerGroupId != null && isAcquireCausesWakeupFlagAllowed(opPackageName, opUid)) {
+ if (powerGroupId != null
+ && isAcquireCausesWakeupFlagAllowed(opPackageName, opUid, opPid)) {
if (powerGroupId == Display.INVALID_DISPLAY_GROUP) {
// wake up all display groups
if (DEBUG_SPEW) {
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index d0088457a9f2..6e43690ad080 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -45,6 +45,7 @@ import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.AppGlobals;
import android.app.AppOpsManager;
+import android.app.ApplicationExitInfo;
import android.app.ILocalWallpaperColorConsumer;
import android.app.IWallpaperManager;
import android.app.IWallpaperManagerCallback;
@@ -187,6 +188,10 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
/** Tracks wallpaper being migrated from system+lock to lock when setting static wp. */
WallpaperDestinationChangeHandler mPendingMigrationViaStatic;
+ private static final double LMK_LOW_THRESHOLD_MEMORY_PERCENTAGE = 10;
+ private static final int LMK_RECONNECT_REBIND_RETRIES = 3;
+ private static final long LMK_RECONNECT_DELAY_MS = 5000;
+
/**
* Minimum time between crashes of a wallpaper service for us to consider
* restarting it vs. just reverting to the static wallpaper.
@@ -988,6 +993,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
/** Time in milliseconds until we expect the wallpaper to reconnect (unless we're in the
* middle of an update). If exceeded, the wallpaper gets reset to the system default. */
private static final long WALLPAPER_RECONNECT_TIMEOUT_MS = 10000;
+ private int mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
final WallpaperInfo mInfo;
IWallpaperService mService;
@@ -1209,20 +1215,51 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
&& mWallpaper.userId == mCurrentUserId
&& !Objects.equals(mDefaultWallpaperComponent, wpService)
&& !Objects.equals(mImageWallpaper, wpService)) {
- // There is a race condition which causes
- // {@link #mWallpaper.wallpaperUpdating} to be false even if it is
- // currently updating since the broadcast notifying us is async.
- // This race is overcome by the general rule that we only reset the
- // wallpaper if its service was shut down twice
- // during {@link #MIN_WALLPAPER_CRASH_TIME} millis.
- if (mWallpaper.lastDiedTime != 0
- && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
- > SystemClock.uptimeMillis()) {
- Slog.w(TAG, "Reverting to built-in wallpaper!");
- clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ List<ApplicationExitInfo> reasonList =
+ mActivityManager.getHistoricalProcessExitReasons(
+ wpService.getPackageName(), 0, 1);
+ int exitReason = ApplicationExitInfo.REASON_UNKNOWN;
+ if (reasonList != null && !reasonList.isEmpty()) {
+ ApplicationExitInfo info = reasonList.get(0);
+ exitReason = info.getReason();
+ }
+ Slog.d(TAG, "exitReason: " + exitReason);
+ // If exit reason is LOW_MEMORY_KILLER
+ // delay the mTryToRebindRunnable for 10s
+ if (exitReason == ApplicationExitInfo.REASON_LOW_MEMORY) {
+ if (isRunningOnLowMemory()) {
+ Slog.i(TAG, "Rebind is delayed due to lmk");
+ mContext.getMainThreadHandler().postDelayed(mTryToRebindRunnable,
+ LMK_RECONNECT_DELAY_MS);
+ mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
+ } else {
+ if (mLmkLimitRebindRetries <= 0) {
+ Slog.w(TAG, "Reverting to built-in wallpaper due to lmk!");
+ clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId,
+ null);
+ mLmkLimitRebindRetries = LMK_RECONNECT_REBIND_RETRIES;
+ return;
+ }
+ mLmkLimitRebindRetries--;
+ mContext.getMainThreadHandler().postDelayed(mTryToRebindRunnable,
+ LMK_RECONNECT_DELAY_MS);
+ }
} else {
- mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
- tryToRebind();
+ // There is a race condition which causes
+ // {@link #mWallpaper.wallpaperUpdating} to be false even if it is
+ // currently updating since the broadcast notifying us is async.
+ // This race is overcome by the general rule that we only reset the
+ // wallpaper if its service was shut down twice
+ // during {@link #MIN_WALLPAPER_CRASH_TIME} millis.
+ if (mWallpaper.lastDiedTime != 0
+ && mWallpaper.lastDiedTime + MIN_WALLPAPER_CRASH_TIME
+ > SystemClock.uptimeMillis()) {
+ Slog.w(TAG, "Reverting to built-in wallpaper!");
+ clearWallpaperLocked(true, FLAG_SYSTEM, mWallpaper.userId, null);
+ } else {
+ mWallpaper.lastDiedTime = SystemClock.uptimeMillis();
+ tryToRebind();
+ }
}
}
} else {
@@ -1233,6 +1270,14 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
};
+ private boolean isRunningOnLowMemory() {
+ ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+ mActivityManager.getMemoryInfo(memoryInfo);
+ double availableMBsInPercentage = memoryInfo.availMem / (double)memoryInfo.totalMem *
+ 100.0;
+ return availableMBsInPercentage < LMK_LOW_THRESHOLD_MEMORY_PERCENTAGE;
+ }
+
/**
* Called by a live wallpaper if its colors have changed.
* @param primaryColors representation of wallpaper primary colors
@@ -1775,21 +1820,23 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
if (record.exists()) {
Slog.w(TAG, "User:" + userID + ", wallpaper tyep = " + type
+ ", wallpaper fail detect!! reset to default wallpaper");
- clearWallpaperData(userID, type);
+ clearWallpaperBitmaps(userID, type);
record.delete();
}
});
}
- private void clearWallpaperData(int userID, int wallpaperType) {
+ private void clearWallpaperBitmaps(int userID, int wallpaperType) {
final WallpaperData wallpaper = new WallpaperData(userID, wallpaperType);
- if (wallpaper.sourceExists()) {
- wallpaper.wallpaperFile.delete();
- }
- if (wallpaper.cropExists()) {
- wallpaper.cropFile.delete();
- }
+ clearWallpaperBitmaps(wallpaper);
+ }
+ private boolean clearWallpaperBitmaps(WallpaperData wallpaper) {
+ boolean sourceExists = wallpaper.sourceExists();
+ boolean cropExists = wallpaper.cropExists();
+ if (sourceExists) wallpaper.wallpaperFile.delete();
+ if (cropExists) wallpaper.cropFile.delete();
+ return sourceExists || cropExists;
}
@Override
@@ -1966,10 +2013,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
// files from the previous static wallpaper may still be stored in memory.
// delete them in order to show the default wallpaper.
- if (wallpaper.wallpaperFile.exists()) {
- wallpaper.wallpaperFile.delete();
- wallpaper.cropFile.delete();
- }
+ clearWallpaperBitmaps(wallpaper);
bindWallpaperComponentLocked(mImageWallpaper, true, false, fallback, reply);
if ((wallpaper.mWhich & FLAG_SYSTEM) != 0) mHomeWallpaperWaitingForUnlock = true;
@@ -2034,9 +2078,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
final long ident = Binder.clearCallingIdentity();
try {
- if (wallpaper.wallpaperFile.exists()) {
- wallpaper.wallpaperFile.delete();
- wallpaper.cropFile.delete();
+ if (clearWallpaperBitmaps(wallpaper)) {
if (which == FLAG_LOCK) {
mLockWallpaperMap.remove(userId);
final IWallpaperManagerCallback cb = mKeyguardListener;
@@ -3107,9 +3149,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
}
} catch (ErrnoException e) {
Slog.e(TAG, "Can't migrate system wallpaper: " + e.getMessage());
- lockWP.wallpaperFile.delete();
- lockWP.cropFile.delete();
- return;
+ clearWallpaperBitmaps(lockWP);
}
}
@@ -3251,6 +3291,9 @@ public class WallpaperManagerService extends IWallpaperManager.Stub
});
}
}
+ if (!mImageWallpaper.equals(newWallpaper.wallpaperComponent)) {
+ clearWallpaperBitmaps(newWallpaper);
+ }
newWallpaper.wallpaperId = makeWallpaperIdLocked();
notifyCallbacksLocked(newWallpaper);
shouldNotifyColors = true;
diff --git a/services/core/java/com/android/server/webkit/OWNERS b/services/core/java/com/android/server/webkit/OWNERS
index 00e540a46ab2..e7fd7a5d1096 100644
--- a/services/core/java/com/android/server/webkit/OWNERS
+++ b/services/core/java/com/android/server/webkit/OWNERS
@@ -1,3 +1,3 @@
-changwan@google.com
-tobiasjs@google.com
+# Bug component: 76427
+ntfschr@google.com
torne@google.com
diff --git a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
index bd07622ee5ca..10a2b9717555 100644
--- a/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
+++ b/services/core/java/com/android/server/wm/AbsAppSnapshotController.java
@@ -167,6 +167,9 @@ abstract class AbsAppSnapshotController<TYPE extends WindowContainer,
}
final TaskSnapshot recordSnapshotInner(TYPE source, boolean allowSnapshotHome) {
+ if (shouldDisableSnapshots()) {
+ return null;
+ }
final boolean snapshotHome = allowSnapshotHome && source.isActivityTypeHome();
final TaskSnapshot snapshot = captureSnapshot(source, snapshotHome);
if (snapshot == null) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 9af34bcb448b..79a6af90fbae 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -4295,6 +4295,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
mTaskSupervisor.mStoppingActivities.remove(this);
+ mLetterboxUiController.destroy();
waitingToShow = false;
// Defer removal of this activity when either a child is animating, or app transition is on
@@ -4364,8 +4365,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mWmService.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/);
}
- mLetterboxUiController.destroy();
-
if (!delayed) {
updateReportedVisibilityLocked();
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 90af4c6236aa..1eb56f1b7d1c 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -280,6 +280,10 @@ class ActivityStartInterceptor {
return false;
}
+ if (isKeepProfilesRunningEnabled() && !isPackageSuspended()) {
+ return false;
+ }
+
IntentSender target = createIntentSenderForOriginalIntent(mCallingUid,
FLAG_CANCEL_CURRENT | FLAG_ONE_SHOT);
@@ -322,8 +326,7 @@ class ActivityStartInterceptor {
private boolean interceptSuspendedPackageIfNeeded() {
// Do not intercept if the package is not suspended
- if (mAInfo == null || mAInfo.applicationInfo == null ||
- (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) == 0) {
+ if (!isPackageSuspended()) {
return false;
}
final PackageManagerInternal pmi = mService.getPackageManagerInternalLocked();
@@ -467,6 +470,17 @@ class ActivityStartInterceptor {
return true;
}
+ private boolean isPackageSuspended() {
+ return mAInfo != null && mAInfo.applicationInfo != null
+ && (mAInfo.applicationInfo.flags & FLAG_SUSPENDED) != 0;
+ }
+
+ private static boolean isKeepProfilesRunningEnabled() {
+ DevicePolicyManagerInternal dpmi =
+ LocalServices.getService(DevicePolicyManagerInternal.class);
+ return dpmi == null || dpmi.isKeepProfilesRunningEnabled();
+ }
+
/**
* Called when an activity is successfully launched.
*/
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 0171c200b56c..3c976725cfee 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -2367,6 +2367,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
final ActivityRecord prevTopActivity = mTopResumedActivity;
final Task topRootTask = mRootWindowContainer.getTopDisplayFocusedRootTask();
if (topRootTask == null || topRootTask.getTopResumedActivity() == prevTopActivity) {
+ if (topRootTask == null) {
+ // There's no focused task and there won't have any resumed activity either.
+ scheduleTopResumedActivityStateLossIfNeeded();
+ }
if (mService.isSleepingLocked()) {
// There won't be a next resumed activity. The top process should still be updated
// according to the current top focused activity.
@@ -2376,16 +2380,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
// Ask previous activity to release the top state.
- final boolean prevActivityReceivedTopState =
- prevTopActivity != null && !mTopResumedActivityWaitingForPrev;
- // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
- // before the prevTopActivity one hasn't reported back yet. So server never sent the top
- // resumed state change message to prevTopActivity.
- if (prevActivityReceivedTopState
- && prevTopActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
- scheduleTopResumedStateLossTimeout(prevTopActivity);
- mTopResumedActivityWaitingForPrev = true;
- }
+ scheduleTopResumedActivityStateLossIfNeeded();
// Update the current top activity.
mTopResumedActivity = topRootTask.getTopResumedActivity();
@@ -2410,6 +2405,23 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mService.updateTopApp(mTopResumedActivity);
}
+ /** Schedule current top resumed activity state loss */
+ private void scheduleTopResumedActivityStateLossIfNeeded() {
+ if (mTopResumedActivity == null) {
+ return;
+ }
+
+ // mTopResumedActivityWaitingForPrev == true at this point would mean that an activity
+ // before the prevTopActivity one hasn't reported back yet. So server never sent the top
+ // resumed state change message to prevTopActivity.
+ if (!mTopResumedActivityWaitingForPrev
+ && mTopResumedActivity.scheduleTopResumedActivityChanged(false /* onTop */)) {
+ scheduleTopResumedStateLossTimeout(mTopResumedActivity);
+ mTopResumedActivityWaitingForPrev = true;
+ mTopResumedActivity = null;
+ }
+ }
+
/** Schedule top resumed state change if previous top activity already reported back. */
private void scheduleTopResumedActivityStateIfNeeded() {
if (mTopResumedActivity != null && !mTopResumedActivityWaitingForPrev) {
diff --git a/services/core/java/com/android/server/wm/AppWarnings.java b/services/core/java/com/android/server/wm/AppWarnings.java
index 123a74dbf597..f7ccc0d91969 100644
--- a/services/core/java/com/android/server/wm/AppWarnings.java
+++ b/services/core/java/com/android/server/wm/AppWarnings.java
@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Handler;
@@ -178,6 +179,14 @@ class AppWarnings {
* @param r activity record for which the warning may be displayed
*/
public void showDeprecatedAbiDialogIfNeeded(ActivityRecord r) {
+ final boolean isUsingAbiOverride = (r.info.applicationInfo.privateFlagsExt
+ & ApplicationInfo.PRIVATE_FLAG_EXT_CPU_OVERRIDE) != 0;
+ if (isUsingAbiOverride) {
+ // The abiOverride flag was specified during installation, which means that if the app
+ // is currently running in 32-bit mode, it is intended. Do not show the warning dialog.
+ return;
+ }
+ // The warning dialog can also be disabled for debugging purpose
final boolean disableDeprecatedAbiDialog = SystemProperties.getBoolean(
"debug.wm.disable_deprecated_abi_dialog", false);
if (disableDeprecatedAbiDialog) {
diff --git a/services/core/java/com/android/server/wm/DeviceStateController.java b/services/core/java/com/android/server/wm/DeviceStateController.java
index 31b1069dd022..db4762e5f877 100644
--- a/services/core/java/com/android/server/wm/DeviceStateController.java
+++ b/services/core/java/com/android/server/wm/DeviceStateController.java
@@ -111,9 +111,13 @@ final class DeviceStateController {
}
/**
- * @return true if the rotation direction on the Z axis should be reversed.
+ * @return true if the rotation direction on the Z axis should be reversed for the default
+ * display.
*/
- boolean shouldReverseRotationDirectionAroundZAxis() {
+ boolean shouldReverseRotationDirectionAroundZAxis(@NonNull DisplayContent displayContent) {
+ if (!displayContent.isDefaultDisplay) {
+ return false;
+ }
return ArrayUtils.contains(mReverseRotationAroundZAxisStates, mCurrentState);
}
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 6aec96988a77..7fc86b0fdc01 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5602,17 +5602,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
*/
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
@WindowManager.TransitionFlags int flags) {
- prepareAppTransition(transit, flags);
- mTransitionController.requestTransitionIfNeeded(transit, flags,
- null /* trigger */, this);
+ requestTransitionAndLegacyPrepare(transit, flags, null /* trigger */);
}
/** @see #requestTransitionAndLegacyPrepare(int, int) */
void requestTransitionAndLegacyPrepare(@WindowManager.TransitionType int transit,
- @Nullable WindowContainer trigger) {
- prepareAppTransition(transit);
- mTransitionController.requestTransitionIfNeeded(transit, 0 /* flags */,
- trigger, this);
+ @WindowManager.TransitionFlags int flags, @Nullable WindowContainer trigger) {
+ prepareAppTransition(transit, flags);
+ mTransitionController.requestTransitionIfNeeded(transit, flags, trigger, this);
}
void executeAppTransition() {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 8be36f07a040..b681c198538f 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -950,7 +950,7 @@ public class DisplayRotation {
}
void freezeRotation(int rotation) {
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
rotation = RotationUtils.reverseRotationDirectionAroundZAxis(rotation);
}
@@ -1225,7 +1225,7 @@ public class DisplayRotation {
if (mFoldController != null && mFoldController.shouldIgnoreSensorRotation()) {
sensorRotation = -1;
}
- if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()) {
+ if (mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mDisplayContent)) {
sensorRotation = RotationUtils.reverseRotationDirectionAroundZAxis(sensorRotation);
}
mLastSensorRotation = sensorRotation;
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 7f845e6c1ead..21004ab8d75a 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -272,15 +272,17 @@ class InsetsSourceProvider {
/** @return A new source computed by the specified window frame in the given display frames. */
InsetsSource createSimulatedSource(DisplayFrames displayFrames, Rect frame) {
- // Don't copy visible frame because it might not be calculated in the provided display
- // frames and it is not significant for this usage.
- final InsetsSource source = new InsetsSource(mSource.getId(), mSource.getType());
- source.setVisible(mSource.isVisible());
+ final InsetsSource source = new InsetsSource(mSource);
mTmpRect.set(frame);
if (mFrameProvider != null) {
mFrameProvider.apply(displayFrames, mWindowContainer, mTmpRect);
}
source.setFrame(mTmpRect);
+
+ // Don't copy visible frame because it might not be calculated in the provided display
+ // frames and it is not significant for this usage.
+ source.setVisibleFrame(null);
+
return source;
}
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 99878a3d0ffe..ad9c3b274267 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -19,17 +19,21 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_APPEARING;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_SUBTLE_ANIMATION;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_OCCLUDING;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_KEYGUARD_OCCLUDE;
import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
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.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_SUBTLE_WINDOW_ANIMATIONS;
import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
@@ -171,10 +175,11 @@ class KeyguardController {
final KeyguardDisplayState state = getDisplayState(displayId);
final boolean aodChanged = aodShowing != state.mAodShowing;
final boolean aodRemoved = state.mAodShowing && !aodShowing;
+ final boolean goingAwayRemoved = state.mKeyguardGoingAway && keyguardShowing;
// If keyguard is going away, but SystemUI aborted the transition, need to reset state.
// Do not reset keyguardChanged status when only AOD is removed.
final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
- || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
+ || (goingAwayRemoved && !aodRemoved);
if (aodRemoved) {
updateDeferTransitionForAod(false /* waiting */);
}
@@ -214,6 +219,15 @@ class KeyguardController {
if (keyguardShowing) {
state.mDismissalRequested = false;
}
+ if (goingAwayRemoved) {
+ // Keyguard dismiss is canceled. Send a transition to undo the changes and clean up
+ // before holding the sleep token again.
+ final DisplayContent dc = mRootWindowContainer.getDefaultDisplay();
+ dc.requestTransitionAndLegacyPrepare(
+ TRANSIT_TO_FRONT, TRANSIT_FLAG_KEYGUARD_APPEARING);
+ dc.mWallpaperController.showWallpaperInTransition(false /* showHome */);
+ mWindowManager.executeAppTransition();
+ }
}
// Update the sleep token first such that ensureActivitiesVisible has correct sleep token
@@ -269,7 +283,7 @@ class KeyguardController {
updateKeyguardSleepToken();
// Make the home wallpaper visible
- dc.mWallpaperController.showHomeWallpaperInTransition();
+ dc.mWallpaperController.showWallpaperInTransition(true /* showHome */);
// Some stack visibility might change (e.g. docked stack)
mRootWindowContainer.resumeFocusedTasksTopActivities();
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
@@ -413,10 +427,12 @@ class KeyguardController {
if (isDisplayOccluded(DEFAULT_DISPLAY)) {
mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
TRANSIT_KEYGUARD_OCCLUDE,
+ TRANSIT_FLAG_KEYGUARD_OCCLUDING,
topActivity != null ? topActivity.getRootTask() : null);
} else {
mRootWindowContainer.getDefaultDisplay().requestTransitionAndLegacyPrepare(
- TRANSIT_KEYGUARD_UNOCCLUDE, 0 /* flags */);
+ TRANSIT_KEYGUARD_UNOCCLUDE,
+ TRANSIT_FLAG_KEYGUARD_UNOCCLUDING);
}
updateKeyguardSleepToken(DEFAULT_DISPLAY);
mWindowManager.executeAppTransition();
diff --git a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
index afef85e34fe0..58e1c544202d 100644
--- a/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
+++ b/services/core/java/com/android/server/wm/SnapshotPersistQueue.java
@@ -314,6 +314,11 @@ class SnapshotPersistQueue {
}
final Bitmap swBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, false /* isMutable */);
+ if (swBitmap == null) {
+ Slog.e(TAG, "Bitmap conversion from (config=" + bitmap.getConfig() + ", isMutable="
+ + bitmap.isMutable() + ") to (config=ARGB_8888, isMutable=false) failed.");
+ return false;
+ }
final File file = mPersistInfoProvider.getHighResolutionBitmapFile(mId, mUserId);
try {
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index ce4f44540285..1ae1e03a6c4a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1847,6 +1847,9 @@ class Task extends TaskFragment {
td.setEnsureStatusBarContrastWhenTransparent(
atd.getEnsureStatusBarContrastWhenTransparent());
}
+ if (td.getStatusBarAppearance() == 0) {
+ td.setStatusBarAppearance(atd.getStatusBarAppearance());
+ }
if (td.getNavigationBarColor() == 0) {
td.setNavigationBarColor(atd.getNavigationBarColor());
td.setEnsureNavigationBarContrastWhenTransparent(
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b938b9e4ec51..9a5f766989ca 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -52,7 +52,6 @@ import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
-import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TASK_LAUNCHING_BEHIND;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -2820,9 +2819,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
}
}
}
- if (occludesKeyguard(wc)) {
- flags |= FLAG_OCCLUDES_KEYGUARD;
- }
if ((mFlags & FLAG_CHANGE_NO_ANIMATION) != 0
&& (mFlags & FLAG_CHANGE_YES_ANIMATION) == 0) {
flags |= FLAG_NO_ANIMATION;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index 1c6a412e5450..0cb6f14b38f2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -16,10 +16,10 @@
package com.android.server.wm;
+import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -619,9 +619,9 @@ class TransitionController {
}
// Make the collecting transition wait until this request is ready.
mCollectingTransition.setReady(readyGroupRef, false);
- if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY) != 0) {
- // Add keyguard flag to dismiss keyguard
- mCollectingTransition.addFlag(flags);
+ if ((flags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS) != 0) {
+ // Add keyguard flags to affect keyguard visibility
+ mCollectingTransition.addFlag(flags & KEYGUARD_VISIBILITY_TRANSIT_FLAGS);
}
} else {
newTransition = requestStartTransition(createTransition(type, flags),
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 64038355b1df..f54a962e4897 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -339,12 +339,12 @@ class WallpaperController {
}
/**
- * Change the visibility if wallpaper is home screen only.
+ * Make one wallpaper visible, according to {@attr showHome}.
* This is called during the keyguard unlocking transition
- * (see {@link KeyguardController#keyguardGoingAway(int, int)}) and thus assumes that if the
- * system wallpaper is shared with lock, then it needs no animation.
+ * (see {@link KeyguardController#keyguardGoingAway(int, int)}),
+ * or when a keyguard unlock is cancelled (see {@link KeyguardController})
*/
- public void showHomeWallpaperInTransition() {
+ public void showWallpaperInTransition(boolean showHome) {
updateWallpaperWindowsTarget(mFindResults);
if (!mFindResults.hasTopShowWhenLockedWallpaper()) {
@@ -357,9 +357,9 @@ class WallpaperController {
// Shared wallpaper, ensure its visibility
showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindows(true);
} else {
- // Separate lock and home wallpapers: show home wallpaper and hide lock
- hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(true);
- showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(false);
+ // Separate lock and home wallpapers: show the correct wallpaper in transition
+ hideWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(showHome);
+ showWhenLocked.mToken.asWallpaperToken().updateWallpaperWindowsInTransition(!showHome);
}
}
@@ -401,6 +401,19 @@ class WallpaperController {
// swiping through Launcher pages).
final Rect wallpaperFrame = wallpaperWin.getFrame();
+ final int diffWidth = wallpaperFrame.width() - lastWallpaperBounds.width();
+ final int diffHeight = wallpaperFrame.height() - lastWallpaperBounds.height();
+ if ((wallpaperWin.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0
+ && Math.abs(diffWidth) > 1 && Math.abs(diffHeight) > 1) {
+ Slog.d(TAG, "Skip wallpaper offset with inconsistent orientation, bounds="
+ + lastWallpaperBounds + " frame=" + wallpaperFrame);
+ // With FLAG_SCALED, the requested size should at least make the frame match one of
+ // side. If both sides contain differences, the client side may not have updated the
+ // latest size according to the current orientation. So skip calculating the offset to
+ // avoid the wallpaper not filling the screen.
+ return false;
+ }
+
int newXOffset = 0;
int newYOffset = 0;
boolean rawChanged = false;
@@ -417,7 +430,7 @@ class WallpaperController {
float wpxs = mLastWallpaperXStep >= 0 ? mLastWallpaperXStep : -1.0f;
// Difference between width of wallpaper image, and the last size of the wallpaper.
// This is the horizontal surplus from the prior configuration.
- int availw = wallpaperFrame.width() - lastWallpaperBounds.width();
+ int availw = diffWidth;
int displayOffset = getDisplayWidthOffset(availw, lastWallpaperBounds,
wallpaperWin.isRtl());
@@ -442,9 +455,7 @@ class WallpaperController {
float wpy = mLastWallpaperY >= 0 ? mLastWallpaperY : 0.5f;
float wpys = mLastWallpaperYStep >= 0 ? mLastWallpaperYStep : -1.0f;
- int availh = wallpaperWin.getFrame().bottom - wallpaperWin.getFrame().top
- - lastWallpaperBounds.height();
- offset = availh > 0 ? -(int)(availh * wpy + .5f) : 0;
+ offset = diffHeight > 0 ? -(int) (diffHeight * wpy + .5f) : 0;
if (mLastWallpaperDisplayOffsetY != Integer.MIN_VALUE) {
offset += mLastWallpaperDisplayOffsetY;
}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a2fde621a06f..c4e3aae0a7c9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7161,15 +7161,45 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void requestAppKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
- mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS,
- "requestAppKeyboardShortcuts");
+ enforceRegisterWindowManagerListenersPermission("requestAppKeyboardShortcuts");
+ WindowState focusedWindow = getFocusedWindow();
+ if (focusedWindow == null || focusedWindow.mClient == null) {
+ notifyReceiverWithEmptyBundle(receiver);
+ return;
+ }
try {
- WindowState focusedWindow = getFocusedWindow();
- if (focusedWindow != null && focusedWindow.mClient != null) {
- getFocusedWindow().mClient.requestAppKeyboardShortcuts(receiver, deviceId);
- }
+ focusedWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
+ } catch (RemoteException e) {
+ notifyReceiverWithEmptyBundle(receiver);
+ }
+ }
+
+ @Override
+ public void requestImeKeyboardShortcuts(IResultReceiver receiver, int deviceId) {
+ enforceRegisterWindowManagerListenersPermission("requestImeKeyboardShortcuts");
+
+ WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
+ if (imeWindow == null || imeWindow.mClient == null) {
+ notifyReceiverWithEmptyBundle(receiver);
+ return;
+ }
+ try {
+ imeWindow.mClient.requestAppKeyboardShortcuts(receiver, deviceId);
+ } catch (RemoteException e) {
+ notifyReceiverWithEmptyBundle(receiver);
+ }
+ }
+
+ private void enforceRegisterWindowManagerListenersPermission(String message) {
+ mContext.enforceCallingOrSelfPermission(REGISTER_WINDOW_MANAGER_LISTENERS, message);
+ }
+
+ private static void notifyReceiverWithEmptyBundle(IResultReceiver receiver) {
+ try {
+ receiver.send(0, Bundle.EMPTY);
} catch (RemoteException e) {
+ ProtoLog.e(WM_ERROR, "unable to call receiver for empty keyboard shortcuts");
}
}
@@ -8246,6 +8276,13 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void addTrustedTaskOverlay(int taskId,
SurfaceControlViewHost.SurfacePackage overlay) {
+ if (overlay == null) {
+ throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+ } else if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
synchronized (mGlobalLock) {
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
@@ -8258,6 +8295,13 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void removeTrustedTaskOverlay(int taskId,
SurfaceControlViewHost.SurfacePackage overlay) {
+ if (overlay == null) {
+ throw new IllegalArgumentException("Invalid overlay passed in for task=" + taskId);
+ } else if (overlay.getSurfaceControl() == null
+ || !overlay.getSurfaceControl().isValid()) {
+ throw new IllegalArgumentException(
+ "Invalid overlay surfacecontrol passed in for task=" + taskId);
+ }
synchronized (mGlobalLock) {
final Task task = mRoot.getRootTask(taskId);
if (task == null) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 7cd7f6975bbd..b52935e9da9b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1445,6 +1445,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
Slog.v(TAG_WM, "Win " + this + " config changed: " + getConfiguration());
}
+ final boolean dragResizingChanged = !mDragResizingChangeReported && isDragResizeChanged();
+
final boolean attachedFrameChanged = LOCAL_LAYOUT
&& mLayoutAttached && getParentWindow().frameChanged();
@@ -1458,6 +1460,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (didFrameInsetsChange
|| configChanged
|| insetsChanged
+ || dragResizingChanged
|| shouldSendRedrawForSync()
|| attachedFrameChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
@@ -1478,7 +1481,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// Reset the drawn state if the window need to redraw for the change, so the transition
// can wait until it has finished drawing to start.
- if ((configChanged || getOrientationChanging()) && isVisibleRequested()) {
+ if ((configChanged || getOrientationChanging() || dragResizingChanged)
+ && isVisibleRequested()) {
winAnimator.mDrawState = DRAW_PENDING;
if (mActivityRecord != null) {
mActivityRecord.clearAllDrawn();
@@ -3883,13 +3887,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
/**
- * @return true if activity bounds are letterboxed or letterboxed for diplay cutout.
+ * @return {@code true} if activity bounds are letterboxed or letterboxed for display cutout.
+ * Note that it's always {@code false} if the activity is in pip mode.
*
* <p>Note that letterbox UI may not be shown even when this returns {@code true}. See {@link
* LetterboxUiController#shouldShowLetterboxUi} for more context.
*/
boolean areAppWindowBoundsLetterboxed() {
return mActivityRecord != null
+ && !mActivityRecord.inPinnedWindowingMode()
&& (mActivityRecord.areBoundsLetterboxed() || isLetterboxedForDisplayCutout());
}
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index d5217c8295bd..101af4d36162 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -177,7 +177,7 @@ cc_defaults {
"android.hardware.power@1.1",
"android.hardware.power@1.2",
"android.hardware.power@1.3",
- "android.hardware.power-V4-cpp",
+ "android.hardware.power-V4-ndk",
"android.hardware.power.stats@1.0",
"android.hardware.power.stats-V1-ndk",
"android.hardware.thermal@1.0",
diff --git a/services/core/jni/com_android_server_hint_HintManagerService.cpp b/services/core/jni/com_android_server_hint_HintManagerService.cpp
index e322fa23e5b4..e148b94bb0a5 100644
--- a/services/core/jni/com_android_server_hint_HintManagerService.cpp
+++ b/services/core/jni/com_android_server_hint_HintManagerService.cpp
@@ -18,77 +18,78 @@
//#define LOG_NDEBUG 0
+#include <aidl/android/hardware/power/IPower.h>
#include <android-base/stringprintf.h>
-#include <android/hardware/power/IPower.h>
-#include <android_runtime/AndroidRuntime.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedPrimitiveArray.h>
#include <powermanager/PowerHalController.h>
#include <utils/Log.h>
-#include <unistd.h>
-#include <cinttypes>
-
-#include <sys/types.h>
+#include <unordered_map>
#include "jni.h"
-using android::hardware::power::IPowerHintSession;
-using android::hardware::power::SessionHint;
-using android::hardware::power::WorkDuration;
+using aidl::android::hardware::power::IPowerHintSession;
+using aidl::android::hardware::power::SessionHint;
+using aidl::android::hardware::power::WorkDuration;
using android::base::StringPrintf;
namespace android {
static power::PowerHalController gPowerHalController;
+static std::unordered_map<jlong, std::shared_ptr<IPowerHintSession>> gSessionMap;
+static std::mutex gSessionMapLock;
static jlong createHintSession(JNIEnv* env, int32_t tgid, int32_t uid,
std::vector<int32_t> threadIds, int64_t durationNanos) {
- auto result =
- gPowerHalController.createHintSession(tgid, uid, std::move(threadIds), durationNanos);
+ auto result = gPowerHalController.createHintSession(tgid, uid, threadIds, durationNanos);
if (result.isOk()) {
- sp<IPowerHintSession> appSession = result.value();
- if (appSession) appSession->incStrong(env);
- return reinterpret_cast<jlong>(appSession.get());
+ auto session_ptr = reinterpret_cast<jlong>(result.value().get());
+ {
+ std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
+ auto res = gSessionMap.insert({session_ptr, result.value()});
+ return res.second ? session_ptr : 0;
+ }
}
return 0;
}
static void pauseHintSession(JNIEnv* env, int64_t session_ptr) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->pause();
}
static void resumeHintSession(JNIEnv* env, int64_t session_ptr) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->resume();
}
static void closeHintSession(JNIEnv* env, int64_t session_ptr) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->close();
- appSession->decStrong(env);
+ std::unique_lock<std::mutex> sessionLock(gSessionMapLock);
+ gSessionMap.erase(session_ptr);
}
static void updateTargetWorkDuration(int64_t session_ptr, int64_t targetDurationNanos) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->updateTargetWorkDuration(targetDurationNanos);
}
static void reportActualWorkDuration(int64_t session_ptr,
const std::vector<WorkDuration>& actualDurations) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->reportActualWorkDuration(actualDurations);
}
static void sendHint(int64_t session_ptr, SessionHint hint) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->sendHint(hint);
}
static void setThreads(int64_t session_ptr, const std::vector<int32_t>& threadIds) {
- sp<IPowerHintSession> appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
+ auto appSession = reinterpret_cast<IPowerHintSession*>(session_ptr);
appSession->setThreads(threadIds);
}
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.cpp b/services/core/jni/com_android_server_power_PowerManagerService.cpp
index fe86ff1ef7ef..6ab98feff210 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/core/jni/com_android_server_power_PowerManagerService.cpp
@@ -18,48 +18,40 @@
//#define LOG_NDEBUG 0
+#include "com_android_server_power_PowerManagerService.h"
+
+#include <aidl/android/hardware/power/Boost.h>
+#include <aidl/android/hardware/power/Mode.h>
#include <aidl/android/system/suspend/ISystemSuspend.h>
#include <aidl/android/system/suspend/IWakeLock.h>
-#include <android/hardware/power/1.1/IPower.h>
-#include <android/hardware/power/Boost.h>
-#include <android/hardware/power/IPower.h>
-#include <android/hardware/power/Mode.h>
-#include <android/system/suspend/ISuspendControlService.h>
-#include <android/system/suspend/internal/ISuspendControlServiceInternal.h>
-#include <nativehelper/JNIHelp.h>
-#include "jni.h"
-
-#include <nativehelper/ScopedUtfChars.h>
-#include <powermanager/PowerHalController.h>
-
-#include <limits.h>
-
#include <android-base/chrono_utils.h>
#include <android/binder_manager.h>
+#include <android/system/suspend/ISuspendControlService.h>
+#include <android/system/suspend/internal/ISuspendControlServiceInternal.h>
#include <android_runtime/AndroidRuntime.h>
#include <android_runtime/Log.h>
#include <binder/IServiceManager.h>
#include <gui/SurfaceComposerClient.h>
-#include <hardware/power.h>
#include <hardware_legacy/power.h>
#include <hidl/ServiceManagement.h>
+#include <limits.h>
+#include <nativehelper/JNIHelp.h>
+#include <nativehelper/ScopedUtfChars.h>
+#include <powermanager/PowerHalController.h>
#include <utils/Log.h>
#include <utils/String8.h>
#include <utils/Timers.h>
#include <utils/misc.h>
-#include "com_android_server_power_PowerManagerService.h"
+#include "jni.h"
+using aidl::android::hardware::power::Boost;
+using aidl::android::hardware::power::Mode;
using aidl::android::system::suspend::ISystemSuspend;
using aidl::android::system::suspend::IWakeLock;
using aidl::android::system::suspend::WakeLockType;
using android::String8;
-using android::hardware::power::Boost;
-using android::hardware::power::Mode;
using android::system::suspend::ISuspendControlService;
-using IPowerV1_1 = android::hardware::power::V1_1::IPower;
-using IPowerV1_0 = android::hardware::power::V1_0::IPower;
-using IPowerAidl = android::hardware::power::IPower;
namespace android {
diff --git a/services/core/jni/com_android_server_power_PowerManagerService.h b/services/core/jni/com_android_server_power_PowerManagerService.h
index a2f335c74870..36aaceb029c7 100644
--- a/services/core/jni/com_android_server_power_PowerManagerService.h
+++ b/services/core/jni/com_android_server_power_PowerManagerService.h
@@ -18,9 +18,10 @@
#define _ANDROID_SERVER_POWER_MANAGER_SERVICE_H
#include <nativehelper/JNIHelp.h>
-#include "jni.h"
-
#include <powermanager/PowerManager.h>
+#include <utils/Timers.h>
+
+#include "jni.h"
namespace android {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 5ba22830eec9..c918fb87154f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -16,6 +16,7 @@
package com.android.server.devicepolicy;
+import static android.app.admin.DevicePolicyIdentifiers.USER_CONTROL_DISABLED_PACKAGES_POLICY;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_TARGET_USER_ID;
import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_UPDATE_RESULT_KEY;
import static android.app.admin.PolicyUpdateResult.RESULT_FAILURE_CONFLICTING_ADMIN_POLICY;
@@ -176,6 +177,16 @@ final class DevicePolicyEngine {
}
boolean policyEnforced = Objects.equals(
localPolicyState.getCurrentResolvedPolicy(), value);
+ // TODO(b/285532044): remove hack and handle properly
+ if (!policyEnforced
+ && policyDefinition.getPolicyKey().getIdentifier().equals(
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
+ PolicyValue<Set<String>> parsedResolvedValue =
+ (PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy();
+ policyEnforced = (parsedResolvedValue != null && parsedValue != null
+ && parsedResolvedValue.getValue().containsAll(parsedValue.getValue()));
+ }
sendPolicyResultToAdmin(
enforcingAdmin,
policyDefinition,
@@ -418,6 +429,17 @@ final class DevicePolicyEngine {
boolean policyAppliedGlobally = Objects.equals(
globalPolicyState.getCurrentResolvedPolicy(), value);
+ // TODO(b/285532044): remove hack and handle properly
+ if (!policyAppliedGlobally
+ && policyDefinition.getPolicyKey().getIdentifier().equals(
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
+ PolicyValue<Set<String>> parsedResolvedValue =
+ (PolicyValue<Set<String>>) globalPolicyState.getCurrentResolvedPolicy();
+ policyAppliedGlobally = (parsedResolvedValue != null && parsedValue != null
+ && parsedResolvedValue.getValue().containsAll(parsedValue.getValue()));
+ }
+
boolean policyApplied = policyAppliedGlobally && policyAppliedOnAllUsers;
sendPolicyResultToAdmin(
@@ -539,8 +561,20 @@ final class DevicePolicyEngine {
userId);
}
- isAdminPolicyApplied &= Objects.equals(
- value, localPolicyState.getCurrentResolvedPolicy());
+ // TODO(b/285532044): remove hack and handle properly
+ if (policyDefinition.getPolicyKey().getIdentifier().equals(
+ USER_CONTROL_DISABLED_PACKAGES_POLICY)) {
+ if (!Objects.equals(value, localPolicyState.getCurrentResolvedPolicy())) {
+ PolicyValue<Set<String>> parsedValue = (PolicyValue<Set<String>>) value;
+ PolicyValue<Set<String>> parsedResolvedValue =
+ (PolicyValue<Set<String>>) localPolicyState.getCurrentResolvedPolicy();
+ isAdminPolicyApplied &= (parsedResolvedValue != null && parsedValue != null
+ && parsedResolvedValue.getValue().containsAll(parsedValue.getValue()));
+ }
+ } else {
+ isAdminPolicyApplied &= Objects.equals(
+ value, localPolicyState.getCurrentResolvedPolicy());
+ }
}
return isAdminPolicyApplied;
}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index acc984cb06dc..3453e2b1ef5f 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -49,6 +49,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_LOCK_TASK;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MICROPHONE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MOBILE_NETWORK;
+import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MODIFY_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_MTE;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY;
@@ -73,7 +74,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_SYSTEM_UPDATES;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_TIME;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER;
-import static android.Manifest.permission.MANAGE_DEVICE_POLICY_USERS;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_VPN;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER;
import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI;
@@ -13725,7 +13725,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ADD_CLONE_PROFILE, new String[]{MANAGE_DEVICE_POLICY_PROFILES});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_ADD_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_ADD_WIFI_CONFIG, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
@@ -13815,13 +13815,13 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_PRINTING, new String[]{MANAGE_DEVICE_POLICY_PRINTING});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_REMOVE_USER, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_RUN_IN_BACKGROUND, new String[]{MANAGE_DEVICE_POLICY_RUN_IN_BACKGROUND});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SAFE_BOOT, new String[]{MANAGE_DEVICE_POLICY_SAFE_BOOT});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_SET_USER_ICON, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_SET_WALLPAPER, new String[]{MANAGE_DEVICE_POLICY_WALLPAPER});
USER_RESTRICTION_PERMISSIONS.put(
@@ -13847,7 +13847,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_USB_FILE_TRANSFER, new String[]{MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER});
USER_RESTRICTION_PERMISSIONS.put(
- UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_USERS});
+ UserManager.DISALLOW_USER_SWITCH, new String[]{MANAGE_DEVICE_POLICY_MODIFY_USERS});
USER_RESTRICTION_PERMISSIONS.put(
UserManager.DISALLOW_WIFI_DIRECT, new String[]{MANAGE_DEVICE_POLICY_WIFI});
USER_RESTRICTION_PERMISSIONS.put(
@@ -23036,6 +23036,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_MICROPHONE,
MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_MODIFY_USERS,
MANAGE_DEVICE_POLICY_MTE,
MANAGE_DEVICE_POLICY_NEARBY_COMMUNICATION,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -23059,7 +23060,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_TIME,
MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_VPN,
MANAGE_DEVICE_POLICY_WALLPAPER,
MANAGE_DEVICE_POLICY_WIFI,
@@ -23080,12 +23080,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_KEYGUARD,
MANAGE_DEVICE_POLICY_LOCK_CREDENTIALS,
MANAGE_DEVICE_POLICY_LOCK_TASK,
+ MANAGE_DEVICE_POLICY_MODIFY_USERS,
MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
MANAGE_DEVICE_POLICY_RUNTIME_PERMISSIONS,
MANAGE_DEVICE_POLICY_SAFE_BOOT,
MANAGE_DEVICE_POLICY_SUPPORT_MESSAGE,
MANAGE_DEVICE_POLICY_TIME,
- MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_WIPE_DATA
);
@@ -23171,6 +23171,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_FUN,
MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_MOBILE_NETWORK,
+ MANAGE_DEVICE_POLICY_MODIFY_USERS,
MANAGE_DEVICE_POLICY_PHYSICAL_MEDIA,
MANAGE_DEVICE_POLICY_PRINTING,
MANAGE_DEVICE_POLICY_PROFILES,
@@ -23179,7 +23180,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_SMS,
MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
MANAGE_DEVICE_POLICY_USB_FILE_TRANSFER,
- MANAGE_DEVICE_POLICY_USERS,
MANAGE_DEVICE_POLICY_WINDOWS,
SET_TIME,
SET_TIME_ZONE
@@ -23369,6 +23369,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_LOCK_TASK,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
+ CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MODIFY_USERS,
+ MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_MICROPHONE_TOGGLE,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_ORGANIZATION_IDENTITY,
@@ -23389,8 +23391,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub {
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_SYSTEM_DIALOGS,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
- CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_USERS,
- MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_VPN,
MANAGE_DEVICE_POLICY_ACROSS_USERS_FULL);
CROSS_USER_PERMISSIONS.put(MANAGE_DEVICE_POLICY_WALLPAPER,
diff --git a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
index cbf0d12cbb56..44a29e546914 100644
--- a/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessPolicy.kt
@@ -372,6 +372,8 @@ class AccessPolicy private constructor(
forEachTag {
when (tagName) {
TAG_PACKAGE_VERSIONS -> parsePackageVersions(state, userId)
+ TAG_DEFAULT_PERMISSION_GRANT ->
+ parseDefaultPermissionGrant(state, userId)
else -> {
forEachSchemePolicy {
with(it) { parseUserState(state, userId) }
@@ -416,12 +418,24 @@ class AccessPolicy private constructor(
packageVersions[packageName] = version
}
+ private fun BinaryXmlPullParser.parseDefaultPermissionGrant(
+ state: MutableAccessState,
+ userId: Int
+ ) {
+ val userState = state.mutateUserState(userId, WriteMode.NONE)!!
+ val fingerprint = getAttributeValueOrThrow(ATTR_FINGERPRINT).intern()
+ userState.setDefaultPermissionGrantFingerprint(fingerprint)
+ }
+
fun BinaryXmlSerializer.serializeUserState(state: AccessState, userId: Int) {
tag(TAG_ACCESS) {
+ serializePackageVersions(state.userStates[userId]!!.packageVersions)
+ serializeDefaultPermissionGrantFingerprint(
+ state.userStates[userId]!!.defaultPermissionGrantFingerprint
+ )
forEachSchemePolicy {
with(it) { serializeUserState(state, userId) }
}
- serializePackageVersions(state.userStates[userId]!!.packageVersions)
}
}
@@ -438,6 +452,16 @@ class AccessPolicy private constructor(
}
}
+ private fun BinaryXmlSerializer.serializeDefaultPermissionGrantFingerprint(
+ fingerprint: String?
+ ) {
+ if (fingerprint != null) {
+ tag(TAG_DEFAULT_PERMISSION_GRANT) {
+ attributeInterned(ATTR_FINGERPRINT, fingerprint)
+ }
+ }
+ }
+
private fun getSchemePolicy(subject: AccessUri, `object`: AccessUri): SchemePolicy =
getSchemePolicy(subject.scheme, `object`.scheme)
@@ -455,9 +479,11 @@ class AccessPolicy private constructor(
internal const val VERSION_LATEST = 14
private const val TAG_ACCESS = "access"
+ private const val TAG_DEFAULT_PERMISSION_GRANT = "default-permission-grant"
private const val TAG_PACKAGE_VERSIONS = "package-versions"
private const val TAG_PACKAGE = "package"
+ private const val ATTR_FINGERPRINT = "fingerprint"
private const val ATTR_NAME = "name"
private const val ATTR_VERSION = "version"
}
diff --git a/services/permission/java/com/android/server/permission/access/AccessState.kt b/services/permission/java/com/android/server/permission/access/AccessState.kt
index 77c31944651e..4ec32ea53f28 100644
--- a/services/permission/java/com/android/server/permission/access/AccessState.kt
+++ b/services/permission/java/com/android/server/permission/access/AccessState.kt
@@ -348,6 +348,7 @@ sealed class UserState(
internal val appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
internal val appIdAppOpModesReference: AppIdAppOpModesReference,
internal val packageAppOpModesReference: PackageAppOpModesReference,
+ defaultPermissionGrantFingerprint: String?,
writeMode: Int
) : WritableState, Immutable<MutableUserState> {
val packageVersions: IndexedMap<String, Int>
@@ -362,6 +363,9 @@ sealed class UserState(
val packageAppOpModes: PackageAppOpModes
get() = packageAppOpModesReference.get()
+ var defaultPermissionGrantFingerprint: String? = defaultPermissionGrantFingerprint
+ protected set
+
override var writeMode: Int = writeMode
protected set
@@ -373,12 +377,14 @@ class MutableUserState private constructor(
appIdPermissionFlagsReference: AppIdPermissionFlagsReference,
appIdAppOpModesReference: AppIdAppOpModesReference,
packageAppOpModesReference: PackageAppOpModesReference,
+ defaultPermissionGrantFingerprint: String?,
writeMode: Int
) : UserState(
packageVersionsReference,
appIdPermissionFlagsReference,
appIdAppOpModesReference,
packageAppOpModesReference,
+ defaultPermissionGrantFingerprint,
writeMode
), MutableWritableState {
constructor() : this(
@@ -386,6 +392,7 @@ class MutableUserState private constructor(
AppIdPermissionFlagsReference(MutableAppIdPermissionFlags()),
AppIdAppOpModesReference(MutableAppIdAppOpModes()),
PackageAppOpModesReference(MutablePackageAppOpModes()),
+ null,
WriteMode.NONE
)
@@ -394,6 +401,7 @@ class MutableUserState private constructor(
userState.appIdPermissionFlagsReference.toImmutable(),
userState.appIdAppOpModesReference.toImmutable(),
userState.packageAppOpModesReference.toImmutable(),
+ userState.defaultPermissionGrantFingerprint,
WriteMode.NONE
)
@@ -406,6 +414,11 @@ class MutableUserState private constructor(
fun mutatePackageAppOpModes(): MutablePackageAppOpModes = packageAppOpModesReference.mutate()
+ @JvmName("setDefaultPermissionGrantFingerprintPublic")
+ fun setDefaultPermissionGrantFingerprint(defaultPermissionGrantFingerprint: String?) {
+ this.defaultPermissionGrantFingerprint = defaultPermissionGrantFingerprint
+ }
+
override fun requestWriteMode(writeMode: Int) {
this.writeMode = maxOf(this.writeMode, writeMode)
}
diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
index fa2bccf03379..82fe0a43af20 100644
--- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt
@@ -1992,6 +1992,15 @@ class PermissionService(
override fun writeLegacyPermissionStateTEMP() {}
+ override fun getDefaultPermissionGrantFingerprint(userId: Int): String? =
+ service.getState { state.userStates[userId]!!.defaultPermissionGrantFingerprint }
+
+ override fun setDefaultPermissionGrantFingerprint(fingerprint: String, userId: Int) {
+ service.mutateState {
+ newState.mutateUserState(userId)!!.setDefaultPermissionGrantFingerprint(fingerprint)
+ }
+ }
+
override fun onSystemReady() {
service.onSystemReady()
permissionControllerManager = PermissionControllerManager(
diff --git a/services/tests/PackageManagerServiceTests/server/Android.bp b/services/tests/PackageManagerServiceTests/server/Android.bp
index 92e4560bf582..a1d846e0f426 100644
--- a/services/tests/PackageManagerServiceTests/server/Android.bp
+++ b/services/tests/PackageManagerServiceTests/server/Android.bp
@@ -105,6 +105,10 @@ android_test {
":PackageParserTestApp6",
],
resource_zips: [":PackageManagerServiceServerTests_apks_as_resources"],
+
+ data: [
+ ":StubTestApp",
+ ],
}
// Rules to copy all the test apks to the intermediate raw resource directory
diff --git a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
index 1b93527065ec..a0ef03c69e3a 100644
--- a/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
+++ b/services/tests/PackageManagerServiceTests/server/AndroidTest.xml
@@ -23,6 +23,14 @@
<option name="install-arg" value="-g" />
<option name="test-file-name" value="PackageManagerServiceServerTests.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put global verifier_engprod 1" />
+ </target_preparer>
+
+ <!-- Load additional APKs onto device -->
+ <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+ <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/>
+ </target_preparer>
<option name="test-tag" value="PackageManagerServiceServerTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
index b82ffb4d0b39..435f0d7f5f15 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerServiceTest.java
@@ -32,7 +32,6 @@ import android.app.AppGlobals;
import android.content.IIntentReceiver;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal;
import android.os.Bundle;
import android.os.UserHandle;
import android.os.UserManager;
@@ -70,7 +69,7 @@ import java.util.regex.Pattern;
@RunWith(AndroidJUnit4.class)
public class PackageManagerServiceTest {
- private static final String PACKAGE_NAME = "com.android.frameworks.servicestests";
+ private static final String PACKAGE_NAME = "com.android.server.pm.test.service.server";
private static final String TEST_DATA_PATH = "/data/local/tmp/servicestests/";
private static final String TEST_APP_APK = "StubTestApp.apk";
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index dc92376263a6..ca4a4048cc00 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -66,6 +66,7 @@ import android.system.StructStat;
import android.test.AndroidTestCase;
import android.util.Log;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.test.filters.SmallTest;
import androidx.test.filters.Suppress;
@@ -2506,6 +2507,7 @@ public class PackageManagerTests extends AndroidTestCase {
}
@LargeTest
+ @FlakyTest(bugId = 283797480)
public void testCheckSignaturesRotatedAgainstRotated() throws Exception {
// checkSignatures should be successful when both apps have been signed with the same
// rotated key since the initial signature comparison between the two apps should
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 4989f841a275..03231ecfb723 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -1904,6 +1904,34 @@ public class BroadcastQueueTest {
}
@Test
+ public void testReplacePending_diffReceivers() throws Exception {
+ final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+ final ProcessRecord receiverGreenApp = makeActiveProcessRecord(PACKAGE_GREEN);
+ final ProcessRecord receiverBlueApp = makeActiveProcessRecord(PACKAGE_BLUE);
+ final ProcessRecord receiverYellowApp = makeActiveProcessRecord(PACKAGE_YELLOW);
+ final BroadcastFilter receiverGreen = makeRegisteredReceiver(receiverGreenApp);
+ final BroadcastFilter receiverBlue = makeRegisteredReceiver(receiverBlueApp);
+ final BroadcastFilter receiverYellow = makeRegisteredReceiver(receiverYellowApp);
+
+ final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+ .addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
+
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5),
+ withPriority(receiverYellow, 0))));
+ enqueueBroadcast(makeBroadcastRecord(airplane, callerApp, List.of(
+ withPriority(receiverGreen, 10),
+ withPriority(receiverBlue, 5))));
+
+ waitForIdle();
+
+ verifyScheduleRegisteredReceiver(times(1), receiverGreenApp, airplane);
+ verifyScheduleRegisteredReceiver(times(1), receiverBlueApp, airplane);
+ verifyScheduleRegisteredReceiver(never(), receiverYellowApp, airplane);
+ }
+
+ @Test
public void testIdleAndBarrier() throws Exception {
final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
final ProcessRecord receiverApp = makeActiveProcessRecord(PACKAGE_GREEN);
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
index 13e16d6614fd..22ad7c46193b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java
@@ -85,22 +85,17 @@ public final class CachedAppOptimizerTest {
@Mock
private PackageManagerInternal mPackageManagerInt;
- private final TestableDeviceConfig mDeviceConfig = new TestableDeviceConfig();
-
@Rule
public final ApplicationExitInfoTest.ServiceThreadRule
mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
@Rule
public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this)
- .configureSessionBuilder(
- sessionBuilder -> mDeviceConfig.setUpMockedClasses(sessionBuilder))
- .build();
+ .addStaticMockFixtures(TestableDeviceConfig::new).build();
@Before
public void setUp() {
System.loadLibrary("mockingservicestestjni");
- mDeviceConfig.setUpMockBehaviors();
mHandlerThread = new HandlerThread("");
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
@@ -131,7 +126,6 @@ public final class CachedAppOptimizerTest {
mHandlerThread.quit();
mThread.quit();
mCountDown = null;
- mDeviceConfig.tearDown();
}
private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, String processName,
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index b7dbaf93b9e2..f89f73c98cfd 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -37,6 +37,7 @@ import static org.mockito.Mockito.when;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -1009,6 +1010,72 @@ public class LocalDisplayAdapterTest {
0.001f);
}
+ @Test
+ public void test_getDisplayDeviceInfoLocked_internalDisplay_usesCutoutAndCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = true;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNotNull();
+ assertThat(info.displayCutout.getBoundingRectTop()).isEqualTo(new Rect(507, 33, 573, 99));
+ assertThat(info.roundedCorners).isNotNull();
+ assertThat(info.roundedCorners.getRoundedCorner(0).getRadius()).isEqualTo(5);
+ }
+
+ @Test public void test_getDisplayDeviceInfoLocked_externalDisplay_doesNotUseCutoutOrCorners()
+ throws Exception {
+ setupCutoutAndRoundedCorners();
+ FakeDisplay display = new FakeDisplay(PORT_A);
+ display.info.isInternal = false;
+ setUpDisplay(display);
+ updateAvailableDisplays();
+ mAdapter.registerLocked();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+ DisplayDevice displayDevice = mListener.addedDisplays.get(0);
+
+ // Turn on / initialize
+ Runnable changeStateRunnable = displayDevice.requestDisplayStateLocked(Display.STATE_ON, 0,
+ 0);
+ changeStateRunnable.run();
+ waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+ mListener.changedDisplays.clear();
+
+ DisplayDeviceInfo info = displayDevice.getDisplayDeviceInfoLocked();
+
+ assertThat(info.displayCutout).isNull();
+ assertThat(info.roundedCorners).isNull();
+ }
+
+ private void setupCutoutAndRoundedCorners() {
+ String sampleCutout = "M 507,66\n"
+ + "a 33,33 0 1 0 66,0 33,33 0 1 0 -66,0\n"
+ + "Z\n"
+ + "@left\n";
+ // Setup some default cutout
+ when(mMockedResources.getString(
+ com.android.internal.R.string.config_mainBuiltInDisplayCutout))
+ .thenReturn(sampleCutout);
+ when(mMockedResources.getDimensionPixelSize(
+ com.android.internal.R.dimen.rounded_corner_radius)).thenReturn(5);
+ }
+
private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
float expectedXdpi,
float expectedYDpi,
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index cfef0b23b3e7..5fd270ecb2b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -48,6 +48,7 @@ open class PackageHelperTestBase {
const val UNINSTALLER_PACKAGE = "com.android.test.known.uninstaller"
const val VERIFIER_PACKAGE = "com.android.test.known.verifier"
const val PERMISSION_CONTROLLER_PACKAGE = "com.android.test.known.permission"
+ const val MGMT_ROLE_HOLDER_PACKAGE = "com.android.test.know.device_management"
const val TEST_USER_ID = 0
}
@@ -119,6 +120,8 @@ open class PackageHelperTestBase {
Mockito.doReturn(arrayOf(PERMISSION_CONTROLLER_PACKAGE)).`when`(pms)
.getKnownPackageNamesInternal(any(),
eq(KnownPackages.PACKAGE_PERMISSION_CONTROLLER), eq(TEST_USER_ID))
+ Mockito.doReturn(MGMT_ROLE_HOLDER_PACKAGE).`when`(pms)
+ .getDevicePolicyManagementRoleHolderPackageName(eq(TEST_USER_ID))
}
private fun createPackageManagerService(vararg stageExistingPackages: String):
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index f9a8ead9cd4a..5cca5fa8ea0b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -128,13 +128,14 @@ class SuspendPackageHelperTest : PackageHelperTestBase() {
fun setPackagesSuspended_forQuietMode() {
val knownPackages = arrayOf(DEVICE_ADMIN_PACKAGE, DEFAULT_HOME_PACKAGE, DIALER_PACKAGE,
INSTALLER_PACKAGE, UNINSTALLER_PACKAGE, VERIFIER_PACKAGE,
- PERMISSION_CONTROLLER_PACKAGE)
+ PERMISSION_CONTROLLER_PACKAGE, MGMT_ROLE_HOLDER_PACKAGE)
val failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
knownPackages, true /* suspended */, null /* appExtras */,
null /* launcherExtras */, null /* dialogInfo */, DEVICE_OWNER_PACKAGE,
TEST_USER_ID, deviceOwnerUid, true /* forQuietMode */)!!
- assertThat(failedNames.size).isEqualTo(0)
+ assertThat(failedNames.size).isEqualTo(1)
+ assertThat(failedNames[0]).isEqualTo(MGMT_ROLE_HOLDER_PACKAGE)
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
index 43f77bfb4c95..e8e1dacd6fcf 100644
--- a/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/power/ScreenUndimDetectorTest.java
@@ -109,8 +109,8 @@ public class ScreenUndimDetectorTest {
public void recordScreenPolicy_disabledByFlag_noop() {
DeviceConfig.setProperty(NAMESPACE_ATTENTION_MANAGER_SERVICE,
KEY_KEEP_SCREEN_ON_ENABLED, Boolean.FALSE.toString(), false /*makeDefault*/);
+ mScreenUndimDetector.readValuesFromDeviceConfig();
- setup();
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_DIM);
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, POLICY_BRIGHT);
@@ -120,7 +120,7 @@ public class ScreenUndimDetectorTest {
@Test
public void recordScreenPolicy_samePolicy_noop() {
for (int policy : ALL_POLICIES) {
- setup();
+ resetDetector();
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, policy);
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, policy);
@@ -154,7 +154,7 @@ public class ScreenUndimDetectorTest {
if (from == POLICY_DIM && to == POLICY_BRIGHT) {
continue;
}
- setup();
+ resetDetector();
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, from);
mScreenUndimDetector.recordScreenPolicy(DEFAULT_DISPLAY_GROUP, to);
@@ -295,7 +295,8 @@ public class ScreenUndimDetectorTest {
@Test
public void recordScreenPolicy_dimToNonBright_resets() {
for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
- setup();
+ resetDetector();
+
mScreenUndimDetector.mUndimCounter = 1;
mScreenUndimDetector.mUndimCounterStartedMillis = 123;
mScreenUndimDetector.mWakeLock.acquire();
@@ -313,7 +314,8 @@ public class ScreenUndimDetectorTest {
@Test
public void recordScreenPolicy_brightToNonDim_resets() {
for (int to : Arrays.asList(POLICY_OFF, POLICY_DOZE)) {
- setup();
+ resetDetector();
+
mScreenUndimDetector.mUndimCounter = 1;
mScreenUndimDetector.mUndimCounterStartedMillis = 123;
mScreenUndimDetector.mWakeLock.acquire();
@@ -356,4 +358,9 @@ public class ScreenUndimDetectorTest {
}
}
}
+
+ private void resetDetector() {
+ mScreenUndimDetector.reset();
+ mScreenUndimDetector.mCurrentScreenPolicy = 0;
+ }
}
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 19af8dc23329..4edb167a54c7 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -116,7 +116,6 @@ android_test {
":SimpleServiceTestApp1",
":SimpleServiceTestApp2",
":SimpleServiceTestApp3",
- ":StubTestApp",
":SuspendTestApp",
":MediaButtonReceiverHolderTestHelperApp",
"data/broken_shortcut.xml",
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index b304968f3e69..fbb0ca108ecd 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -44,11 +44,6 @@
<option name="teardown-command" value="rm -rf /data/local/tmp/servicestests"/>
</target_preparer>
- <!-- Load additional APKs onto device -->
- <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
- <option name="push" value="StubTestApp.apk->/data/local/tmp/servicestests/StubTestApp.apk"/>
- </target_preparer>
-
<option name="test-tag" value="FrameworksServicesTests" />
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.frameworks.servicestests" />
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index c872a11e7988..5f19887f15b6 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -18,17 +18,31 @@ package com.android.server.contentcapture;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import android.annotation.NonNull;
+import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
import android.content.pm.UserInfo;
+import android.service.contentcapture.ContentCaptureServiceInfo;
+import android.view.contentcapture.ContentCaptureEvent;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.server.LocalServices;
+import com.android.server.contentprotection.ContentProtectionBlocklistManager;
+import com.android.server.contentprotection.RemoteContentProtectionService;
import com.android.server.pm.UserManagerInternal;
import com.google.common.collect.ImmutableList;
@@ -56,12 +70,39 @@ public class ContentCaptureManagerServiceTest {
private static final String PACKAGE_NAME = "com.test.package";
+ private static final ComponentName COMPONENT_NAME =
+ new ComponentName(PACKAGE_NAME, "TestClass");
+
+ private static final ContentCaptureEvent EVENT =
+ new ContentCaptureEvent(/* sessionId= */ 100, /* type= */ 200);
+
+ private static final ParceledListSlice<ContentCaptureEvent> PARCELED_EVENTS =
+ new ParceledListSlice<>(ImmutableList.of(EVENT));
+
private static final Context sContext = ApplicationProvider.getApplicationContext();
@Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
@Mock private UserManagerInternal mMockUserManagerInternal;
+ @Mock private ContentProtectionBlocklistManager mMockContentProtectionBlocklistManager;
+
+ @Mock private ContentCaptureServiceInfo mMockContentCaptureServiceInfo;
+
+ @Mock private RemoteContentProtectionService mMockRemoteContentProtectionService;
+
+ private boolean mDevCfgEnableContentProtectionReceiver;
+
+ private int mContentProtectionBlocklistManagersCreated;
+
+ private int mContentProtectionServiceInfosCreated;
+
+ private int mRemoteContentProtectionServicesCreated;
+
+ private String mConfigDefaultContentProtectionService = COMPONENT_NAME.flattenToString();
+
+ private boolean mContentProtectionServiceInfoConstructorShouldThrow;
+
private ContentCaptureManagerService mContentCaptureManagerService;
@Before
@@ -69,20 +110,107 @@ public class ContentCaptureManagerServiceTest {
when(mMockUserManagerInternal.getUserInfos()).thenReturn(new UserInfo[0]);
LocalServices.removeServiceForTest(UserManagerInternal.class);
LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
- mContentCaptureManagerService = new ContentCaptureManagerService(sContext);
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ }
+
+ @Test
+ public void constructor_contentProtection_flagDisabled_noBlocklistManager() {
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_contentProtection_componentNameNull_noBlocklistManager() {
+ mConfigDefaultContentProtectionService = null;
+
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_contentProtection_componentNameBlank_noBlocklistManager() {
+ mConfigDefaultContentProtectionService = " ";
+
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void constructor_contentProtection_serviceInfoThrows_noBlocklistManager() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentProtectionServiceInfoConstructorShouldThrow = true;
+
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(0);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+ verifyZeroInteractions(mMockContentProtectionBlocklistManager);
}
@Test
- public void getOptions_notAllowlisted() {
+ public void constructor_contentProtection_enabled_createsBlocklistManager() {
+ mDevCfgEnableContentProtectionReceiver = true;
+
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ assertThat(mContentProtectionBlocklistManagersCreated).isEqualTo(1);
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+ verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+ }
+
+ @Test
+ public void setFineTuneParamsFromDeviceConfig_doesNotUpdateContentProtectionBlocklist() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgContentProtectionAppsBlocklistSize += 100;
+ verify(mMockContentProtectionBlocklistManager).updateBlocklist(anyInt());
+
+ mContentCaptureManagerService.setFineTuneParamsFromDeviceConfig();
+
+ verifyNoMoreInteractions(mMockContentProtectionBlocklistManager);
+ }
+
+ @Test
+ public void getOptions_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
ContentCaptureOptions actual =
mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
USER_ID, PACKAGE_NAME);
assertThat(actual).isNull();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
}
@Test
- public void getOptions_allowlisted_contentCaptureReceiver() {
+ public void getOptions_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ ContentCaptureOptions actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.getOptions(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isNotNull();
+ assertThat(actual.enableReceiver).isFalse();
+ assertThat(actual.contentProtectionOptions).isNotNull();
+ assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
+ assertThat(actual.whitelistedComponents).isNull();
+ }
+
+ @Test
+ public void getOptions_contentCaptureEnabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
@@ -92,13 +220,17 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isNotNull();
assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions).isNotNull();
assertThat(actual.contentProtectionOptions.enableReceiver).isFalse();
assertThat(actual.whitelistedComponents).isNull();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
}
@Test
- public void getOptions_allowlisted_bothReceivers() {
- mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = true;
+ public void getOptions_contentCaptureEnabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
@@ -108,7 +240,185 @@ public class ContentCaptureManagerServiceTest {
assertThat(actual).isNotNull();
assertThat(actual.enableReceiver).isTrue();
+ assertThat(actual.contentProtectionOptions).isNotNull();
assertThat(actual.contentProtectionOptions.enableReceiver).isTrue();
assertThat(actual.whitelistedComponents).isNull();
}
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isWhitelisted_packageName_contentCaptureEnabled_contentProtectionNotChecked() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, ImmutableList.of(PACKAGE_NAME), /* components= */ null);
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isTrue();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionDisabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager).isAllowed(PACKAGE_NAME);
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureDisabled_contentProtectionEnabled() {
+ when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isTrue();
+ }
+
+ @Test
+ public void isWhitelisted_componentName_contentCaptureEnabled_contentProtectionNotChecked() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.setWhitelist(
+ USER_ID, /* packageNames= */ null, ImmutableList.of(COMPONENT_NAME));
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, COMPONENT_NAME);
+
+ assertThat(actual).isTrue();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_withoutBlocklistManager() {
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+ boolean actual =
+ mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+ USER_ID, PACKAGE_NAME);
+
+ assertThat(actual).isFalse();
+ verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+ }
+
+ @Test
+ public void onLoginDetected_disabledAfterConstructor() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+ mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
+
+ mContentCaptureManagerService
+ .getContentCaptureManagerServiceStub()
+ .onLoginDetected(PARCELED_EVENTS);
+
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+ assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(0);
+ verifyZeroInteractions(mMockRemoteContentProtectionService);
+ }
+
+ @Test
+ public void onLoginDetected_enabled() {
+ mDevCfgEnableContentProtectionReceiver = true;
+ mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+ mContentCaptureManagerService
+ .getContentCaptureManagerServiceStub()
+ .onLoginDetected(PARCELED_EVENTS);
+
+ assertThat(mContentProtectionServiceInfosCreated).isEqualTo(1);
+ assertThat(mRemoteContentProtectionServicesCreated).isEqualTo(1);
+ verify(mMockRemoteContentProtectionService).onLoginDetected(PARCELED_EVENTS);
+ }
+
+ private class TestContentCaptureManagerService extends ContentCaptureManagerService {
+
+ TestContentCaptureManagerService() {
+ super(sContext);
+ this.mDevCfgEnableContentProtectionReceiver =
+ ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ }
+
+ @Override
+ protected boolean getEnableContentProtectionReceiverLocked() {
+ return ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+ }
+
+ @Override
+ protected ContentProtectionBlocklistManager createContentProtectionBlocklistManager() {
+ mContentProtectionBlocklistManagersCreated++;
+ return mMockContentProtectionBlocklistManager;
+ }
+
+ @Override
+ protected String getContentProtectionServiceFlatComponentName() {
+ return mConfigDefaultContentProtectionService;
+ }
+
+ @Override
+ protected ContentCaptureServiceInfo createContentProtectionServiceInfo(
+ @NonNull ComponentName componentName) throws PackageManager.NameNotFoundException {
+ mContentProtectionServiceInfosCreated++;
+ if (mContentProtectionServiceInfoConstructorShouldThrow) {
+ throw new RuntimeException("TEST RUNTIME EXCEPTION");
+ }
+ return mMockContentCaptureServiceInfo;
+ }
+
+ @Override
+ protected RemoteContentProtectionService createRemoteContentProtectionService(
+ @NonNull ComponentName componentName) {
+ mRemoteContentProtectionServicesCreated++;
+ return mMockRemoteContentProtectionService;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java b/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java
index b46f1a61c745..50ea48719948 100644
--- a/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/credentials/MetricUtilitiesTest.java
@@ -15,14 +15,27 @@
*/
package com.android.server.credentials;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.server.credentials.metrics.BrowsedAuthenticationMetric;
+import com.android.server.credentials.metrics.CandidateAggregateMetric;
+import com.android.server.credentials.metrics.CandidateBrowsingPhaseMetric;
+import com.android.server.credentials.metrics.ChosenProviderFinalPhaseMetric;
import com.android.server.credentials.metrics.InitialPhaseMetric;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.security.cert.CertificateException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* Given the secondary-system nature of the MetricUtilities, we expect absolutely nothing to
* throw an error. If one presents itself, that is problematic.
@@ -33,6 +46,11 @@ import org.junit.runner.RunWith;
@SmallTest
public final class MetricUtilitiesTest {
+ @Before
+ public void setUp() throws CertificateException {
+ final Context context = ApplicationProvider.getApplicationContext();
+ }
+
@Test
public void logApiCalledInitialPhase_nullInitPhaseMetricAndNegativeSequence_success() {
MetricUtilities.logApiCalledInitialPhase(null, -1);
@@ -49,4 +67,102 @@ public final class MetricUtilitiesTest {
MetricUtilities.getHighlyUniqueInteger());
MetricUtilities.logApiCalledInitialPhase(validInitPhaseMetric, 1);
}
+
+ @Test
+ public void logApiCalledTotalCandidate_nullCandidateNegativeSequence_success() {
+ MetricUtilities.logApiCalledAggregateCandidate(null, -1);
+ }
+
+ @Test
+ public void logApiCalledTotalCandidate_invalidCandidatePhasePositiveSequence_success() {
+ MetricUtilities.logApiCalledAggregateCandidate(new CandidateAggregateMetric(-1), 1);
+ }
+
+ @Test
+ public void logApiCalledTotalCandidate_validPhaseMetric_success() {
+ MetricUtilities.logApiCalledAggregateCandidate(
+ new CandidateAggregateMetric(MetricUtilities.getHighlyUniqueInteger()), 1);
+ }
+
+ @Test
+ public void logApiCalledNoUidFinal_nullNoUidFinalNegativeSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledNoUidFinal(null, null,
+ -1, -1);
+ }
+
+ @Test
+ public void logApiCalledNoUidFinal_invalidNoUidFinalPhasePositiveSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledNoUidFinal(new ChosenProviderFinalPhaseMetric(-1, -1),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
+
+ @Test
+ public void logApiCalledNoUidFinal_validNoUidFinalMetric_success() {
+ MetricUtilities.logApiCalledNoUidFinal(
+ new ChosenProviderFinalPhaseMetric(MetricUtilities.getHighlyUniqueInteger(),
+ MetricUtilities.getHighlyUniqueInteger()),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
+
+ @Test
+ public void logApiCalledCandidate_nullMapNullInitFinalNegativeSequence_success() {
+ MetricUtilities.logApiCalledCandidatePhase(null, -1,
+ null);
+ }
+
+ @Test
+ public void logApiCalledCandidate_invalidProvidersCandidatePositiveSequence_success() {
+ Map<String, ProviderSession> testMap = new HashMap<>();
+ testMap.put("s", null);
+ MetricUtilities.logApiCalledCandidatePhase(testMap, 1,
+ null);
+ }
+
+ @Test
+ public void logApiCalledCandidateGet_nullMapFinalNegativeSequence_success() {
+ MetricUtilities.logApiCalledCandidateGetMetric(null, -1);
+ }
+
+ @Test
+ public void logApiCalledCandidateGet_invalidProvidersCandidatePositiveSequence_success() {
+ Map<String, ProviderSession> testMap = new HashMap<>();
+ testMap.put("s", null);
+ MetricUtilities.logApiCalledCandidateGetMetric(testMap, 1);
+ }
+
+ @Test
+ public void logApiCalledAuthMetric_nullAuthMetricNegativeSequence_success() {
+ MetricUtilities.logApiCalledAuthenticationMetric(null, -1);
+ }
+
+ @Test
+ public void logApiCalledAuthMetric_invalidAuthMetricPositiveSequence_success() {
+ MetricUtilities.logApiCalledAuthenticationMetric(new BrowsedAuthenticationMetric(-1), 1);
+ }
+
+ @Test
+ public void logApiCalledAuthMetric_nullAuthMetricPositiveSequence_success() {
+ MetricUtilities.logApiCalledAuthenticationMetric(
+ new BrowsedAuthenticationMetric(MetricUtilities.getHighlyUniqueInteger()), -1);
+ }
+
+ @Test
+ public void logApiCalledFinal_nullFinalNegativeSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledFinalPhase(null, null,
+ -1, -1);
+ }
+
+ @Test
+ public void logApiCalledFinal_invalidFinalPhasePositiveSequenceAndStatus_success() {
+ MetricUtilities.logApiCalledFinalPhase(new ChosenProviderFinalPhaseMetric(-1, -1),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
+
+ @Test
+ public void logApiCalledFinal_validFinalMetric_success() {
+ MetricUtilities.logApiCalledFinalPhase(
+ new ChosenProviderFinalPhaseMetric(MetricUtilities.getHighlyUniqueInteger(),
+ MetricUtilities.getHighlyUniqueInteger()),
+ List.of(new CandidateBrowsingPhaseMetric()), 1, 1);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
new file mode 100644
index 000000000000..920c376d5dfb
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTvTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.hdmi;
+
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.hardware.hdmi.HdmiDeviceInfo;
+import android.os.Looper;
+import android.os.test.TestLooper;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+/**
+ * TV specific tests for {@link HdmiControlService} class.
+ */
+@SmallTest
+@Presubmit
+@RunWith(JUnit4.class)
+public class HdmiControlServiceTvTest {
+
+ private static final String TAG = "HdmiControlServiceTvTest";
+ private HdmiControlService mHdmiControlService;
+ private HdmiCecController mHdmiCecController;
+ private FakeNativeWrapper mNativeWrapper;
+ private HdmiEarcController mHdmiEarcController;
+ private FakeEarcNativeWrapper mEarcNativeWrapper;
+ private Looper mMyLooper;
+ private TestLooper mTestLooper = new TestLooper();
+
+ @Before
+ public void setUp() throws Exception {
+ Context context = InstrumentationRegistry.getTargetContext();
+ mMyLooper = mTestLooper.getLooper();
+
+ FakeAudioFramework audioFramework = new FakeAudioFramework();
+
+ mHdmiControlService =
+ new HdmiControlService(InstrumentationRegistry.getTargetContext(),
+ Collections.singletonList(HdmiDeviceInfo.DEVICE_TV),
+ audioFramework.getAudioManager(),
+ audioFramework.getAudioDeviceVolumeManager()) {
+ @Override
+ int pathToPortId(int path) {
+ return Constants.INVALID_PORT_ID + 1;
+ }
+ };
+
+ mMyLooper = mTestLooper.getLooper();
+ mHdmiControlService.setIoLooper(mMyLooper);
+ mHdmiControlService.setHdmiCecConfig(new FakeHdmiCecConfig(context));
+ mHdmiControlService.setDeviceConfig(new FakeDeviceConfigWrapper());
+ mHdmiControlService.onBootPhase(PHASE_SYSTEM_SERVICES_READY);
+
+ mNativeWrapper = new FakeNativeWrapper();
+ mHdmiCecController = HdmiCecController.createWithNativeWrapper(
+ mHdmiControlService, mNativeWrapper, mHdmiControlService.getAtomWriter());
+ mHdmiControlService.setCecController(mHdmiCecController);
+ mEarcNativeWrapper = new FakeEarcNativeWrapper();
+ mHdmiEarcController = HdmiEarcController.createWithNativeWrapper(
+ mHdmiControlService, mEarcNativeWrapper);
+ mHdmiControlService.setEarcController(mHdmiEarcController);
+ mHdmiControlService.setHdmiMhlController(HdmiMhlControllerStub.create(
+ mHdmiControlService));
+ mHdmiControlService.initService();
+
+ mTestLooper.dispatchAll();
+ }
+
+ @Test
+ public void onCecMessage_shortPhysicalAddress_featureAbortInvalidOperand() {
+ // Invalid <Inactive Source> message.
+ HdmiCecMessage message = HdmiUtils.buildMessage("40:9D:14");
+
+ mNativeWrapper.onCecMessage(message);
+ mTestLooper.dispatchAll();
+
+ HdmiCecMessage featureAbort = HdmiCecMessageBuilder.buildFeatureAbortCommand(
+ Constants.ADDR_TV, Constants.ADDR_PLAYBACK_1, Constants.MESSAGE_INACTIVE_SOURCE,
+ Constants.ABORT_INVALID_OPERAND);
+ assertThat(mNativeWrapper.getResultMessages()).contains(featureAbort);
+ }
+
+ @Test
+ public void handleCecCommand_shortPhysicalAddress_returnsAbortInvalidOperand() {
+ // Invalid <Active Source> message.
+ HdmiCecMessage message = HdmiUtils.buildMessage("4F:82:10");
+
+ // In case of a broadcasted message <Feature Abort> is not expected.
+ // See CEC 1.4b specification, 12.2 Protocol General Rules for detail.
+ assertThat(mHdmiControlService.handleCecCommand(message))
+ .isEqualTo(Constants.ABORT_INVALID_OPERAND);
+ }
+
+ @Test
+ public void test_verifyPhysicalAddresses() {
+ // <Routing Change>
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10:00:40:00"))).isTrue();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10:00:40"))).isFalse();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("0F:80:10"))).isFalse();
+
+ // <System Audio Mode Request>
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("40:70:00:00"))).isTrue();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("40:70:00"))).isFalse();
+
+ // <Active Source>
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10:00"))).isTrue();
+ assertThat(mHdmiControlService
+ .verifyPhysicalAddresses(HdmiUtils.buildMessage("4F:82:10"))).isFalse();
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
new file mode 100644
index 000000000000..98b46286e977
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/input/AmbientKeyboardBacklightControllerTests.kt
@@ -0,0 +1,355 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.content.res.Resources
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import android.hardware.display.DisplayManagerInternal
+import android.hardware.input.InputSensorInfo
+import android.os.Handler
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.util.TypedValue
+import android.view.Display
+import android.view.DisplayInfo
+import androidx.test.core.app.ApplicationProvider
+import com.android.internal.R
+import com.android.server.LocalServices
+import com.android.server.input.AmbientKeyboardBacklightController.HYSTERESIS_THRESHOLD
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertThrows
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link AmbientKeyboardBacklightController}.
+ *
+ * Build/Install/Run:
+ * atest FrameworksServicesTests:AmbientKeyboardBacklightControllerTests
+ */
+@Presubmit
+class AmbientKeyboardBacklightControllerTests {
+
+ companion object {
+ const val DEFAULT_DISPLAY_UNIQUE_ID = "uniqueId_1"
+ const val SENSOR_NAME = "test_sensor_name"
+ const val SENSOR_TYPE = "test_sensor_type"
+ }
+
+ @get:Rule
+ val rule = MockitoJUnit.rule()!!
+
+ private lateinit var context: Context
+ private lateinit var testLooper: TestLooper
+ private lateinit var ambientController: AmbientKeyboardBacklightController
+
+ @Mock
+ private lateinit var resources: Resources
+
+ @Mock
+ private lateinit var lightSensorInfo: InputSensorInfo
+
+ @Mock
+ private lateinit var sensorManager: SensorManager
+
+ @Mock
+ private lateinit var displayManagerInternal: DisplayManagerInternal
+ private lateinit var lightSensor: Sensor
+
+ private var currentDisplayInfo = DisplayInfo()
+ private var lastBrightnessCallback: Int = 0
+ private var listenerRegistered: Boolean = false
+ private var listenerRegistrationCount: Int = 0
+
+ @Before
+ fun setup() {
+ context = spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+ `when`(context.resources).thenReturn(resources)
+ setupBrightnessSteps()
+ setupSensor()
+ testLooper = TestLooper()
+ ambientController = AmbientKeyboardBacklightController(context, testLooper.looper)
+ ambientController.systemRunning()
+ testLooper.dispatchAll()
+ }
+
+ private fun setupBrightnessSteps() {
+ val brightnessValues = intArrayOf(100, 200, 0)
+ val decreaseThresholds = intArrayOf(-1, 900, 1900)
+ val increaseThresholds = intArrayOf(1000, 2000, -1)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightBrightnessValues))
+ .thenReturn(brightnessValues)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightDecreaseLuxThreshold))
+ .thenReturn(decreaseThresholds)
+ `when`(resources.getIntArray(R.array.config_autoKeyboardBacklightIncreaseLuxThreshold))
+ .thenReturn(increaseThresholds)
+ `when`(
+ resources.getValue(
+ eq(R.dimen.config_autoKeyboardBrightnessSmoothingConstant),
+ any(TypedValue::class.java),
+ anyBoolean()
+ )
+ ).then {
+ val args = it.arguments
+ val outValue = args[1] as TypedValue
+ outValue.data = java.lang.Float.floatToRawIntBits(1.0f)
+ Unit
+ }
+ }
+
+ private fun setupSensor() {
+ LocalServices.removeServiceForTest(DisplayManagerInternal::class.java)
+ LocalServices.addService(DisplayManagerInternal::class.java, displayManagerInternal)
+ currentDisplayInfo.uniqueId = DEFAULT_DISPLAY_UNIQUE_ID
+ `when`(displayManagerInternal.getDisplayInfo(Display.DEFAULT_DISPLAY)).thenReturn(
+ currentDisplayInfo
+ )
+ val sensorData = DisplayManagerInternal.AmbientLightSensorData(SENSOR_NAME, SENSOR_TYPE)
+ `when`(displayManagerInternal.getAmbientLightSensorData(Display.DEFAULT_DISPLAY))
+ .thenReturn(sensorData)
+
+ `when`(lightSensorInfo.name).thenReturn(SENSOR_NAME)
+ `when`(lightSensorInfo.stringType).thenReturn(SENSOR_TYPE)
+ lightSensor = Sensor(lightSensorInfo)
+ `when`(context.getSystemService(eq(Context.SENSOR_SERVICE))).thenReturn(sensorManager)
+ `when`(sensorManager.getSensorList(anyInt())).thenReturn(listOf(lightSensor))
+ `when`(
+ sensorManager.registerListener(
+ any(),
+ eq(lightSensor),
+ anyInt(),
+ any(Handler::class.java)
+ )
+ ).then {
+ listenerRegistered = true
+ listenerRegistrationCount++
+ true
+ }
+ `when`(
+ sensorManager.unregisterListener(
+ any(SensorEventListener::class.java),
+ eq(lightSensor)
+ )
+ ).then {
+ listenerRegistered = false
+ Unit
+ }
+ }
+
+ private fun setupSensorWithInitialLux(luxValue: Float) {
+ ambientController.registerAmbientBacklightListener { brightnessValue: Int ->
+ lastBrightnessCallback = brightnessValue
+ }
+ sendAmbientLuxValue(luxValue)
+ testLooper.dispatchAll()
+ }
+
+ @Test
+ fun testInitialAmbientLux_sendsCallbackImmediately() {
+ setupSensorWithInitialLux(500F)
+
+ assertEquals(
+ "Should receive immediate callback for first lux change",
+ 100,
+ lastBrightnessCallback
+ )
+ }
+
+ @Test
+ fun testBrightnessIncrease_afterInitialLuxChanges() {
+ setupSensorWithInitialLux(500F)
+
+ // Current state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+ repeat(HYSTERESIS_THRESHOLD) {
+ sendAmbientLuxValue(1500F)
+ }
+ testLooper.dispatchAll()
+
+ assertEquals(
+ "Should receive brightness change callback for increasing lux change",
+ 200,
+ lastBrightnessCallback
+ )
+ }
+
+ @Test
+ fun testBrightnessDecrease_afterInitialLuxChanges() {
+ setupSensorWithInitialLux(1500F)
+
+ // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+ repeat(HYSTERESIS_THRESHOLD) {
+ sendAmbientLuxValue(500F)
+ }
+ testLooper.dispatchAll()
+
+ assertEquals(
+ "Should receive brightness change callback for decreasing lux change",
+ 100,
+ lastBrightnessCallback
+ )
+ }
+
+ @Test
+ fun testRegisterAmbientListener_throwsExceptionOnRegisteringDuplicate() {
+ val ambientListener =
+ AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+ ambientController.registerAmbientBacklightListener(ambientListener)
+
+ assertThrows(IllegalStateException::class.java) {
+ ambientController.registerAmbientBacklightListener(
+ ambientListener
+ )
+ }
+ }
+
+ @Test
+ fun testUnregisterAmbientListener_throwsExceptionOnUnregisteringNonExistent() {
+ val ambientListener =
+ AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+ assertThrows(IllegalStateException::class.java) {
+ ambientController.unregisterAmbientBacklightListener(
+ ambientListener
+ )
+ }
+ }
+
+ @Test
+ fun testSensorListenerRegistered_onRegisterUnregisterAmbientListener() {
+ assertEquals(
+ "Should not have a sensor listener registered at init",
+ 0,
+ listenerRegistrationCount
+ )
+ assertFalse("Should not have a sensor listener registered at init", listenerRegistered)
+
+ val ambientListener1 =
+ AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+ ambientController.registerAmbientBacklightListener(ambientListener1)
+ assertEquals(
+ "Should register a new sensor listener", 1, listenerRegistrationCount
+ )
+ assertTrue("Should have sensor listener registered", listenerRegistered)
+
+ val ambientListener2 =
+ AmbientKeyboardBacklightController.AmbientKeyboardBacklightListener { }
+ ambientController.registerAmbientBacklightListener(ambientListener2)
+ assertEquals(
+ "Should not register a new sensor listener when adding a second ambient listener",
+ 1,
+ listenerRegistrationCount
+ )
+ assertTrue("Should have sensor listener registered", listenerRegistered)
+
+ ambientController.unregisterAmbientBacklightListener(ambientListener1)
+ assertTrue("Should have sensor listener registered", listenerRegistered)
+
+ ambientController.unregisterAmbientBacklightListener(ambientListener2)
+ assertFalse(
+ "Should not have sensor listener registered if there are no ambient listeners",
+ listenerRegistered
+ )
+ }
+
+ @Test
+ fun testDisplayChange_shouldNotReRegisterListener_ifUniqueIdSame() {
+ setupSensorWithInitialLux(0F)
+
+ val count = listenerRegistrationCount
+ ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY)
+ testLooper.dispatchAll()
+
+ assertEquals(
+ "Should not re-register listener on display change if unique is same",
+ count,
+ listenerRegistrationCount
+ )
+ }
+
+ @Test
+ fun testDisplayChange_shouldReRegisterListener_ifUniqueIdChanges() {
+ setupSensorWithInitialLux(0F)
+
+ val count = listenerRegistrationCount
+ currentDisplayInfo.uniqueId = "xyz"
+ ambientController.onDisplayChanged(Display.DEFAULT_DISPLAY)
+ testLooper.dispatchAll()
+
+ assertEquals(
+ "Should re-register listener on display change if unique id changed",
+ count + 1,
+ listenerRegistrationCount
+ )
+ }
+
+ @Test
+ fun testBrightnessDoesntChange_betweenIncreaseAndDecreaseThresholds() {
+ setupSensorWithInitialLux(1001F)
+
+ // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+ // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+ lastBrightnessCallback = -1
+ repeat(HYSTERESIS_THRESHOLD) {
+ sendAmbientLuxValue(999F)
+ }
+ testLooper.dispatchAll()
+
+ assertEquals(
+ "Should not receive any callback for brightness change",
+ -1,
+ lastBrightnessCallback
+ )
+ }
+
+ @Test
+ fun testBrightnessDoesntChange_onChangeOccurringLessThanHysteresisThreshold() {
+ setupSensorWithInitialLux(1001F)
+
+ // Previous state: Step 1 [value = 100, increaseThreshold = 1000, decreaseThreshold = -1]
+ // Current state: Step 2 [value = 200, increaseThreshold = 2000, decreaseThreshold = 900]
+ lastBrightnessCallback = -1
+ repeat(HYSTERESIS_THRESHOLD - 1) {
+ sendAmbientLuxValue(2001F)
+ }
+ testLooper.dispatchAll()
+
+ assertEquals(
+ "Should not receive any callback for brightness change",
+ -1,
+ lastBrightnessCallback
+ )
+ }
+
+ private fun sendAmbientLuxValue(luxValue: Float) {
+ ambientController.onSensorChanged(SensorEvent(lightSensor, 0, 0, floatArrayOf(luxValue)))
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
index ef15ccba4c74..67158f24839d 100644
--- a/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/KeyboardBacklightControllerTests.kt
@@ -37,6 +37,8 @@ import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANG
import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS
import org.junit.After
import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
@@ -154,7 +156,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightIncrementDecrement() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -168,7 +174,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardWithoutBacklight() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithoutBacklight = createKeyboard(DEVICE_ID)
val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight)
@@ -182,7 +192,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardWithMultipleLight() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT)
@@ -204,7 +218,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testRestoreBacklightOnInputDeviceAdded() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -233,7 +251,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testRestoreBacklightOnInputDeviceChanged() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -265,7 +287,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklight_registerUnregisterListener() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1
@@ -314,7 +340,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklight_userActivity() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -346,7 +376,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklight_displayOnOff() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -436,7 +470,11 @@ class KeyboardBacklightControllerTests {
@Test
@UiThreadTest
fun testKeyboardBacklightAnimation_onChangeLevels() {
- KeyboardBacklightFlags(animationEnabled = true, customLevelsEnabled = false).use {
+ KeyboardBacklightFlags(
+ animationEnabled = true,
+ customLevelsEnabled = false,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
@@ -459,7 +497,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = true,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
@@ -475,7 +517,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels_moreThanMax_shouldUseDefault() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = true,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) }
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
@@ -491,7 +537,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels_mustHaveZeroAndMaxBrightnessAsBounds() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = true,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val suggestedLevels = intArrayOf(22, 63, 135, 196)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
@@ -508,7 +558,11 @@ class KeyboardBacklightControllerTests {
@Test
fun testKeyboardBacklightPreferredLevels_dropsOutOfBoundsLevels() {
- KeyboardBacklightFlags(animationEnabled = false, customLevelsEnabled = true).use {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = true,
+ ambientControlEnabled = false
+ ).use {
val keyboardWithBacklight = createKeyboard(DEVICE_ID)
val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000)
val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT,
@@ -523,10 +577,148 @@ class KeyboardBacklightControllerTests {
}
}
+ @Test
+ fun testAmbientBacklightControl_doesntRestoreBacklightLevel() {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = true
+ ).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+ dataStore.setKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor,
+ LIGHT_ID,
+ DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1]
+ )
+
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ keyboardBacklightController.notifyUserActivity()
+ testLooper.dispatchNext()
+ assertNull(
+ "Keyboard backlight level should not be restored to the saved level",
+ lightColorMap[LIGHT_ID]
+ )
+ }
+ }
+
+ @Test
+ fun testAmbientBacklightControl_doesntBackupBacklightLevel() {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = true
+ ).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertFalse(
+ "Light value should not be backed up if ambient control is enabled",
+ dataStore.getKeyboardBacklightBrightness(
+ keyboardWithBacklight.descriptor, LIGHT_ID
+ ).isPresent
+ )
+ }
+ }
+
+ @Test
+ fun testAmbientBacklightControl_incrementLevel_afterAmbientChange() {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = true
+ ).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ sendAmbientBacklightValue(1)
+ assertEquals(
+ "Light value should be changed to ambient provided value",
+ Color.argb(1, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+
+ incrementKeyboardBacklight(DEVICE_ID)
+
+ assertEquals(
+ "Light value for level after increment post Ambient change is mismatched",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+ }
+ }
+
+ @Test
+ fun testAmbientBacklightControl_decrementLevel_afterAmbientChange() {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = true
+ ).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ sendAmbientBacklightValue(254)
+ assertEquals(
+ "Light value should be changed to ambient provided value",
+ Color.argb(254, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+
+ decrementKeyboardBacklight(DEVICE_ID)
+
+ val numLevels = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size
+ assertEquals(
+ "Light value for level after decrement post Ambient change is mismatched",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[numLevels - 2], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+ }
+ }
+
+ @Test
+ fun testAmbientBacklightControl_ambientChanges_afterManualChange() {
+ KeyboardBacklightFlags(
+ animationEnabled = false,
+ customLevelsEnabled = false,
+ ambientControlEnabled = true
+ ).use {
+ val keyboardWithBacklight = createKeyboard(DEVICE_ID)
+ val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT)
+ `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight)
+ `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight))
+ keyboardBacklightController.onInputDeviceAdded(DEVICE_ID)
+ incrementKeyboardBacklight(DEVICE_ID)
+ assertEquals(
+ "Light value should be changed to the first level",
+ Color.argb(DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL[1], 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+
+ sendAmbientBacklightValue(100)
+ assertNotEquals(
+ "Light value should not change based on ambient changes after manual changes",
+ Color.argb(100, 0, 0, 0),
+ lightColorMap[LIGHT_ID]
+ )
+ }
+ }
+
private fun assertIncrementDecrementForLevels(
- device: InputDevice,
- light: Light,
- expectedLevels: IntArray
+ device: InputDevice,
+ light: Light,
+ expectedLevels: IntArray
) {
val deviceId = device.id
val lightId = light.id
@@ -612,6 +804,12 @@ class KeyboardBacklightControllerTests {
testLooper.dispatchAll()
}
+ private fun sendAmbientBacklightValue(brightnessValue: Int) {
+ keyboardBacklightController.handleAmbientLightValueChanged(brightnessValue)
+ keyboardBacklightController.notifyUserActivity()
+ testLooper.dispatchAll()
+ }
+
class KeyboardBacklightState(
val deviceId: Int,
val brightnessLevel: Int,
@@ -620,12 +818,15 @@ class KeyboardBacklightControllerTests {
)
private inner class KeyboardBacklightFlags constructor(
- animationEnabled: Boolean,
- customLevelsEnabled: Boolean
+ animationEnabled: Boolean,
+ customLevelsEnabled: Boolean,
+ ambientControlEnabled: Boolean
) : AutoCloseable {
init {
InputFeatureFlagProvider.setKeyboardBacklightAnimationEnabled(animationEnabled)
InputFeatureFlagProvider.setKeyboardBacklightCustomLevelsEnabled(customLevelsEnabled)
+ InputFeatureFlagProvider
+ .setAmbientKeyboardBacklightControlEnabled(ambientControlEnabled)
}
override fun close() {
diff --git a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
index e6c527be9b8f..70cd0dcf97d3 100644
--- a/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/PowerManagerServiceTest.java
@@ -18,8 +18,6 @@ package com.android.server.power;
import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
import static android.app.ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
-import static android.app.AppOpsManager.MODE_ALLOWED;
-import static android.app.AppOpsManager.MODE_ERRORED;
import static android.os.PowerManager.USER_ACTIVITY_EVENT_BUTTON;
import static android.os.PowerManagerInternal.WAKEFULNESS_ASLEEP;
import static android.os.PowerManagerInternal.WAKEFULNESS_AWAKE;
@@ -44,6 +42,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
@@ -51,13 +50,14 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.ActivityManagerInternal;
-import android.app.AppOpsManager;
import android.attention.AttentionManagerInternal;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.AttributionSource;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.pm.PackageManager;
+import android.content.PermissionChecker;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -96,7 +96,6 @@ import com.android.server.lights.LightsManager;
import com.android.server.policy.WindowManagerPolicy;
import com.android.server.power.PowerManagerService.BatteryReceiver;
import com.android.server.power.PowerManagerService.BinderService;
-import com.android.server.power.PowerManagerService.Injector;
import com.android.server.power.PowerManagerService.NativeWrapper;
import com.android.server.power.PowerManagerService.UserSwitchedReceiver;
import com.android.server.power.PowerManagerService.WakeLock;
@@ -106,9 +105,14 @@ import com.android.server.power.batterysaver.BatterySaverStateMachine;
import com.android.server.power.batterysaver.BatterySavingStats;
import com.android.server.testutils.OffsettableClock;
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TestRule;
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
@@ -151,12 +155,13 @@ public class PowerManagerServiceTest {
@Mock private WirelessChargerDetector mWirelessChargerDetectorMock;
@Mock private AmbientDisplayConfiguration mAmbientDisplayConfigurationMock;
@Mock private SystemPropertiesWrapper mSystemPropertiesMock;
- @Mock private AppOpsManager mAppOpsManagerMock;
@Mock private LowPowerStandbyController mLowPowerStandbyControllerMock;
@Mock private Callable<Void> mInvalidateInteractiveCachesMock;
+ @Mock private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+ @Mock private PowerManagerService.PermissionCheckerWrapper mPermissionCheckerWrapperMock;
+ @Mock private PowerManagerService.PowerPropertiesWrapper mPowerPropertiesWrapper;
- @Mock
- private InattentiveSleepWarningController mInattentiveSleepWarningControllerMock;
+ @Rule public TestRule compatChangeRule = new PlatformCompatChangeRule();
private PowerManagerService mService;
private ContextWrapper mContextSpy;
@@ -232,7 +237,7 @@ public class PowerManagerServiceTest {
}
private PowerManagerService createService() {
- mService = new PowerManagerService(mContextSpy, new Injector() {
+ mService = new PowerManagerService(mContextSpy, new PowerManagerService.Injector() {
@Override
Notifier createNotifier(Looper looper, Context context, IBatteryStats batteryStats,
SuspendBlocker suspendBlocker, WindowManagerPolicy policy,
@@ -328,8 +333,13 @@ public class PowerManagerServiceTest {
}
@Override
- AppOpsManager createAppOpsManager(Context context) {
- return mAppOpsManagerMock;
+ PowerManagerService.PermissionCheckerWrapper createPermissionCheckerWrapper() {
+ return mPermissionCheckerWrapperMock;
+ }
+
+ @Override
+ PowerManagerService.PowerPropertiesWrapper createPowerPropertiesWrapper() {
+ return mPowerPropertiesWrapper;
}
});
return mService;
@@ -590,6 +600,7 @@ public class PowerManagerServiceTest {
}
@Test
+ @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnAllowed() {
createService();
startSystem();
@@ -598,11 +609,12 @@ public class PowerManagerServiceTest {
IBinder token = new Binder();
String tag = "acq_causes_wakeup";
String packageName = "pkg.name";
- when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
- Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
- when(mContextSpy.checkCallingOrSelfPermission(
- android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
- PackageManager.PERMISSION_GRANTED);
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+
+ doReturn(PermissionChecker.PERMISSION_GRANTED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
// First, ensure that a normal full wake lock does not cause a wakeup
int flags = PowerManager.FULL_WAKE_LOCK;
@@ -627,6 +639,35 @@ public class PowerManagerServiceTest {
}
@Test
+ @DisableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+ public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnAllowed() {
+ createService();
+ startSystem();
+ forceSleep();
+
+ IBinder token = new Binder();
+ String tag = "acq_causes_wakeup";
+ String packageName = "pkg.name";
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+
+ // verify that the wakeup is allowed for apps targeting older sdks, and therefore won't have
+ // the TURN_SCREEN_ON permission granted
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+
+ doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+
+ int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+ mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
+ null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
+ mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+ }
+
+ @Test
+ @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
public void testWakefulnessAwake_AcquireCausesWakeup_turnScreenOnDenied() {
createService();
startSystem();
@@ -635,30 +676,43 @@ public class PowerManagerServiceTest {
IBinder token = new Binder();
String tag = "acq_causes_wakeup";
String packageName = "pkg.name";
- when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
- Binder.getCallingUid(), packageName)).thenReturn(MODE_ERRORED);
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
+ doReturn(false).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
+ doReturn(false).when(mPowerPropertiesWrapper).permissionless_turn_screen_on();
- // Verify that flag has no effect when OP_TURN_SCREEN_ON is not allowed
+ // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
- if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_AWAKE);
- } else {
- assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
- }
+ assertThat(mService.getGlobalWakefulnessLocked()).isEqualTo(WAKEFULNESS_ASLEEP);
mService.getBinderServiceInstance().releaseWakeLock(token, 0 /* flags */);
+ }
+
+ @Test
+ @EnableCompatChanges({PowerManagerService.REQUIRE_TURN_SCREEN_ON_PERMISSION})
+ public void testWakefulnessAwake_AcquireCausesWakeupOldSdk_turnScreenOnDenied() {
+ createService();
+ startSystem();
+ forceSleep();
+
+ IBinder token = new Binder();
+ String tag = "acq_causes_wakeup";
+ String packageName = "pkg.name";
+ AttributionSource attrSrc = new AttributionSource(Binder.getCallingUid(),
+ packageName, /* attributionTag= */ null);
+ doReturn(PermissionChecker.PERMISSION_HARD_DENIED).when(
+ mPermissionCheckerWrapperMock).checkPermissionForDataDelivery(any(),
+ eq(android.Manifest.permission.TURN_SCREEN_ON), anyInt(), eq(attrSrc), anyString());
- when(mAppOpsManagerMock.checkOpNoThrow(AppOpsManager.OP_TURN_SCREEN_ON,
- Binder.getCallingUid(), packageName)).thenReturn(MODE_ALLOWED);
- when(mContextSpy.checkCallingOrSelfPermission(
- android.Manifest.permission.TURN_SCREEN_ON)).thenReturn(
- PackageManager.PERMISSION_DENIED);
+ doReturn(true).when(mPowerPropertiesWrapper).waive_target_sdk_check_for_turn_screen_on();
- // Verify that the flag has no effect when OP_TURN_SCREEN_ON is allowed but
- // android.permission.TURN_SCREEN_ON is denied
- flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
+ // Verify that flag has no effect when TURN_SCREEN_ON is not allowed for apps targeting U+
+ int flags = PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP;
mService.getBinderServiceInstance().acquireWakeLock(token, flags, tag, packageName,
null /* workSource */, null /* historyTag */, Display.INVALID_DISPLAY, null);
if (PowerProperties.permissionless_turn_screen_on().orElse(false)) {
diff --git a/services/tests/servicestests/src/com/android/server/webkit/OWNERS b/services/tests/servicestests/src/com/android/server/webkit/OWNERS
index 00e540a46ab2..e7fd7a5d1096 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/webkit/OWNERS
@@ -1,3 +1,3 @@
-changwan@google.com
-tobiasjs@google.com
+# Bug component: 76427
+ntfschr@google.com
torne@google.com
diff --git a/services/tests/uiservicestests/AndroidManifest.xml b/services/tests/uiservicestests/AndroidManifest.xml
index f44c1d18614d..4315254f68a9 100644
--- a/services/tests/uiservicestests/AndroidManifest.xml
+++ b/services/tests/uiservicestests/AndroidManifest.xml
@@ -20,6 +20,7 @@
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
<uses-permission android:name="android.permission.UPDATE_APP_OPS_STATS" />
+ <uses-permission android:name="android.permission.UPDATE_DEVICE_STATS" />
<uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
<uses-permission android:name="android.permission.ACCESS_NOTIFICATIONS" />
@@ -36,6 +37,7 @@
<uses-permission android:name="android.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG" />
<uses-permission android:name="android.permission.READ_WRITE_SYNC_DISABLED_MODE_CONFIG" />
<uses-permission android:name="android.permission.ACCESS_KEYGUARD_SECURE_STORAGE" />
+ <uses-permission android:name="android.permission.WAKE_LOCK" />
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 8f7f2f66308b..4debbb4d38c1 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -61,6 +61,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Build.VERSION_CODES.O_MR1;
import static android.os.Build.VERSION_CODES.P;
+import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.UserHandle.USER_SYSTEM;
import static android.os.UserManager.USER_TYPE_FULL_SECONDARY;
import static android.os.UserManager.USER_TYPE_PROFILE_CLONE;
@@ -80,6 +81,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.ALLOW_DISMISS_ONGOING;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.FSI_FORCE_DEMOTE;
import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.SHOW_STICKY_HUN_FOR_DENIED_FSI;
+import static com.android.internal.config.sysui.SystemUiSystemPropertiesFlags.NotificationFlags.WAKE_LOCK_FOR_POSTING_NOTIFICATION;
import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
import static com.android.server.notification.NotificationManagerService.DEFAULT_MAX_NOTIFICATION_ENQUEUE_RATE;
import static com.android.server.notification.NotificationRecordLogger.NotificationReportedEvent.NOTIFICATION_ADJUSTED;
@@ -120,12 +122,14 @@ import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import android.Manifest;
+import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -182,11 +186,14 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.Looper;
import android.os.Parcel;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
+import android.os.WorkSource;
import android.permission.PermissionManager;
import android.provider.DeviceConfig;
import android.provider.MediaStore;
@@ -352,6 +359,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private PermissionManager mPermissionManager;
@Mock
private DevicePolicyManagerInternal mDevicePolicyManager;
+ @Mock
+ private PowerManager mPowerManager;
+ private final ArrayList<WakeLock> mAcquiredWakeLocks = new ArrayList<>();
private final TestPostNotificationTrackerFactory mPostNotificationTrackerFactory =
new TestPostNotificationTrackerFactory();
@@ -432,8 +442,9 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
private final List<PostNotificationTracker> mCreatedTrackers = new ArrayList<>();
@Override
- public PostNotificationTracker newTracker() {
- PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker();
+ public PostNotificationTracker newTracker(@Nullable WakeLock optionalWakeLock) {
+ PostNotificationTracker tracker = PostNotificationTrackerFactory.super.newTracker(
+ optionalWakeLock);
mCreatedTrackers.add(tracker);
return tracker;
}
@@ -564,6 +575,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mAssistants.isAdjustmentAllowed(anyString())).thenReturn(true);
+ // Use the real PowerManager to back up the mock w.r.t. creating WakeLocks.
+ // This is because 1) we need a mock to verify() calls and tracking the created WakeLocks,
+ // but 2) PowerManager and WakeLock perform their own checks (e.g. correct arguments, don't
+ // call release twice, etc) and we want the test to fail if such misuse happens, too.
+ PowerManager realPowerManager = mContext.getSystemService(PowerManager.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).then(
+ (Answer<WakeLock>) invocation -> {
+ WakeLock wl = realPowerManager.newWakeLock(invocation.getArgument(0),
+ invocation.getArgument(1));
+ mAcquiredWakeLocks.add(wl);
+ return wl;
+ });
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, true);
+
// apps allowed as convos
mService.setStringArrayResourceValue(PKG_O);
@@ -580,7 +605,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mock(TelephonyManager.class),
mAmi, mToastRateLimiter, mPermissionHelper, mock(UsageStatsManagerInternal.class),
mTelecomManager, mLogger, mTestFlagResolver, mPermissionManager,
- mPostNotificationTrackerFactory);
+ mPowerManager, mPostNotificationTrackerFactory);
// Return first true for RoleObserver main-thread check
when(mMainLooper.isCurrentThread()).thenReturn(true).thenReturn(false);
mService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY, mMainLooper);
@@ -688,6 +713,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@After
+ public void assertAllWakeLocksReleased() {
+ for (WakeLock wakeLock : mAcquiredWakeLocks) {
+ assertThat(wakeLock.isHeld()).isFalse();
+ }
+ }
+
+ @After
public void tearDown() throws Exception {
if (mFile != null) mFile.delete();
clearDeviceConfig();
@@ -1488,7 +1520,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1509,7 +1541,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -1805,6 +1837,112 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
+ public void enqueueNotification_acquiresAndReleasesWakeLock() throws Exception {
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_throws_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to rejected inputs.
+ assertThrows(Exception.class,
+ () -> mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_throws_acquiresAndReleasesWakeLock", 0,
+ /* notification= */ null, 0));
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate not enqueued due to snoozing inputs.
+ when(mSnoozeHelper.getSnoozeContextForUnpostedNotification(anyInt(), any(), any()))
+ .thenReturn("zzzzzzz");
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notEnqueued_acquiresAndReleasesWakeLock", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_notPosted_acquiresAndReleasesWakeLock() throws Exception {
+ // Simulate enqueued but not posted due to missing small icon.
+ Notification notif = new Notification.Builder(mContext, mTestNotificationChannel.getId())
+ .setContentTitle("foo")
+ .build();
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_notPosted_acquiresAndReleasesWakeLock", 0,
+ notif, 0);
+
+ verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isTrue();
+
+ waitForIdle();
+
+ // NLSes were not called.
+ verify(mListeners, never()).prepareNotifyPostedLocked(any(), any(), anyBoolean());
+
+ assertThat(mAcquiredWakeLocks).hasSize(1);
+ assertThat(mAcquiredWakeLocks.get(0).isHeld()).isFalse();
+ }
+
+ @Test
+ public void enqueueNotification_setsWakeLockWorkSource() throws Exception {
+ // Use a "full" mock for the PowerManager (instead of the one that delegates to the real
+ // service) so we can return a mocked WakeLock that we can verify() on.
+ reset(mPowerManager);
+ WakeLock wakeLock = mock(WakeLock.class);
+ when(mPowerManager.newWakeLock(anyInt(), anyString())).thenReturn(wakeLock);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ InOrder inOrder = inOrder(mPowerManager, wakeLock);
+ inOrder.verify(mPowerManager).newWakeLock(eq(PARTIAL_WAKE_LOCK), anyString());
+ inOrder.verify(wakeLock).setWorkSource(eq(new WorkSource(mUid, PKG)));
+ inOrder.verify(wakeLock).acquire(anyLong());
+ inOrder.verify(wakeLock).release();
+ inOrder.verifyNoMoreInteractions();
+ }
+
+ @Test
+ public void enqueueNotification_wakeLockFlagOff_noWakeLock() throws Exception {
+ mTestFlagResolver.setFlagOverride(WAKE_LOCK_FOR_POSTING_NOTIFICATION, false);
+
+ mBinderService.enqueueNotificationWithTag(PKG, PKG,
+ "enqueueNotification_setsWakeLockWorkSource", 0,
+ generateNotificationRecord(null).getNotification(), 0);
+ waitForIdle();
+
+ verifyZeroInteractions(mPowerManager);
+ }
+
+ @Test
public void testCancelNonexistentNotification() throws Exception {
mBinderService.cancelNotificationWithTag(PKG, PKG,
"testCancelNonexistentNotification", 0, 0);
@@ -4363,7 +4501,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4382,7 +4520,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4402,7 +4540,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(), update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4422,7 +4560,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
- update.getUid(), mPostNotificationTrackerFactory.newTracker());
+ update.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -4436,13 +4574,13 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
r = generateNotificationRecord(mTestNotificationChannel, 1, null, false);
r.setCriticality(CriticalNotificationExtractor.CRITICAL);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
mService.addEnqueuedNotification(r);
runnable.run();
@@ -5092,7 +5230,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.new PostNotificationRunnable(original.getKey(),
original.getSbn().getPackageName(),
original.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -5116,7 +5254,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.new PostNotificationRunnable(update.getKey(),
update.getSbn().getPackageName(),
update.getUid(),
- mPostNotificationTrackerFactory.newTracker());
+ mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -7538,7 +7676,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(update.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10236,7 +10374,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
NotificationManagerService.PostNotificationRunnable runnable =
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10253,7 +10391,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10270,7 +10408,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
runnable = mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(),
- r.getUid(), mPostNotificationTrackerFactory.newTracker());
+ r.getUid(), mPostNotificationTrackerFactory.newTracker(null));
runnable.run();
waitForIdle();
@@ -10363,7 +10501,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
// normal blocked notifications - blocked
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10381,7 +10519,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10394,7 +10532,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10408,7 +10546,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats, never()).registerBlocked(any());
@@ -10422,7 +10560,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10437,7 +10575,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
@@ -10450,7 +10588,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addEnqueuedNotification(r);
mService.new PostNotificationRunnable(r.getKey(), r.getSbn().getPackageName(), r.getUid(),
- mPostNotificationTrackerFactory.newTracker()).run();
+ mPostNotificationTrackerFactory.newTracker(null)).run();
waitForIdle();
verify(mUsageStats).registerBlocked(any());
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
index 66c1e35754c5..81c573d8fb1e 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/RoleObserverTest.java
@@ -48,6 +48,7 @@ import android.content.Context;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.os.Looper;
+import android.os.PowerManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.permission.PermissionManager;
@@ -169,6 +170,7 @@ public class RoleObserverTest extends UiServiceTestCase {
mock(UsageStatsManagerInternal.class), mock(TelecomManager.class),
mock(NotificationChannelLogger.class), new TestableFlagResolver(),
mock(PermissionManager.class),
+ mock(PowerManager.class),
new NotificationManagerService.PostNotificationTrackerFactory() {});
} catch (SecurityException e) {
if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
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 77e944f35cb2..41fcd6935567 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -1996,7 +1996,8 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(activity.isSnapshotCompatible(snapshot));
- setRotatedScreenOrientationSilently(activity);
+ doReturn(task.getWindowConfiguration().getRotation() + 1).when(mDisplayContent)
+ .rotationForActivityInDifferentOrientation(activity);
assertFalse(activity.isSnapshotCompatible(snapshot));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
index 4890f3e6cbf1..bcb0c6b5c269 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -250,9 +250,22 @@ public class ActivityStartInterceptorTest {
}
@Test
- public void testInterceptQuietProfile() {
- // GIVEN that the user the activity is starting as is currently in quiet mode
+ public void testInterceptQuietProfile_keepProfilesRunningEnabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode and
+ // profiles are kept running when in quiet mode.
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
+
+ // THEN calling intercept returns false because package also has to be suspended.
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+ }
+
+ @Test
+ public void testInterceptQuietProfile_keepProfilesRunningDisabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode and
+ // profiles are stopped when in quiet mode (pre-U behavior, no profile app suspension).
+ when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
// THEN calling intercept returns true
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
@@ -263,10 +276,28 @@ public class ActivityStartInterceptorTest {
}
@Test
- public void testInterceptQuietProfileWhenPackageSuspended() {
+ public void testInterceptQuietProfileWhenPackageSuspended_keepProfilesRunningEnabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode,
+ // the package is suspended and profiles are kept running while in quiet mode.
+ suspendPackage("com.test.suspending.package");
+ when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(true);
+
+ // THEN calling intercept returns true
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
+
+ // THEN the returned intent is the quiet mode intent
+ assertTrue(UnlaunchableAppActivity.createInQuietModeDialogIntent(TEST_USER_ID)
+ .filterEquals(mInterceptor.mIntent));
+ }
+
+ @Test
+ public void testInterceptQuietProfileWhenPackageSuspended_keepProfilesRunningDisabled() {
+ // GIVEN that the user the activity is starting as is currently in quiet mode,
+ // the package is suspended and profiles are stopped while in quiet mode.
suspendPackage("com.test.suspending.package");
- // GIVEN that the user the activity is starting as is currently in quiet mode
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
+ when(mDevicePolicyManager.isKeepProfilesRunningEnabled()).thenReturn(false);
// THEN calling intercept returns true
assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
index 04e1d9c07a07..2a8f0ffc4d49 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationTests.java
@@ -59,10 +59,10 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.hardware.devicestate.DeviceStateManager;
+import android.os.Handler;
import android.os.IBinder;
import android.os.PowerManagerInternal;
import android.os.SystemClock;
-import android.os.Handler;
import android.platform.test.annotations.Presubmit;
import android.provider.Settings;
import android.view.DisplayAddress;
@@ -518,7 +518,8 @@ public class DisplayRotationTests {
mBuilder.build();
configureDisplayRotation(SCREEN_ORIENTATION_PORTRAIT, false, false);
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
thawRotation();
@@ -544,7 +545,8 @@ public class DisplayRotationTests {
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_yes() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(true);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(true);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_270, mTarget.getUserRotation());
@@ -553,7 +555,8 @@ public class DisplayRotationTests {
@Test
public void testFreezeRotation_reverseRotationDirectionAroundZAxis_no() throws Exception {
mBuilder.build();
- when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis()).thenReturn(false);
+ when(mDeviceStateController.shouldReverseRotationDirectionAroundZAxis(mMockDisplayContent))
+ .thenReturn(false);
freezeRotation(Surface.ROTATION_90);
assertEquals(Surface.ROTATION_90, mTarget.getUserRotation());
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d91be16f2538..27e6e31ec152 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -407,7 +407,6 @@ public class SizeCompatTests extends WindowTestsBase {
clearInvocations(translucentActivity.mLetterboxUiController);
// We destroy the first opaque activity
- mActivity.setState(DESTROYED, "testing");
mActivity.removeImmediately();
// Check that updateInheritedLetterbox() is invoked again
@@ -4655,14 +4654,6 @@ public class SizeCompatTests extends WindowTestsBase {
return c;
}
- private static void resizeDisplay(DisplayContent displayContent, int width, int height) {
- displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
- displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
- final Configuration c = new Configuration();
- displayContent.computeScreenConfiguration(c);
- displayContent.onRequestedOverrideConfigurationChanged(c);
- }
-
private static void setNeverConstrainDisplayApisFlag(@Nullable String value,
boolean makeDefault) {
DeviceConfig.setProperty(NAMESPACE_CONSTRAIN_DISPLAY_APIS,
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index 01ddcca99300..f3d8114b9e94 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -127,6 +127,10 @@ public class WallpaperControllerTests extends WindowTestsBase {
public void testWallpaperSizeWithFixedTransform() {
// No wallpaper
final DisplayContent dc = mDisplayContent;
+ if (dc.mBaseDisplayHeight == dc.mBaseDisplayWidth) {
+ // Make sure the size is different when changing orientation.
+ resizeDisplay(dc, 500, 1000);
+ }
// No wallpaper WSA Surface
final WindowState wallpaperWindow = createWallpaperWindow(dc);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index 68640fc0eae8..5b7b1b297a1d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -53,6 +53,7 @@ import static com.google.common.truth.Truth.assertThat;
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;
@@ -100,6 +101,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+import com.android.internal.os.IResultReceiver;
import org.junit.Rule;
import org.junit.Test;
@@ -913,6 +915,56 @@ public class WindowManagerServiceTests extends WindowTestsBase {
argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY));
}
+ @Test
+ public void testRequestKeyboardShortcuts_noWindow() {
+ doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
+ doReturn(null).when(mWm).getFocusedWindowLocked();
+ doReturn(null).when(mWm.mRoot).getCurrentInputMethodWindow();
+
+ TestResultReceiver receiver = new TestResultReceiver();
+ mWm.requestAppKeyboardShortcuts(receiver, 0);
+ assertNotNull(receiver.resultData);
+ assertTrue(receiver.resultData.isEmpty());
+
+ receiver = new TestResultReceiver();
+ mWm.requestImeKeyboardShortcuts(receiver, 0);
+ assertNotNull(receiver.resultData);
+ assertTrue(receiver.resultData.isEmpty());
+ }
+
+ @Test
+ public void testRequestKeyboardShortcuts() throws RemoteException {
+ final IWindow window = mock(IWindow.class);
+ final IBinder binder = mock(IBinder.class);
+ doReturn(binder).when(window).asBinder();
+ final WindowState windowState =
+ createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "appWin", window);
+ doNothing().when(mWm.mContext).enforceCallingOrSelfPermission(anyString(), anyString());
+ doReturn(windowState).when(mWm).getFocusedWindowLocked();
+ doReturn(windowState).when(mWm.mRoot).getCurrentInputMethodWindow();
+
+ TestResultReceiver receiver = new TestResultReceiver();
+ mWm.requestAppKeyboardShortcuts(receiver, 0);
+ mWm.requestImeKeyboardShortcuts(receiver, 0);
+ verify(window, times(2)).requestAppKeyboardShortcuts(receiver, 0);
+ }
+
+ class TestResultReceiver implements IResultReceiver {
+ public android.os.Bundle resultData;
+ private final IBinder mBinder = mock(IBinder.class);
+
+ @Override
+ public void send(int resultCode, android.os.Bundle resultData)
+ throws android.os.RemoteException {
+ this.resultData = resultData;
+ }
+
+ @Override
+ public android.os.IBinder asBinder() {
+ return mBinder;
+ }
+ }
+
private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
when(remoteToken.toWindowContainerToken()).thenReturn(wct);
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index ddc729f773b2..be8ee7832a5d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -71,6 +71,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
+import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
@@ -946,6 +947,14 @@ class WindowTestsBase extends SystemServiceTestsBase {
dc.setRotationAnimation(null);
}
+ static void resizeDisplay(DisplayContent displayContent, int width, int height) {
+ displayContent.updateBaseDisplayMetrics(width, height, displayContent.mBaseDisplayDensity,
+ displayContent.mBaseDisplayPhysicalXDpi, displayContent.mBaseDisplayPhysicalYDpi);
+ final Configuration c = new Configuration();
+ displayContent.computeScreenConfiguration(c);
+ displayContent.onRequestedOverrideConfigurationChanged(c);
+ }
+
// The window definition for UseTestDisplay#addWindows. The test can declare to add only
// necessary windows, that avoids adding unnecessary overhead of unused windows.
static final int W_NOTIFICATION_SHADE = TYPE_NOTIFICATION_SHADE;
diff --git a/telephony/java/android/telephony/data/DataCallResponse.java b/telephony/java/android/telephony/data/DataCallResponse.java
index a834e2bbd0d1..b5c1d7d9e5f4 100644
--- a/telephony/java/android/telephony/data/DataCallResponse.java
+++ b/telephony/java/android/telephony/data/DataCallResponse.java
@@ -461,10 +461,6 @@ public final class DataCallResponse implements Parcelable {
DataCallResponse other = (DataCallResponse) o;
- final boolean isQosSame = (mDefaultQos == null || other.mDefaultQos == null)
- ? mDefaultQos == other.mDefaultQos
- : mDefaultQos.equals(other.mDefaultQos);
-
final boolean isQosBearerSessionsSame =
(mQosBearerSessions == null || other.mQosBearerSessions == null)
? mQosBearerSessions == other.mQosBearerSessions
@@ -496,7 +492,7 @@ public final class DataCallResponse implements Parcelable {
&& mMtuV6 == other.mMtuV6
&& mHandoverFailureMode == other.mHandoverFailureMode
&& mPduSessionId == other.mPduSessionId
- && isQosSame
+ && Objects.equals(mDefaultQos, other.mDefaultQos)
&& isQosBearerSessionsSame
&& Objects.equals(mSliceInfo, other.mSliceInfo)
&& isTrafficDescriptorsSame;
@@ -557,15 +553,7 @@ public final class DataCallResponse implements Parcelable {
dest.writeInt(mMtuV6);
dest.writeInt(mHandoverFailureMode);
dest.writeInt(mPduSessionId);
- if (mDefaultQos != null) {
- if (mDefaultQos.getType() == Qos.QOS_TYPE_EPS) {
- dest.writeParcelable((EpsQos) mDefaultQos, flags);
- } else {
- dest.writeParcelable((NrQos) mDefaultQos, flags);
- }
- } else {
- dest.writeParcelable(null, flags);
- }
+ dest.writeParcelable(mDefaultQos, flags);
dest.writeList(mQosBearerSessions);
dest.writeParcelable(mSliceInfo, flags);
dest.writeList(mTrafficDescriptors);
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
new file mode 100644
index 000000000000..00316ea249b7
--- /dev/null
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/MainActivityStartsSecondaryWithAlwaysExpandTest.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm.flicker.activityembedding
+
+import android.platform.test.annotations.Presubmit
+import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
+import android.tools.device.flicker.legacy.FlickerBuilder
+import android.tools.device.flicker.legacy.FlickerTest
+import android.tools.device.flicker.legacy.FlickerTestFactory
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+import android.tools.common.datatypes.Rect
+
+/**
+ * Test launching an activity with AlwaysExpand rule.
+ *
+ * Setup: Launch A|B in split with B being the secondary activity.
+ * Transitions:
+ * A start C with alwaysExpand=true, expect C to launch in fullscreen and cover split A|B.
+ *
+ * To run this test: `atest FlickerTests:MainActivityStartsSecondaryWithAlwaysExpandTest`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class MainActivityStartsSecondaryWithAlwaysExpandTest(flicker: FlickerTest) :
+ ActivityEmbeddingTestBase(flicker) {
+
+ /** {@inheritDoc} */
+ override val transition: FlickerBuilder.() -> Unit = {
+ setup {
+ tapl.setExpectedRotationCheckEnabled(false)
+ // Launch a split
+ testApp.launchViaIntent(wmHelper)
+ testApp.launchSecondaryActivity(wmHelper)
+ startDisplayBounds =
+ wmHelper.currentState.layerState.physicalDisplayBounds ?: error("Display not found")
+ }
+ transitions {
+ // Launch C with alwaysExpand
+ testApp.launchAlwaysExpandActivity(wmHelper)
+ }
+ teardown {
+ tapl.goHome()
+ testApp.exit(wmHelper)
+ }
+ }
+
+ /** Transition begins with a split. */
+ @Presubmit
+ @Test
+ fun startsWithSplit() {
+ flicker.assertWmStart {
+ this.isAppWindowVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ flicker.assertWmStart {
+ this.isAppWindowVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+
+ /** Main activity should become invisible after being covered by always expand activity. */
+ @Presubmit
+ @Test
+ fun mainActivityLayerBecomesInvisible() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.MAIN_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** Secondary activity should become invisible after being covered by always expand activity. */
+ @Presubmit
+ @Test
+ fun secondaryActivityLayerBecomesInvisible() {
+ flicker.assertLayers {
+ isVisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ .then()
+ .isInvisible(ActivityEmbeddingAppHelper.SECONDARY_ACTIVITY_COMPONENT)
+ }
+ }
+
+ /** At the end of transition always expand activity is in fullscreen. */
+ @Presubmit
+ @Test
+ fun endsWithAlwaysExpandActivityCoveringFullScreen() {
+ flicker.assertWmEnd {
+ this.visibleRegion(ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+ .coversExactly(startDisplayBounds)
+ }
+ }
+
+ /** Always expand activity is on top of the split. */
+ @Presubmit
+ @Test
+ fun endsWithAlwaysExpandActivityOnTop() {
+ flicker.assertWmEnd {
+ this.isAppWindowOnTop(
+ ActivityEmbeddingAppHelper.ALWAYS_EXPAND_ACTIVITY_COMPONENT)
+ }
+ }
+
+ companion object {
+ /** {@inheritDoc} */
+ private var startDisplayBounds = Rect.EMPTY
+ /**
+ * Creates the test configurations.
+ *
+ * See [FlickerTestFactory.nonRotationTests] for configuring screen orientation and
+ * navigation modes.
+ */
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): Collection<FlickerTest> {
+ return FlickerTestFactory.nonRotationTests()
+ }
+ }
+}
+
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
index ed17059e79e7..ed17059e79e7 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingPlaceholderSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingPlaceholderSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
index 863828881d36..863828881d36 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/OpenActivityEmbeddingSecondaryToSplitTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/activityembedding/open/OpenActivityEmbeddingSecondaryToSplitTest.kt
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
index e019b2b22680..a21965e0d7d5 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ActivityEmbeddingAppHelper.kt
@@ -75,6 +75,29 @@ constructor(
.StateSyncBuilder()
.withActivityRemoved(SECONDARY_ACTIVITY_COMPONENT)
.waitForAndVerify()
+ }
+
+ /**
+ * Clicks the button to launch a secondary activity with alwaysExpand enabled, which will launch
+ * a fullscreen window on top of the visible region.
+ */
+ fun launchAlwaysExpandActivity(wmHelper: WindowManagerStateHelper) {
+ val launchButton =
+ uiDevice.wait(
+ Until.findObject(
+ By.res(getPackage(),
+ "launch_always_expand_activity_button")),
+ FIND_TIMEOUT
+ )
+ require(launchButton != null) {
+ "Can't find launch always expand activity button on screen."
+ }
+ launchButton.click()
+ wmHelper
+ .StateSyncBuilder()
+ .withActivityState(ALWAYS_EXPAND_ACTIVITY_COMPONENT, PlatformConsts.STATE_RESUMED)
+ .withActivityState(MAIN_ACTIVITY_COMPONENT, PlatformConsts.STATE_PAUSED)
+ .waitForAndVerify()
}
/**
@@ -105,6 +128,9 @@ constructor(
val SECONDARY_ACTIVITY_COMPONENT =
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT.toFlickerComponent()
+ val ALWAYS_EXPAND_ACTIVITY_COMPONENT =
+ ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT.toFlickerComponent()
+
val PLACEHOLDER_PRIMARY_COMPONENT =
ActivityOptions.ActivityEmbedding.PlaceholderPrimaryActivity.COMPONENT
.toFlickerComponent()
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 1ec9ec9b0eda..dc9ff3b01822 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -198,6 +198,13 @@
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="false"/>
<activity
+ android:name=".ActivityEmbeddingAlwaysExpandActivity"
+ android:label="ActivityEmbedding AlwaysExpand"
+ android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
+ android:theme="@style/CutoutShortEdges"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
+ android:exported="false"/>
+ <activity
android:name=".ActivityEmbeddingPlaceholderPrimaryActivity"
android:label="ActivityEmbedding Placeholder Primary"
android:taskAffinity="com.android.server.wm.flicker.testapp.ActivityEmbedding"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
index d78b9a836a37..f5241cae8fa8 100644
--- a/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/res/layout/activity_embedding_main_layout.xml
@@ -37,4 +37,12 @@
android:onClick="launchPlaceholderSplit"
android:text="Launch Placeholder Split" />
+ <Button
+ android:id="@+id/launch_always_expand_activity_button"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_centerHorizontal="true"
+ android:onClick="launchAlwaysExpandActivity"
+ android:text="Launch Always Expand Activity" />
+
</LinearLayout>
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
new file mode 100644
index 000000000000..d9b24ed23424
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingAlwaysExpandActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.wm.flicker.testapp;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+
+/**
+ * Activity with alwaysExpand=true (launched via R.id.launch_always_expand_activity_button)
+ */
+public class ActivityEmbeddingAlwaysExpandActivity extends Activity {
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_embedding_base_layout);
+ findViewById(R.id.root_activity_layout).setBackgroundColor(Color.GREEN);
+ }
+
+}
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
index 6a7a2ccd3378..61202545f407 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityEmbeddingMainActivity.java
@@ -23,6 +23,9 @@ import android.util.ArraySet;
import android.util.Log;
import android.view.View;
+import androidx.window.embedding.ActivityFilter;
+import androidx.window.embedding.ActivityRule;
+import androidx.window.embedding.RuleController;
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.EmbeddingRule;
import androidx.window.extensions.embedding.SplitPairRule;
@@ -30,6 +33,7 @@ import androidx.window.extensions.embedding.SplitPlaceholderRule;
import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper;
+import java.util.HashSet;
import java.util.Set;
/** Main activity of the ActivityEmbedding test app to launch other embedding activities. */
@@ -50,6 +54,23 @@ public class ActivityEmbeddingMainActivity extends Activity {
ActivityOptions.ActivityEmbedding.SecondaryActivity.COMPONENT));
}
+ /** R.id.launch_always_expand_activity_button onClick */
+ public void launchAlwaysExpandActivity(View view) {
+ final Set<ActivityFilter> activityFilters = new HashSet<>();
+ activityFilters.add(
+ new ActivityFilter(ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT,
+ null));
+ final ActivityRule activityRule = new ActivityRule.Builder(activityFilters)
+ .setAlwaysExpand(true)
+ .build();
+
+ RuleController rc = RuleController.getInstance(this);
+
+ rc.addRule(activityRule);
+ startActivity(new Intent().setComponent(
+ ActivityOptions.ActivityEmbedding.AlwaysExpandActivity.COMPONENT));
+ }
+
/** R.id.launch_placeholder_split_button onClick */
public void launchPlaceholderSplit(View view) {
initializeSplitRules(createSplitPlaceholderRules());
diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
index 9c3226b5292c..0f5c003f12fd 100644
--- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
+++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java
@@ -99,6 +99,12 @@ public class ActivityOptions {
FLICKER_APP_PACKAGE + ".ActivityEmbeddingSecondaryActivity");
}
+ public static class AlwaysExpandActivity {
+ public static final String LABEL = "ActivityEmbeddingAlwaysExpandActivity";
+ public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
+ FLICKER_APP_PACKAGE + ".ActivityEmbeddingAlwaysExpandActivity");
+ }
+
public static class PlaceholderPrimaryActivity {
public static final String LABEL = "ActivityEmbeddingPlaceholderPrimaryActivity";
public static final ComponentName COMPONENT = new ComponentName(FLICKER_APP_PACKAGE,
diff --git a/tests/InputMethodStressTest/Android.bp b/tests/InputMethodStressTest/Android.bp
index 27640a5f51bc..84845c69fb27 100644
--- a/tests/InputMethodStressTest/Android.bp
+++ b/tests/InputMethodStressTest/Android.bp
@@ -30,7 +30,6 @@ android_test {
],
test_suites: [
"general-tests",
- "vts",
],
data: [
":SimpleTestIme",
diff --git a/tests/InputMethodStressTest/AndroidTest.xml b/tests/InputMethodStressTest/AndroidTest.xml
index bedf0990a188..bfebb42ad244 100644
--- a/tests/InputMethodStressTest/AndroidTest.xml
+++ b/tests/InputMethodStressTest/AndroidTest.xml
@@ -19,6 +19,7 @@
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
<target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="setprop debug.wm.disable_deprecated_abi_dialog 1" />
<option name="run-command" value="settings put secure show_ime_with_hard_keyboard 1" />
<option name="teardown-command" value="settings delete secure show_ime_with_hard_keyboard" />
</target_preparer>
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
index 3d257b29287f..807f0c63668c 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/AutoShowTest.java
@@ -199,8 +199,7 @@ public final class AutoShowTest {
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
TestActivity firstActivity = TestActivity.start(intent1);
// Show Ime with InputMethodManager to ensure the keyboard is on.
- boolean succ = callOnMainSync(firstActivity::showImeWithInputMethodManager);
- assertThat(succ).isTrue();
+ callOnMainSync(firstActivity::showImeWithInputMethodManager);
SystemClock.sleep(1000);
mInstrumentation.waitForIdleSync();
@@ -264,8 +263,7 @@ public final class AutoShowTest {
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
ImeStressTestUtil.TestActivity secondActivity = activity.startSecondTestActivity(intent2);
// Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
- boolean succ = callOnMainSync(secondActivity::showImeWithInputMethodManager);
- assertThat(succ).isTrue();
+ callOnMainSync(secondActivity::showImeWithInputMethodManager);
SystemClock.sleep(1000);
mInstrumentation.waitForIdleSync();
// Close the second activity
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
index 299cbf1a84c7..0c267b27490b 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/DefaultImeVisibilityTest.java
@@ -26,8 +26,6 @@ import static com.android.inputmethod.stresstest.ImeStressTestUtil.verifyWindowA
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsHidden;
import static com.android.inputmethod.stresstest.ImeStressTestUtil.waitOnMainUntilImeIsShown;
-import static com.google.common.truth.Truth.assertThat;
-
import android.content.Intent;
import android.platform.test.annotations.RootPermissionTest;
import android.platform.test.rule.UnlockScreenRule;
@@ -83,14 +81,11 @@ public final class DefaultImeVisibilityTest {
ImeStressTestUtil.TestActivity activity = ImeStressTestUtil.TestActivity.start(intent);
EditText editText = activity.getEditText();
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
-
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isTrue();
+ callOnMainSync(activity::showImeWithInputMethodManager);
verifyWindowAndViewFocus(editText, true, true);
waitOnMainUntilImeIsShown(editText);
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isTrue();
+ callOnMainSync(activity::hideImeWithInputMethodManager);
waitOnMainUntilImeIsHidden(editText);
}
}
diff --git a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
index b76a4eb8c0e6..5c0212400ff1 100644
--- a/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
+++ b/tests/InputMethodStressTest/src/com/android/inputmethod/stresstest/ImeOpenCloseStressTest.java
@@ -102,12 +102,10 @@ public final class ImeOpenCloseStressTest {
for (int i = 0; i < iterNum; i++) {
String msgPrefix = "Iteration #" + i + " ";
Log.i(TAG, msgPrefix + "start");
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::showImeWithInputMethodManager);
verifyShowBehavior(activity);
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::hideImeWithInputMethodManager);
verifyHideBehavior(activity);
}
@@ -128,14 +126,12 @@ public final class ImeOpenCloseStressTest {
for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
String msgPrefix = "Iteration #" + i + " ";
Log.i(TAG, msgPrefix + "start");
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isTrue();
+ callOnMainSync(activity::showImeWithInputMethodManager);
waitOnMainUntil(
msgPrefix + "IME should be visible",
() -> !activity.isAnimating() && isImeShown(editText));
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isTrue();
+ callOnMainSync(activity::hideImeWithInputMethodManager);
waitOnMainUntil(
msgPrefix + "IME should be hidden",
() -> !activity.isAnimating() && !isImeShown(editText));
@@ -156,17 +152,13 @@ public final class ImeOpenCloseStressTest {
List<Integer> intervals = new ArrayList<>();
for (int i = 10; i < 100; i += 10) intervals.add(i);
for (int i = 100; i < 1000; i += 50) intervals.add(i);
- boolean firstHide = false;
for (int intervalMillis : intervals) {
String msgPrefix = "Interval = " + intervalMillis + " ";
Log.i(TAG, msgPrefix + " start");
- boolean hideResult = callOnMainSync(activity::hideImeWithInputMethodManager);
- assertThat(hideResult).isEqualTo(firstHide);
- firstHide = true;
+ callOnMainSync(activity::hideImeWithInputMethodManager);
SystemClock.sleep(intervalMillis);
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isTrue();
+ callOnMainSync(activity::showImeWithInputMethodManager);
verifyShowBehavior(activity);
}
}
@@ -247,8 +239,7 @@ public final class ImeOpenCloseStressTest {
TestActivity activity = TestActivity.start(intent);
// Show InputMethodManager without requesting focus
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isFalse();
+ callOnMainSync(activity::showImeWithInputMethodManager);
int windowFlags = activity.getWindow().getAttributes().flags;
EditText editText = activity.getEditText();
@@ -474,8 +465,7 @@ public final class ImeOpenCloseStressTest {
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
TestActivity activity = TestActivity.start(intent1);
// Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::showImeWithInputMethodManager);
Thread.sleep(1000);
verifyShowBehavior(activity);
@@ -503,8 +493,7 @@ public final class ImeOpenCloseStressTest {
Collections.singletonList(REQUEST_FOCUS_ON_CREATE));
TestActivity activity = TestActivity.start(intent);
// Show Ime with InputMethodManager to ensure the keyboard is shown on the second activity
- boolean showResult = callOnMainSync(activity::showImeWithInputMethodManager);
- assertThat(showResult).isEqualTo(!(hasUnfocusableWindowFlags(activity)));
+ callOnMainSync(activity::showImeWithInputMethodManager);
Thread.sleep(2000);
verifyShowBehavior(activity);
diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp
index 9328b67795cb..c60c519ec4d4 100644
--- a/tests/UsbTests/Android.bp
+++ b/tests/UsbTests/Android.bp
@@ -29,7 +29,7 @@ android_test {
static_libs: [
"frameworks-base-testutils",
"androidx.test.rules",
- "mockito-target-inline-minus-junit4",
+ "mockito-target-extended-minus-junit4",
"platform-test-annotations",
"services.core",
"services.net",
@@ -37,7 +37,12 @@ android_test {
"truth-prebuilt",
"UsbManagerTestLib",
],
- jni_libs: ["libdexmakerjvmtiagent"],
+ jni_libs: [
+ // Required for ExtendedMockito
+ "libdexmakerjvmtiagent",
+ "libmultiplejvmtiagentsinterferenceagent",
+ "libstaticjvmtiagent",
+ ],
certificate: "platform",
platform_apis: true,
test_suites: ["device-tests"],
diff --git a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
index 210e3ea2a9b2..c2d0f7c05b91 100644
--- a/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
+++ b/tests/UsbTests/src/com/android/server/usb/UsbHandlerTest.java
@@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -27,23 +28,29 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.debug.AdbManagerInternal;
+import android.debug.AdbTransportType;
import android.hardware.usb.UsbManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
-import android.provider.Settings;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.dx.mockito.inline.extended.ExtendedMockito;
import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
import java.util.HashMap;
import java.util.Locale;
@@ -68,6 +75,8 @@ public class UsbHandlerTest {
private SharedPreferences mSharedPreferences;
@Mock
private SharedPreferences.Editor mEditor;
+ @Mock
+ private AdbManagerInternal mAdbManagerInternal;
private MockUsbHandler mUsbHandler;
@@ -83,6 +92,7 @@ public class UsbHandlerTest {
private Map<String, String> mMockProperties;
private Map<String, Integer> mMockGlobalSettings;
+ private MockitoSession mStaticMockSession;
private class MockUsbHandler extends UsbDeviceManager.UsbHandler {
boolean mIsUsbTransferAllowed;
@@ -157,6 +167,10 @@ public class UsbHandlerTest {
@Before
public void before() {
MockitoAnnotations.initMocks(this);
+ mStaticMockSession = ExtendedMockito.mockitoSession()
+ .mockStatic(LocalServices.class)
+ .strictness(Strictness.WARN)
+ .startMocking();
mMockProperties = new HashMap<>();
mMockGlobalSettings = new HashMap<>();
when(mSharedPreferences.edit()).thenReturn(mEditor);
@@ -164,6 +178,16 @@ public class UsbHandlerTest {
mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
mUsbSettingsManager, mUsbPermissionManager);
+
+ when(LocalServices.getService(eq(AdbManagerInternal.class)))
+ .thenReturn(mAdbManagerInternal);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mStaticMockSession != null) {
+ mStaticMockSession.finishMocking();
+ }
}
@SmallTest
@@ -234,8 +258,8 @@ public class UsbHandlerTest {
assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
assertEquals(mMockProperties.get(UsbDeviceManager.UsbHandler
.USB_PERSISTENT_CONFIG_PROPERTY), UsbManager.USB_FUNCTION_ADB);
- assertTrue(mUsbHandler.isAdbEnabled());
+ when(mAdbManagerInternal.isAdbEnabled(eq(AdbTransportType.USB))).thenReturn(true);
mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_UPDATE_STATE, 1, 1));
assertTrue(mUsbHandler.mBroadcastedIntent.getBooleanExtra(UsbManager.USB_CONNECTED, false));
@@ -271,20 +295,6 @@ public class UsbHandlerTest {
@SmallTest
@Test
- public void bootCompletedAdbEnabled() {
- mMockProperties.put(UsbDeviceManager.UsbHandler.USB_PERSISTENT_CONFIG_PROPERTY, "adb");
- mUsbHandler = new MockUsbHandler(FgThread.get().getLooper(),
- InstrumentationRegistry.getContext(), mUsbDeviceManager, mUsbAlsaManager,
- mUsbSettingsManager, mUsbPermissionManager);
-
- sendBootCompleteMessages(mUsbHandler);
- assertEquals(mUsbHandler.getEnabledFunctions(), UsbManager.FUNCTION_NONE);
- assertEquals(mMockGlobalSettings.get(Settings.Global.ADB_ENABLED).intValue(), 1);
- assertTrue(mUsbHandler.isAdbEnabled());
- }
-
- @SmallTest
- @Test
public void userSwitchedDisablesMtp() {
mUsbHandler.handleMessage(mUsbHandler.obtainMessage(MSG_SET_CURRENT_FUNCTIONS,
UsbManager.FUNCTION_MTP));