summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java4
-rw-r--r--apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java7
-rw-r--r--apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java22
-rw-r--r--apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java15
-rw-r--r--apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java21
-rw-r--r--core/api/test-current.txt9
-rw-r--r--core/java/android/app/Activity.java14
-rw-r--r--core/java/android/app/ActivityClient.java22
-rw-r--r--core/java/android/app/ActivityManager.java1
-rw-r--r--core/java/android/app/ActivityOptions.java1
-rw-r--r--core/java/android/app/ActivityThread.java56
-rw-r--r--core/java/android/app/ActivityThreadInternal.java2
-rw-r--r--core/java/android/app/ActivityTransitionState.java10
-rw-r--r--core/java/android/app/BroadcastOptions.java33
-rw-r--r--core/java/android/app/ConfigurationController.java6
-rw-r--r--core/java/android/app/IActivityClientController.aidl7
-rw-r--r--core/java/android/app/IActivityTaskManager.aidl5
-rw-r--r--core/java/android/app/Instrumentation.java39
-rw-r--r--core/java/android/app/ResourcesManager.java4
-rw-r--r--core/java/android/app/TaskInfo.java10
-rw-r--r--core/java/android/app/WindowConfiguration.java22
-rw-r--r--core/java/android/app/search/SearchAction.java4
-rw-r--r--core/java/android/app/search/SearchTarget.java2
-rw-r--r--core/java/android/app/usage/UsageStats.java11
-rw-r--r--core/java/android/app/usage/UsageStatsManager.java8
-rw-r--r--core/java/android/appwidget/AppWidgetManager.java4
-rw-r--r--core/java/android/companion/AssociationInfo.java46
-rw-r--r--core/java/android/content/ClipboardManager.java55
-rw-r--r--core/java/android/content/IClipboard.aidl20
-rw-r--r--core/java/android/content/pm/ActivityInfo.java20
-rw-r--r--core/java/android/content/pm/PackageParser.java56
-rw-r--r--core/java/android/content/pm/UserInfo.java19
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java5
-rw-r--r--core/java/android/hardware/biometrics/BiometricConstants.java5
-rw-r--r--core/java/android/hardware/biometrics/BiometricFaceConstants.java7
-rw-r--r--core/java/android/hardware/biometrics/BiometricFingerprintConstants.java9
-rw-r--r--core/java/android/hardware/biometrics/BiometricStateListener.java15
-rw-r--r--core/java/android/hardware/biometrics/IBiometricStateListener.aidl1
-rw-r--r--core/java/android/hardware/display/AmbientDisplayConfiguration.java3
-rw-r--r--core/java/android/hardware/display/DisplayManager.java23
-rw-r--r--core/java/android/hardware/display/DisplayManagerGlobal.java13
-rw-r--r--core/java/android/hardware/display/IDisplayManager.aidl2
-rw-r--r--core/java/android/hardware/fingerprint/FingerprintManager.java25
-rw-r--r--core/java/android/hardware/fingerprint/IFingerprintService.aidl3
-rw-r--r--core/java/android/hardware/radio/ProgramList.java55
-rw-r--r--core/java/android/nfc/INfcTag.aidl1
-rw-r--r--core/java/android/nfc/Tag.java23
-rw-r--r--core/java/android/os/IUserManager.aidl1
-rw-r--r--core/java/android/os/UserManager.java52
-rw-r--r--core/java/android/provider/Settings.java114
-rw-r--r--core/java/android/service/dreams/DreamActivity.java15
-rw-r--r--core/java/android/service/wallpaper/WallpaperService.java42
-rw-r--r--core/java/android/util/FeatureFlagUtils.java12
-rw-r--r--core/java/android/util/SafetyProtectionUtils.java53
-rw-r--r--core/java/android/view/IDisplayChangeWindowCallback.aidl (renamed from core/java/android/view/IDisplayWindowRotationCallback.aidl)10
-rw-r--r--core/java/android/view/IDisplayChangeWindowController.aidl (renamed from core/java/android/view/IDisplayWindowRotationController.aidl)24
-rw-r--r--core/java/android/view/IDisplayWindowInsetsController.aidl6
-rw-r--r--core/java/android/view/IRecentsAnimationController.aidl5
-rw-r--r--core/java/android/view/IWindowManager.aidl36
-rw-r--r--core/java/android/view/IWindowSession.aidl48
-rw-r--r--core/java/android/view/InsetsFrameProvider.java294
-rw-r--r--core/java/android/view/InsetsState.java69
-rw-r--r--core/java/android/view/RemoteAccessibilityController.java23
-rw-r--r--core/java/android/view/RemoteAnimationTarget.java13
-rw-r--r--core/java/android/view/ScrollCaptureConnection.java2
-rw-r--r--core/java/android/view/SurfaceControl.java10
-rw-r--r--core/java/android/view/SurfaceControlViewHost.java6
-rw-r--r--core/java/android/view/ThreadedRenderer.java33
-rw-r--r--core/java/android/view/ViewRootImpl.java196
-rw-r--r--core/java/android/view/ViewTreeObserver.java84
-rw-r--r--core/java/android/view/WindowInsets.java18
-rw-r--r--core/java/android/view/WindowLayout.java49
-rw-r--r--core/java/android/view/WindowManager.java168
-rw-r--r--core/java/android/view/WindowManagerGlobal.java12
-rw-r--r--core/java/android/view/WindowManagerPolicyConstants.java4
-rw-r--r--core/java/android/view/WindowlessWindowLayout.java39
-rw-r--r--core/java/android/view/WindowlessWindowManager.java29
-rw-r--r--core/java/android/widget/TextView.java15
-rw-r--r--core/java/android/widget/Toast.java37
-rw-r--r--core/java/android/widget/ToastPresenter.java21
-rw-r--r--core/java/android/window/ClientWindowFrames.java13
-rw-r--r--core/java/android/window/ITaskFragmentOrganizer.aidl7
-rw-r--r--core/java/android/window/ImeOnBackInvokedDispatcher.java4
-rw-r--r--core/java/android/window/PictureInPictureSurfaceTransaction.java23
-rw-r--r--core/java/android/window/SizeConfigurationBuckets.java17
-rw-r--r--core/java/android/window/TaskFragmentOrganizer.java57
-rw-r--r--core/java/android/window/TransitionInfo.java16
-rw-r--r--core/java/android/window/TransitionRequestInfo.java4
-rw-r--r--core/java/android/window/WindowContainerTransaction.java50
-rw-r--r--core/java/android/window/WindowOnBackInvokedDispatcher.java26
-rw-r--r--core/java/com/android/internal/app/ChooserActivity.java131
-rw-r--r--core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java4
-rw-r--r--core/java/com/android/internal/app/ResolverActivity.java17
-rw-r--r--core/java/com/android/internal/app/ResolverListAdapter.java13
-rw-r--r--core/java/com/android/internal/app/ResolverListController.java11
-rw-r--r--core/java/com/android/internal/jank/FrameTracker.java303
-rw-r--r--core/java/com/android/internal/jank/InteractionJankMonitor.java258
-rw-r--r--core/java/com/android/internal/policy/DecorView.java14
-rw-r--r--core/java/com/android/internal/policy/PhoneWindow.java6
-rw-r--r--core/java/com/android/internal/policy/TransitionAnimation.java33
-rw-r--r--core/java/com/android/internal/statusbar/IStatusBar.aidl5
-rw-r--r--core/java/com/android/internal/statusbar/LetterboxDetails.aidl19
-rw-r--r--core/java/com/android/internal/statusbar/LetterboxDetails.java243
-rw-r--r--core/java/com/android/internal/statusbar/RegisterStatusBarResult.java11
-rw-r--r--core/java/com/android/internal/widget/ResolverDrawerLayout.java38
-rw-r--r--core/jni/android_graphics_BLASTBufferQueue.cpp21
-rw-r--r--core/jni/android_view_SurfaceControl.cpp7
-rw-r--r--core/proto/android/providers/settings/secure.proto9
-rw-r--r--core/proto/android/server/powermanagerservice.proto1
-rw-r--r--core/proto/android/server/windowmanagerservice.proto4
-rw-r--r--core/proto/android/server/windowmanagertransitiontrace.proto70
-rw-r--r--core/res/AndroidManifest.xml11
-rw-r--r--core/res/res/anim-ldrtl/task_fragment_clear_top_close_enter.xml39
-rw-r--r--core/res/res/anim-ldrtl/task_fragment_clear_top_close_exit.xml38
-rw-r--r--core/res/res/anim-ldrtl/task_fragment_clear_top_open_enter.xml37
-rw-r--r--core/res/res/anim-ldrtl/task_fragment_clear_top_open_exit.xml36
-rw-r--r--core/res/res/anim-ldrtl/task_fragment_open_enter.xml2
-rw-r--r--core/res/res/anim/task_fragment_clear_top_close_enter.xml40
-rw-r--r--core/res/res/anim/task_fragment_clear_top_close_exit.xml41
-rw-r--r--core/res/res/anim/task_fragment_clear_top_open_enter.xml41
-rw-r--r--core/res/res/anim/task_fragment_clear_top_open_exit.xml40
-rw-r--r--core/res/res/anim/task_fragment_open_enter.xml2
-rw-r--r--core/res/res/anim/task_fragment_open_exit.xml4
-rw-r--r--core/res/res/drawable-nodpi/default_wallpaper.pngbin738385 -> 861738 bytes
-rw-r--r--core/res/res/drawable-sw600dp-nodpi/default_wallpaper.pngbin2774036 -> 3063458 bytes
-rw-r--r--core/res/res/drawable-sw720dp-nodpi/default_wallpaper.pngbin4958722 -> 4449640 bytes
-rw-r--r--core/res/res/layout-car/car_alert_dialog.xml2
-rw-r--r--core/res/res/layout-car/car_alert_dialog_button_bar.xml20
-rw-r--r--core/res/res/layout/transient_notification_with_icon.xml46
-rw-r--r--core/res/res/values-af/strings.xml3
-rw-r--r--core/res/res/values-am/strings.xml3
-rw-r--r--core/res/res/values-ar/strings.xml9
-rw-r--r--core/res/res/values-as/strings.xml9
-rw-r--r--core/res/res/values-az/strings.xml5
-rw-r--r--core/res/res/values-b+sr+Latn/strings.xml3
-rw-r--r--core/res/res/values-be/strings.xml9
-rw-r--r--core/res/res/values-bg/strings.xml9
-rw-r--r--core/res/res/values-bn/strings.xml9
-rw-r--r--core/res/res/values-bs/strings.xml7
-rw-r--r--core/res/res/values-ca/strings.xml3
-rw-r--r--core/res/res/values-cs/strings.xml3
-rw-r--r--core/res/res/values-da/strings.xml9
-rw-r--r--core/res/res/values-de/strings.xml3
-rw-r--r--core/res/res/values-el/strings.xml9
-rw-r--r--core/res/res/values-en-rAU/strings.xml3
-rw-r--r--core/res/res/values-en-rCA/strings.xml3
-rw-r--r--core/res/res/values-en-rGB/strings.xml3
-rw-r--r--core/res/res/values-en-rIN/strings.xml3
-rw-r--r--core/res/res/values-en-rXC/strings.xml2
-rw-r--r--core/res/res/values-es-rUS/strings.xml9
-rw-r--r--core/res/res/values-es/strings.xml33
-rw-r--r--core/res/res/values-et/strings.xml11
-rw-r--r--core/res/res/values-eu/strings.xml9
-rw-r--r--core/res/res/values-fa/strings.xml5
-rw-r--r--core/res/res/values-fi/strings.xml9
-rw-r--r--core/res/res/values-fr-rCA/strings.xml9
-rw-r--r--core/res/res/values-fr/strings.xml9
-rw-r--r--core/res/res/values-gl/strings.xml9
-rw-r--r--core/res/res/values-gu/strings.xml11
-rw-r--r--core/res/res/values-hi/strings.xml11
-rw-r--r--core/res/res/values-hr/strings.xml3
-rw-r--r--core/res/res/values-hu/strings.xml3
-rw-r--r--core/res/res/values-hy/strings.xml5
-rw-r--r--core/res/res/values-in/strings.xml19
-rw-r--r--core/res/res/values-is/strings.xml9
-rw-r--r--core/res/res/values-it/strings.xml9
-rw-r--r--core/res/res/values-iw/strings.xml3
-rw-r--r--core/res/res/values-ja/strings.xml5
-rw-r--r--core/res/res/values-ka/strings.xml3
-rw-r--r--core/res/res/values-kk/strings.xml54
-rw-r--r--core/res/res/values-km/strings.xml3
-rw-r--r--core/res/res/values-kn/strings.xml3
-rw-r--r--core/res/res/values-ko/strings.xml9
-rw-r--r--core/res/res/values-ky/strings.xml7
-rw-r--r--core/res/res/values-lo/strings.xml9
-rw-r--r--core/res/res/values-lt/strings.xml9
-rw-r--r--core/res/res/values-lv/strings.xml9
-rw-r--r--core/res/res/values-mk/strings.xml3
-rw-r--r--core/res/res/values-ml/strings.xml3
-rw-r--r--core/res/res/values-mn/strings.xml5
-rw-r--r--core/res/res/values-mr/strings.xml3
-rw-r--r--core/res/res/values-ms/strings.xml5
-rw-r--r--core/res/res/values-my/strings.xml3
-rw-r--r--core/res/res/values-nb/strings.xml25
-rw-r--r--core/res/res/values-ne/strings.xml11
-rw-r--r--core/res/res/values-nl/strings.xml9
-rw-r--r--core/res/res/values-or/strings.xml9
-rw-r--r--core/res/res/values-pa/strings.xml3
-rw-r--r--core/res/res/values-pl/strings.xml9
-rw-r--r--core/res/res/values-pt-rBR/strings.xml3
-rw-r--r--core/res/res/values-pt-rPT/strings.xml3
-rw-r--r--core/res/res/values-pt/strings.xml3
-rw-r--r--core/res/res/values-ro/strings.xml3
-rw-r--r--core/res/res/values-ru/strings.xml3
-rw-r--r--core/res/res/values-si/strings.xml3
-rw-r--r--core/res/res/values-sk/strings.xml3
-rw-r--r--core/res/res/values-sl/strings.xml3
-rw-r--r--core/res/res/values-sq/strings.xml9
-rw-r--r--core/res/res/values-sr/strings.xml3
-rw-r--r--core/res/res/values-sv/strings.xml9
-rw-r--r--core/res/res/values-sw/strings.xml9
-rw-r--r--core/res/res/values-ta/strings.xml9
-rw-r--r--core/res/res/values-te/strings.xml11
-rw-r--r--core/res/res/values-th/strings.xml3
-rw-r--r--core/res/res/values-tl/strings.xml3
-rw-r--r--core/res/res/values-tr/strings.xml9
-rw-r--r--core/res/res/values-uk/strings.xml54
-rw-r--r--core/res/res/values-ur/strings.xml9
-rw-r--r--core/res/res/values-uz/strings.xml3
-rw-r--r--core/res/res/values-vi/strings.xml9
-rw-r--r--core/res/res/values-zh-rCN/strings.xml3
-rw-r--r--core/res/res/values-zh-rHK/strings.xml23
-rw-r--r--core/res/res/values-zh-rTW/strings.xml9
-rw-r--r--core/res/res/values-zu/strings.xml3
-rw-r--r--core/res/res/values/attrs.xml9
-rw-r--r--core/res/res/values/config.xml70
-rw-r--r--core/res/res/values/dimens.xml2
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--core/res/res/values/styles.xml3
-rw-r--r--core/res/res/values/styles_car.xml93
-rw-r--r--core/res/res/values/symbols.xml22
-rw-r--r--core/tests/coretests/src/android/app/activity/ActivityThreadTest.java48
-rw-r--r--core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java13
-rw-r--r--core/tests/coretests/src/android/view/InsetsControllerTest.java26
-rw-r--r--core/tests/coretests/src/android/widget/TextViewTest.java17
-rw-r--r--core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java28
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java130
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java12
-rw-r--r--core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java124
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java39
-rw-r--r--core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java15
-rw-r--r--core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java11
-rw-r--r--core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java20
-rw-r--r--core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java15
-rw-r--r--data/etc/services.core.protolog.json116
-rw-r--r--data/fonts/fonts.xml2
-rw-r--r--graphics/java/android/graphics/ImageDecoder.java263
-rw-r--r--graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java177
-rw-r--r--graphics/java/android/graphics/drawable/RippleDrawable.java19
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java4
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java119
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java22
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java15
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java78
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java28
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java133
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java57
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java42
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java32
-rw-r--r--libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java23
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java23
-rw-r--r--libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java17
-rw-r--r--libs/WindowManager/Shell/res/color/decor_button_dark_color.xml21
-rw-r--r--libs/WindowManager/Shell/res/color/decor_button_light_color.xml21
-rw-r--r--libs/WindowManager/Shell/res/color/decor_caption_title_color.xml23
-rw-r--r--libs/WindowManager/Shell/res/color/taskbar_background.xml3
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_caption_title.xml22
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml32
-rw-r--r--libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml36
-rw-r--r--libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml3
-rw-r--r--libs/WindowManager/Shell/res/layout/caption_window_decoration.xml45
-rw-r--r--libs/WindowManager/Shell/res/values/attrs.xml4
-rw-r--r--libs/WindowManager/Shell/res/values/strings.xml10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java80
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java357
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java38
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java213
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java93
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java126
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java303
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java54
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java358
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java175
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java55
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java75
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java431
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java10
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java233
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java35
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/README.txt13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java212
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java236
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md18
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md73
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md50
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md69
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md13
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md58
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md65
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md83
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java45
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java78
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java418
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java1314
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java110
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java150
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java326
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java85
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java762
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java376
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java348
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java386
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java11
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java68
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java4
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java44
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java12
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java40
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java230
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java39
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java127
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java97
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java5
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java22
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java26
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java8
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java182
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java198
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java579
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java128
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl103
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java104
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java181
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java144
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java595
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java292
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java324
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java1333
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java298
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java224
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java3
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java31
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java51
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java36
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java)84
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java240
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java)111
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java62
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java37
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java82
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java289
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java57
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java7
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java9
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java225
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java2
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java95
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java)191
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java)279
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java117
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java29
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java30
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java14
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java)20
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java179
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java205
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java46
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java296
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java)17
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java99
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java72
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java49
-rw-r--r--libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java341
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt3
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt115
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt128
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt121
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt178
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt109
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt117
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt76
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt44
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt178
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt116
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt104
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt126
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt110
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt115
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt124
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt129
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt196
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt153
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt169
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt155
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt47
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt160
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt149
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS2
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt122
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt228
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt114
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt113
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt136
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt133
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt116
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt111
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt24
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt12
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt7
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt5
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt148
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt18
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt123
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt139
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt129
-rw-r--r--libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt59
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml11
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml30
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml33
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java6
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java27
-rw-r--r--libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java61
-rw-r--r--libs/WindowManager/Shell/tests/unittest/Android.bp8
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java55
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java134
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java22
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java33
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java123
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java104
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java77
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java36
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java142
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java181
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt9
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java27
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java65
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java166
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java23
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java5
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java42
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTestCase.java4
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java18
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java66
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java21
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java34
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java96
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java24
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java (renamed from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java)17
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java2
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java167
-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.java14
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java63
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java30
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java6
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java331
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java58
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java3
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java354
-rw-r--r--libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java326
-rw-r--r--libs/hwui/JankTracker.cpp5
-rw-r--r--libs/hwui/tests/unit/JankTrackerTests.cpp65
-rw-r--r--media/TEST_MAPPING12
-rw-r--r--media/java/android/media/projection/IMediaProjection.aidl14
-rw-r--r--media/java/android/media/projection/MediaProjection.java10
-rw-r--r--packages/CompanionDeviceManager/res/values/strings.xml4
-rw-r--r--packages/SettingsLib/Android.bp1
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/Android.bp16
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/AndroidManifest.xml21
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java (renamed from packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java)0
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java (renamed from packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java)0
-rw-r--r--packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java (renamed from packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java)0
-rw-r--r--packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml2
-rw-r--r--packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml6
-rw-r--r--packages/SettingsLib/res/values-af/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-am/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ar/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-as/strings.xml21
-rw-r--r--packages/SettingsLib/res/values-az/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-b+sr+Latn/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-be/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-bg/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-bn/strings.xml19
-rw-r--r--packages/SettingsLib/res/values-bs/strings.xml19
-rw-r--r--packages/SettingsLib/res/values-ca/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-cs/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-da/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-de/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-el/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-en-rAU/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-en-rCA/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-en-rGB/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-en-rIN/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-en-rXC/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-es-rUS/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-es/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-et/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-eu/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-fa/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-fi/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-fr-rCA/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-fr/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-gl/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-gu/strings.xml19
-rw-r--r--packages/SettingsLib/res/values-hi/arrays.xml2
-rw-r--r--packages/SettingsLib/res/values-hi/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-hr/strings.xml25
-rw-r--r--packages/SettingsLib/res/values-hu/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-hy/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-in/strings.xml21
-rw-r--r--packages/SettingsLib/res/values-is/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-it/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-iw/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ja/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ka/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-kk/strings.xml31
-rw-r--r--packages/SettingsLib/res/values-km/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-kn/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ko/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ky/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-lo/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-lt/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-lv/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-mk/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ml/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-mn/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-mr/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ms/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-my/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-nb/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ne/strings.xml21
-rw-r--r--packages/SettingsLib/res/values-nl/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-or/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-pa/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-pl/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-pt-rBR/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-pt-rPT/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-pt/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ro/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-ru/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-si/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-sk/strings.xml21
-rw-r--r--packages/SettingsLib/res/values-sl/strings.xml19
-rw-r--r--packages/SettingsLib/res/values-sq/strings.xml19
-rw-r--r--packages/SettingsLib/res/values-sr/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-sv/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-sw/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-ta/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-te/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-th/strings.xml17
-rw-r--r--packages/SettingsLib/res/values-tl/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-tr/strings.xml23
-rw-r--r--packages/SettingsLib/res/values-uk/strings.xml29
-rw-r--r--packages/SettingsLib/res/values-ur/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-uz/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-vi/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-zh-rCN/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-zh-rHK/strings.xml21
-rw-r--r--packages/SettingsLib/res/values-zh-rTW/strings.xml15
-rw-r--r--packages/SettingsLib/res/values-zu/strings.xml15
-rw-r--r--packages/SettingsLib/res/values/strings.xml44
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/Utils.java2
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java14
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java29
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java47
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java11
-rw-r--r--packages/SettingsLib/tests/integ/Android.bp3
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java11
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java88
-rw-r--r--packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java125
-rw-r--r--packages/SettingsProvider/Android.bp2
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java1
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java5
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java21
-rw-r--r--packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java6
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java10
-rw-r--r--packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java6
-rw-r--r--packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java2
-rw-r--r--packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java59
-rw-r--r--packages/SystemUI/Android.bp3
-rw-r--r--packages/SystemUI/AndroidManifest.xml13
-rw-r--r--packages/SystemUI/TEST_MAPPING11
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt304
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt535
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt (renamed from packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt)4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt31
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt148
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt2
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt187
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt4
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt (renamed from packages/SystemUI/src/com/android/keyguard/TextAnimator.kt)114
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt (renamed from packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt)91
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt489
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt41
-rw-r--r--packages/SystemUI/docs/camera.md45
-rw-r--r--packages/SystemUI/docs/device-entry/doze.md3
-rw-r--r--packages/SystemUI/docs/user-file-manager.md33
-rw-r--r--packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java5
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java2
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt109
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java2
-rw-r--r--packages/SystemUI/proguard.flags6
-rw-r--r--packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_off.xml725
-rw-r--r--packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_on.xml781
-rw-r--r--packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_off.xml144
-rw-r--r--packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_on.xml138
-rw-r--r--packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_search.xml268
-rw-r--r--packages/SystemUI/res-keyguard/font/clock.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml1
-rw-r--r--packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml4
-rw-r--r--packages/SystemUI/res-keyguard/values/attrs.xml6
-rw-r--r--packages/SystemUI/res-keyguard/values/dimens.xml13
-rw-r--r--packages/SystemUI/res-keyguard/values/donottranslate.xml6
-rw-r--r--packages/SystemUI/res-keyguard/values/strings.xml46
-rw-r--r--packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml23
-rw-r--r--packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml20
-rw-r--r--packages/SystemUI/res/drawable/dream_overlay_camera_off.xml29
-rw-r--r--packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml33
-rw-r--r--packages/SystemUI/res/drawable/dream_overlay_mic_off.xml29
-rw-r--r--packages/SystemUI/res/drawable/ic_media_pause_container.xml11
-rw-r--r--packages/SystemUI/res/drawable/ic_media_play_container.xml6
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_screen_saver.xml24
-rw-r--r--packages/SystemUI/res/drawable/ic_qs_screen_saver_undocked.xml30
-rw-r--r--packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml22
-rw-r--r--packages/SystemUI/res/drawable/qs_airplane_icon_off.xml53
-rw-r--r--packages/SystemUI/res/drawable/qs_airplane_icon_on.xml98
-rw-r--r--packages/SystemUI/res/drawable/qs_battery_saver_icon_off.xml67
-rw-r--r--packages/SystemUI/res/drawable/qs_battery_saver_icon_on.xml636
-rw-r--r--packages/SystemUI/res/drawable/qs_camera_access_icon_off.xml110
-rw-r--r--packages/SystemUI/res/drawable/qs_camera_access_icon_on.xml93
-rw-r--r--packages/SystemUI/res/drawable/qs_dnd_icon_off.xml56
-rw-r--r--packages/SystemUI/res/drawable/qs_dnd_icon_on.xml73
-rw-r--r--packages/SystemUI/res/drawable/qs_location_icon_off.xml166
-rw-r--r--packages/SystemUI/res/drawable/qs_location_icon_on.xml155
-rw-r--r--packages/SystemUI/res/drawable/qs_mic_access_off.xml212
-rw-r--r--packages/SystemUI/res/drawable/qs_mic_access_on.xml212
-rw-r--r--packages/SystemUI/res/drawable/screenshot_edit_background.xml2
-rw-r--r--packages/SystemUI/res/drawable/settings_input_antenna.xml23
-rw-r--r--packages/SystemUI/res/layout/auth_container_view.xml1
-rw-r--r--packages/SystemUI/res/layout/brightness_mirror_container.xml1
-rw-r--r--packages/SystemUI/res/layout/broadcast_dialog.xml79
-rw-r--r--packages/SystemUI/res/layout/clipboard_overlay.xml1
-rw-r--r--packages/SystemUI/res/layout/combined_qs_header.xml60
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml26
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml4
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml4
-rw-r--r--packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml29
-rw-r--r--packages/SystemUI/res/layout/hybrid_conversation_notification.xml6
-rw-r--r--packages/SystemUI/res/layout/hybrid_notification.xml9
-rw-r--r--packages/SystemUI/res/layout/keyguard_bottom_area.xml20
-rw-r--r--packages/SystemUI/res/layout/keyguard_status_bar.xml3
-rw-r--r--packages/SystemUI/res/layout/large_screen_shade_header.xml2
-rw-r--r--packages/SystemUI/res/layout/media_output_dialog.xml36
-rw-r--r--packages/SystemUI/res/layout/media_session_view.xml4
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip.xml5
-rw-r--r--packages/SystemUI/res/layout/media_ttt_chip_receiver.xml11
-rw-r--r--packages/SystemUI/res/layout/notification_icon_area.xml17
-rw-r--r--packages/SystemUI/res/layout/people_space_activity.xml104
-rw-r--r--packages/SystemUI/res/layout/people_space_activity_no_conversations.xml3
-rw-r--r--packages/SystemUI/res/layout/people_space_activity_with_conversations.xml115
-rw-r--r--packages/SystemUI/res/layout/people_space_tile_view.xml4
-rw-r--r--packages/SystemUI/res/layout/qs_tile_label.xml4
-rw-r--r--packages/SystemUI/res/layout/status_bar.xml160
-rw-r--r--packages/SystemUI/res/layout/status_bar_expanded.xml8
-rw-r--r--packages/SystemUI/res/layout/super_notification_shade.xml4
-rw-r--r--packages/SystemUI/res/layout/user_switcher_fullscreen.xml4
-rw-r--r--packages/SystemUI/res/layout/wireless_charging_layout.xml2
-rw-r--r--packages/SystemUI/res/values-night/styles.xml1
-rw-r--r--packages/SystemUI/res/values-sw600dp-land/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw600dp-port/dimens.xml5
-rw-r--r--packages/SystemUI/res/values-sw720dp-land/dimens.xml2
-rw-r--r--packages/SystemUI/res/values-sw720dp-port/dimens.xml7
-rw-r--r--packages/SystemUI/res/values-television/config.xml2
-rw-r--r--packages/SystemUI/res/values/attrs.xml10
-rw-r--r--packages/SystemUI/res/values/colors.xml10
-rw-r--r--packages/SystemUI/res/values/config.xml43
-rw-r--r--packages/SystemUI/res/values/dimens.xml87
-rw-r--r--packages/SystemUI/res/values/strings.xml130
-rw-r--r--packages/SystemUI/res/values/styles.xml72
-rw-r--r--packages/SystemUI/res/values/tiles_states_strings.xml10
-rw-r--r--packages/SystemUI/res/xml/combined_qs_header_scene.xml93
-rw-r--r--packages/SystemUI/res/xml/large_screen_shade_header.xml40
-rw-r--r--packages/SystemUI/res/xml/media_session_collapsed.xml17
-rw-r--r--packages/SystemUI/res/xml/qqs_header.xml47
-rw-r--r--packages/SystemUI/res/xml/qs_header_new.xml124
-rw-r--r--packages/SystemUI/screenshot/Android.bp45
-rw-r--r--packages/SystemUI/screenshot/AndroidManifest.xml26
-rw-r--r--packages/SystemUI/screenshot/res/values/themes.xml31
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt66
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt22
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt42
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt180
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt203
-rw-r--r--packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt37
-rw-r--r--packages/SystemUI/shared/Android.bp7
-rw-r--r--packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml20
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_large.xml32
-rw-r--r--packages/SystemUI/shared/res/layout/clock_default_small.xml33
-rw-r--r--packages/SystemUI/shared/res/values/attrs.xml28
-rw-r--r--packages/SystemUI/shared/res/values/dimens.xml27
-rw-r--r--packages/SystemUI/shared/res/values/donottranslate.xml23
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt41
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt44
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt (renamed from packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt)47
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt169
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt183
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl21
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java16
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java9
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java)25
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java46
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java22
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java8
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java37
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java80
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java90
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java44
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java68
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt49
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt35
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt51
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt61
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt13
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt19
-rw-r--r--packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt14
-rw-r--r--packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java32
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java11
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java5
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java6
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java470
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java85
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java9
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java143
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java22
-rw-r--r--packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java2
-rw-r--r--packages/SystemUI/src/com/android/keyguard/LockIconViewController.java10
-rw-r--r--packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java3
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java21
-rw-r--r--packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/Gefingerpoken.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java269
-rw-r--r--packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java72
-rw-r--r--packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/Somnambulator.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java158
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt183
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIApplication.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java (renamed from packages/SystemUI/src/com/android/systemui/SystemUIFactory.java)168
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt54
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java134
-rw-r--r--packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt158
-rw-r--r--packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt)19
-rw-r--r--packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java)26
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLog.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java25
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/doze/DozeUi.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java185
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java110
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt (renamed from packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt)42
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/flags/Flags.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java58
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt40
-rw-r--r--packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java53
-rw-r--r--packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java)25
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt44
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java119
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaData.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java43
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt147
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt119
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java40
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java172
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt35
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt73
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl)30
-rw-r--r--packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java44
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java14
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java49
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt397
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt733
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java190
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt144
-rw-r--r--packages/SystemUI/src/com/android/systemui/navigationbar/gestural/GestureModule.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java162
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt61
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt43
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt247
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt27
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt163
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt114
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSFragment.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java133
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt3
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java39
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java253
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt21
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt26
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt)10
-rw-r--r--packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt)25
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java60
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java131
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java)30
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt161
-rw-r--r--packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java (renamed from packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java)17
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt109
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt510
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEvents.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEventsModule.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java)7
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java)727
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java)3
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java)5
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/OWNERS14
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelView.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java)49
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt106
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt)2
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt)44
-rw-r--r--packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt)22
-rw-r--r--packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java129
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java65
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java54
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java21
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java154
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java67
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java33
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt25
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt46
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt66
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java77
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt36
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java100
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java128
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt182
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt137
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt)8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt (renamed from packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt)6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java106
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt163
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java182
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java59
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java19
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridGroupManager.java23
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java96
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt22
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java9
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java104
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java114
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt20
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java97
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java47
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java200
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java587
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java800
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt34
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt304
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt227
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java18
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java3
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java38
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java16
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java17
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt92
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java1
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java136
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java99
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java13
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java45
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java102
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java7
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java2
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt158
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java35
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java8
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java71
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java27
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt32
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt14
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt30
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt42
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java11
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java15
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java6
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java132
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt24
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java150
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java266
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIInitializer.java (renamed from packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java)16
-rw-r--r--packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt2
-rw-r--r--packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt8
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java24
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserCreator.java85
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserCreator.kt81
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt17
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt89
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt12
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt79
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt38
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt57
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java26
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt29
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java274
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java46
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java4
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt41
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java10
-rw-r--r--packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java5
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java188
-rw-r--r--packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java149
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java17
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt43
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt36
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java188
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java157
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt38
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt)7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt (renamed from packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt)131
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt311
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt)29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt84
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java64
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java111
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java79
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java45
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt150
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt90
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt107
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java56
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java50
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java222
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java120
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java40
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java205
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java270
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt112
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt110
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java22
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt29
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java59
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt153
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt328
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt85
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt75
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt659
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt)80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java)134
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt)2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java)34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt284
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java)5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt151
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt)99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt)11
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt44
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt232
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt77
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt87
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java153
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java35
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java74
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java26
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java99
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt68
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java21
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java83
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java91
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java118
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java7
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java89
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt80
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java8
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt16
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt12
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt197
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java130
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt95
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt98
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java345
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java299
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java10
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt175
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java71
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java72
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt6
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java9
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt205
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt281
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt1
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java14
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt171
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java)137
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java34
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java31
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java86
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt250
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt5
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java32
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java23
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt144
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt20
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt54
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt166
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt27
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt25
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt2
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt73
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt4
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java (renamed from packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java)15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java145
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java24
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java19
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java416
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java1401
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java13
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java3
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java48
-rw-r--r--packages/SystemUI/unfold/Android.bp39
-rw-r--r--packages/SystemUI/unfold/AndroidManifest.xml (renamed from libs/WindowManager/Shell/res/color/unfold_transition_background.xml)13
-rw-r--r--packages/SystemUI/unfold/lint-baseline.xml3
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt)18
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt)4
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt)15
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt)4
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt56
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt54
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt41
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt)1
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt25
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt25
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt)0
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt)4
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt)91
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt26
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt)7
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt27
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt33
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt)14
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt)2
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt)14
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt20
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt22
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt)14
-rw-r--r--packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt (renamed from packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt)0
-rw-r--r--proto/src/system_messages.proto8
-rw-r--r--services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java26
-rw-r--r--services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java13
-rw-r--r--services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java21
-rw-r--r--services/companion/java/com/android/server/companion/AssociationStoreImpl.java16
-rw-r--r--services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java366
-rw-r--r--services/companion/java/com/android/server/companion/PackageUtils.java24
-rw-r--r--services/companion/java/com/android/server/companion/PersistentDataStore.java24
-rw-r--r--services/companion/java/com/android/server/companion/RolesUtils.java2
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java50
-rw-r--r--services/core/java/com/android/server/am/BroadcastRecord.java3
-rw-r--r--services/core/java/com/android/server/am/ContentProviderHelper.java20
-rw-r--r--services/core/java/com/android/server/am/OomAdjuster.java16
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java18
-rw-r--r--services/core/java/com/android/server/am/PendingStartActivityUids.java6
-rw-r--r--services/core/java/com/android/server/am/ProcessList.java4
-rw-r--r--services/core/java/com/android/server/app/GameManagerService.java184
-rw-r--r--services/core/java/com/android/server/audio/AudioService.java62
-rw-r--r--services/core/java/com/android/server/audio/SpatializerHelper.java351
-rw-r--r--services/core/java/com/android/server/biometrics/log/BiometricLogger.java30
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java5
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java7
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java43
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java11
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java (renamed from libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java)14
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java2
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java199
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java3
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java23
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java10
-rw-r--r--services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java3
-rw-r--r--services/core/java/com/android/server/clipboard/ClipboardService.java177
-rw-r--r--services/core/java/com/android/server/devicestate/DeviceStateManagerService.java23
-rw-r--r--services/core/java/com/android/server/display/BrightnessThrottler.java205
-rw-r--r--services/core/java/com/android/server/display/DisplayDeviceConfig.java62
-rw-r--r--services/core/java/com/android/server/display/DisplayManagerService.java32
-rw-r--r--services/core/java/com/android/server/display/DisplayPowerController.java4
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplay.java6
-rw-r--r--services/core/java/com/android/server/display/LogicalDisplayMapper.java120
-rw-r--r--services/core/java/com/android/server/display/color/ColorDisplayService.java2
-rw-r--r--services/core/java/com/android/server/dreams/DreamManagerService.java20
-rw-r--r--services/core/java/com/android/server/dreams/DreamShellCommand.java92
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java10
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java5
-rw-r--r--services/core/java/com/android/server/location/contexthub/ContextHubService.java4
-rw-r--r--services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java12
-rw-r--r--services/core/java/com/android/server/notification/ManagedServices.java19
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryDatabase.java97
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java2
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryJobService.java81
-rw-r--r--services/core/java/com/android/server/notification/NotificationHistoryManager.java24
-rw-r--r--services/core/java/com/android/server/notification/NotificationManagerInternal.java2
-rwxr-xr-xservices/core/java/com/android/server/notification/NotificationManagerService.java48
-rw-r--r--services/core/java/com/android/server/notification/PermissionHelper.java8
-rw-r--r--services/core/java/com/android/server/notification/SnoozeHelper.java391
-rw-r--r--services/core/java/com/android/server/notification/ZenModeHelper.java56
-rw-r--r--services/core/java/com/android/server/pm/InitAppsHelper.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java5
-rw-r--r--services/core/java/com/android/server/pm/ScanPackageUtils.java3
-rw-r--r--services/core/java/com/android/server/pm/SettingBase.java11
-rw-r--r--services/core/java/com/android/server/pm/Settings.java25
-rw-r--r--services/core/java/com/android/server/pm/ShortcutLauncher.java6
-rw-r--r--services/core/java/com/android/server/pm/UserManagerService.java42
-rw-r--r--services/core/java/com/android/server/policy/AppOpsPolicy.java5
-rw-r--r--services/core/java/com/android/server/policy/PhoneWindowManager.java26
-rw-r--r--services/core/java/com/android/server/policy/SideFpsEventHandler.java237
-rw-r--r--services/core/java/com/android/server/policy/WindowManagerPolicy.java20
-rw-r--r--services/core/java/com/android/server/power/PowerManagerService.java7
-rw-r--r--services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java27
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java4
-rw-r--r--services/core/java/com/android/server/statusbar/StatusBarManagerService.java16
-rw-r--r--services/core/java/com/android/server/vibrator/Vibration.java3
-rw-r--r--services/core/java/com/android/server/vibrator/VibratorManagerService.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java55
-rw-r--r--services/core/java/com/android/server/wm/ActivityMetricsLogger.java88
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java375
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecordInputSink.java9
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartController.java50
-rw-r--r--services/core/java/com/android/server/wm/ActivityStartInterceptor.java52
-rw-r--r--services/core/java/com/android/server/wm/ActivityStarter.java115
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java3
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskManagerService.java166
-rw-r--r--services/core/java/com/android/server/wm/ActivityTaskSupervisor.java79
-rw-r--r--services/core/java/com/android/server/wm/AnrController.java2
-rw-r--r--services/core/java/com/android/server/wm/AppTransition.java206
-rw-r--r--services/core/java/com/android/server/wm/AppTransitionController.java25
-rw-r--r--services/core/java/com/android/server/wm/AsyncRotationController.java84
-rw-r--r--services/core/java/com/android/server/wm/BLASTSyncEngine.java6
-rw-r--r--services/core/java/com/android/server/wm/BackNavigationController.java12
-rw-r--r--services/core/java/com/android/server/wm/ConfigurationContainer.java8
-rw-r--r--services/core/java/com/android/server/wm/ContentRecordingController.java1
-rw-r--r--services/core/java/com/android/server/wm/DisplayContent.java179
-rw-r--r--services/core/java/com/android/server/wm/DisplayFrames.java17
-rw-r--r--services/core/java/com/android/server/wm/DisplayPolicy.java265
-rw-r--r--services/core/java/com/android/server/wm/DisplayRotation.java114
-rw-r--r--services/core/java/com/android/server/wm/DragState.java16
-rw-r--r--services/core/java/com/android/server/wm/EventLogTags.logtags2
-rw-r--r--services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java11
-rw-r--r--services/core/java/com/android/server/wm/InputConsumerImpl.java5
-rw-r--r--services/core/java/com/android/server/wm/InputMonitor.java32
-rw-r--r--services/core/java/com/android/server/wm/InsetsPolicy.java75
-rw-r--r--services/core/java/com/android/server/wm/InsetsSourceProvider.java103
-rw-r--r--services/core/java/com/android/server/wm/KeyguardController.java51
-rw-r--r--services/core/java/com/android/server/wm/Letterbox.java17
-rw-r--r--services/core/java/com/android/server/wm/LetterboxConfiguration.java568
-rw-r--r--services/core/java/com/android/server/wm/LetterboxUiController.java234
-rw-r--r--services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java55
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimation.java30
-rw-r--r--services/core/java/com/android/server/wm/RecentsAnimationController.java55
-rw-r--r--services/core/java/com/android/server/wm/RemoteAnimationController.java61
-rw-r--r--services/core/java/com/android/server/wm/RemoteDisplayChangeController.java171
-rw-r--r--services/core/java/com/android/server/wm/RootWindowContainer.java77
-rw-r--r--services/core/java/com/android/server/wm/SafeActivityOptions.java11
-rw-r--r--services/core/java/com/android/server/wm/ScreenRotationAnimation.java9
-rw-r--r--services/core/java/com/android/server/wm/Session.java46
-rw-r--r--services/core/java/com/android/server/wm/StartingSurfaceController.java5
-rw-r--r--services/core/java/com/android/server/wm/Task.java364
-rw-r--r--services/core/java/com/android/server/wm/TaskDisplayArea.java84
-rw-r--r--services/core/java/com/android/server/wm/TaskFragment.java102
-rw-r--r--services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java37
-rw-r--r--services/core/java/com/android/server/wm/TaskOrganizerController.java371
-rw-r--r--services/core/java/com/android/server/wm/TaskPositioner.java5
-rw-r--r--services/core/java/com/android/server/wm/TaskSnapshotController.java3
-rw-r--r--services/core/java/com/android/server/wm/Transition.java385
-rw-r--r--services/core/java/com/android/server/wm/TransitionController.java126
-rw-r--r--services/core/java/com/android/server/wm/TransitionTracer.java243
-rw-r--r--services/core/java/com/android/server/wm/UnknownAppVisibilityController.java4
-rw-r--r--services/core/java/com/android/server/wm/WallpaperController.java23
-rw-r--r--services/core/java/com/android/server/wm/WindowContainer.java78
-rw-r--r--services/core/java/com/android/server/wm/WindowContainerThumbnail.java3
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java245
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerShellCommand.java740
-rw-r--r--services/core/java/com/android/server/wm/WindowOrganizerController.java291
-rw-r--r--services/core/java/com/android/server/wm/WindowOrientationListener.java6
-rw-r--r--services/core/java/com/android/server/wm/WindowProcessController.java116
-rw-r--r--services/core/java/com/android/server/wm/WindowState.java185
-rw-r--r--services/core/java/com/android/server/wm/WindowSurfaceController.java14
-rw-r--r--services/core/java/com/android/server/wm/WindowToken.java41
-rw-r--r--services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java4
-rw-r--r--services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java13
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java28
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java160
-rw-r--r--services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java67
-rw-r--r--services/tests/servicestests/AndroidTest.xml1
-rw-r--r--services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java42
-rw-r--r--services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java59
-rw-r--r--services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java2
-rw-r--r--services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java58
-rw-r--r--services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java181
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java182
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java9
-rw-r--r--services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java6
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java89
-rw-r--r--services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java46
-rw-r--r--services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java12
-rw-r--r--services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java124
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java1
-rw-r--r--services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java15
-rw-r--r--services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java86
-rw-r--r--services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java2
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java150
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java21
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java108
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java37
-rwxr-xr-xservices/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java87
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java17
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java108
-rw-r--r--services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java109
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java22
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java26
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java10
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java33
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java166
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java21
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java3
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java11
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java7
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java18
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java111
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java19
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java280
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java96
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java46
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java12
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TaskTests.java86
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java4
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/TransitionTests.java136
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java98
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java102
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java146
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java76
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java75
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java40
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java20
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java51
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java73
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsDatabase.java37
-rw-r--r--services/usage/java/com/android/server/usage/UsageStatsService.java3
-rw-r--r--services/usage/java/com/android/server/usage/UserUsageStatsService.java5
-rw-r--r--services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java65
-rw-r--r--telephony/common/com/android/internal/telephony/SmsApplication.java31
-rw-r--r--telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java8
-rw-r--r--telephony/java/android/telephony/UiccSlotInfo.java2
-rw-r--r--telephony/java/android/telephony/data/ApnSetting.java9
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt46
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt4
-rw-r--r--tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt34
-rw-r--r--tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml1
-rw-r--r--tests/WindowInsetsTests/res/layout/controller_activity.xml2
-rw-r--r--tests/WindowInsetsTests/res/values/strings.xml2
-rw-r--r--tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java46
-rw-r--r--tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java10
1611 files changed, 57853 insertions, 35162 deletions
diff --git a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
index 452bb0ab5909..06207215b7be 100644
--- a/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
+++ b/apct-tests/perftests/windowmanager/src/android/wm/WindowAddRemovePerfTest.java
@@ -19,6 +19,7 @@ package android.wm;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
+import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
import android.perftests.utils.ManualBenchmarkState;
@@ -86,6 +87,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
final InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
final InsetsState mOutInsetsState = new InsetsState();
final InsetsSourceControl[] mOutControls = new InsetsSourceControl[0];
+ final Rect mOutAttachedFrame = new Rect();
TestWindow() {
mLayoutParams.setTitle(TestWindow.class.getName());
@@ -104,7 +106,7 @@ public class WindowAddRemovePerfTest extends WindowManagerPerfTestBase
long startTime = SystemClock.elapsedRealtimeNanos();
session.addToDisplay(this, mLayoutParams, View.VISIBLE,
Display.DEFAULT_DISPLAY, mRequestedVisibilities, inputChannel,
- mOutInsetsState, mOutControls);
+ mOutInsetsState, mOutControls, mOutAttachedFrame);
final long elapsedTimeNsOfAdd = SystemClock.elapsedRealtimeNanos() - startTime;
state.addExtraResult("add", elapsedTimeNsOfAdd);
diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
index c43c832992cf..9b64edf53d8c 100644
--- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
+++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java
@@ -164,6 +164,13 @@ public interface AppStandbyInternal {
@ElapsedRealtimeLong long elapsedRealtime);
/**
+ * Puts the list of apps in the {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RARE}
+ * bucket.
+ * @param restoredApps the list of restored apps
+ */
+ void restoreAppsToRare(@NonNull Set<String> restoredApps, int userId);
+
+ /**
* Put the specified app in the
* {@link android.app.usage.UsageStatsManager#STANDBY_BUCKET_RESTRICTED}
* bucket. If it has been used by the user recently, the restriction will delayed until an
diff --git a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
index d6b246a9e2e3..c94cc8ff2612 100644
--- a/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/alarm/AlarmManagerService.java
@@ -335,12 +335,18 @@ public class AlarmManagerService extends SystemService {
"REORDER_ALARMS_FOR_TARE",
});
- BroadcastOptions mOptsWithFgs = BroadcastOptions.makeBasic();
- BroadcastOptions mOptsWithFgsForAlarmClock = BroadcastOptions.makeBasic();
- BroadcastOptions mOptsWithoutFgs = BroadcastOptions.makeBasic();
- BroadcastOptions mOptsTimeBroadcast = BroadcastOptions.makeBasic();
+ BroadcastOptions mOptsWithFgs = makeBasicAlarmBroadcastOptions();
+ BroadcastOptions mOptsWithFgsForAlarmClock = makeBasicAlarmBroadcastOptions();
+ BroadcastOptions mOptsWithoutFgs = makeBasicAlarmBroadcastOptions();
+ BroadcastOptions mOptsTimeBroadcast = makeBasicAlarmBroadcastOptions();
ActivityOptions mActivityOptsRestrictBal = ActivityOptions.makeBasic();
- BroadcastOptions mBroadcastOptsRestrictBal = BroadcastOptions.makeBasic();
+ BroadcastOptions mBroadcastOptsRestrictBal = makeBasicAlarmBroadcastOptions();
+
+ private static BroadcastOptions makeBasicAlarmBroadcastOptions() {
+ final BroadcastOptions b = BroadcastOptions.makeBasic();
+ b.setAlarmBroadcast(true);
+ return b;
+ }
// TODO(b/172085676): Move inside alarm store.
private final SparseArray<AlarmManager.AlarmClockInfo> mNextAlarmClockForUser =
@@ -2889,7 +2895,11 @@ public class AlarmManagerService extends SystemService {
} else {
needsPermission = false;
lowerQuota = allowWhileIdle;
- idleOptions = allowWhileIdle ? mOptsWithFgs.toBundle() : null;
+ idleOptions = (allowWhileIdle || (alarmClock != null))
+ // This avoids exceptions on existing alarms when the app upgrades to
+ // target S. Note that FGS from pre-S apps isn't restricted anyway.
+ ? mOptsWithFgs.toBundle()
+ : null;
if (exact) {
exactAllowReason = EXACT_ALLOW_REASON_COMPAT;
}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index 63781ae334d2..5ef6855151cc 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -34,6 +34,7 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.JobSchedulerBackgroundThread;
import com.android.server.LocalServices;
import com.android.server.job.JobSchedulerService;
@@ -100,6 +101,10 @@ public final class BatteryController extends RestrictingController {
@Override
@GuardedBy("mLock")
public void prepareForExecutionLocked(JobStatus jobStatus) {
+ if (!jobStatus.hasPowerConstraint()) {
+ // Ignore all jobs the controller wouldn't be tracking.
+ return;
+ }
if (DEBUG) {
Slog.d(TAG, "Prepping for " + jobStatus.toShortString());
}
@@ -259,6 +264,16 @@ public final class BatteryController extends RestrictingController {
}
}
+ @VisibleForTesting
+ ArraySet<JobStatus> getTrackedJobs() {
+ return mTrackedTasks;
+ }
+
+ @VisibleForTesting
+ ArraySet<JobStatus> getTopStartedJobs() {
+ return mTopStartedJobs;
+ }
+
@Override
public void dumpControllerStateLocked(IndentingPrintWriter pw,
Predicate<JobStatus> predicate) {
diff --git a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
index 9e3e3553c125..5d9f3357125a 100644
--- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
+++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java
@@ -23,6 +23,7 @@ import static android.app.usage.UsageStatsManager.REASON_MAIN_MASK;
import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
+import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_RESTORED;
import static android.app.usage.UsageStatsManager.REASON_SUB_DEFAULT_APP_UPDATE;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_SYSTEM_FLAG_BUGGY;
import static android.app.usage.UsageStatsManager.REASON_SUB_FORCED_USER_FLAG_INTERACTION;
@@ -1605,6 +1606,26 @@ public class AppStandbyController
}
@Override
+ public void restoreAppsToRare(Set<String> restoredApps, int userId) {
+ final int reason = REASON_MAIN_DEFAULT | REASON_SUB_DEFAULT_APP_RESTORED;
+ final long nowElapsed = mInjector.elapsedRealtime();
+ for (String packageName : restoredApps) {
+ // If the package is not installed, don't allow the bucket to be set.
+ if (!mInjector.isPackageInstalled(packageName, 0, userId)) {
+ Slog.e(TAG, "Tried to restore bucket for uninstalled app: " + packageName);
+ continue;
+ }
+
+ final int standbyBucket = getAppStandbyBucket(packageName, userId, nowElapsed, false);
+ // Only update the standby bucket to RARE if the app is still in the NEVER bucket.
+ if (standbyBucket == STANDBY_BUCKET_NEVER) {
+ setAppStandbyBucket(packageName, userId, STANDBY_BUCKET_RARE, reason,
+ nowElapsed, false);
+ }
+ }
+ }
+
+ @Override
public void setAppStandbyBucket(@NonNull String packageName, int bucket, int userId,
int callingUid, int callingPid) {
setAppStandbyBuckets(
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 92532280bf4c..94e2add4cb29 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -489,8 +489,6 @@ package android.app {
field public static final int WINDOWING_MODE_FULLSCREEN = 1; // 0x1
field public static final int WINDOWING_MODE_MULTI_WINDOW = 6; // 0x6
field public static final int WINDOWING_MODE_PINNED = 2; // 0x2
- field public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3; // 0x3
- field public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4; // 0x4
field public static final int WINDOWING_MODE_UNDEFINED = 0; // 0x0
}
@@ -1864,7 +1862,6 @@ package android.os {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS, android.Manifest.permission.QUERY_USERS}) public String getUserType();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public java.util.List<android.content.pm.UserInfo> getUsers(boolean, boolean, boolean);
method @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public boolean hasBaseUserRestriction(@NonNull String, @NonNull android.os.UserHandle);
- method public static boolean isGuestUserEphemeral();
method public static boolean isSplitSystemUser();
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.content.pm.UserInfo preCreateUser(@NonNull String) throws android.os.UserManager.UserOperationException;
}
@@ -3354,7 +3351,8 @@ package android.window {
method @NonNull public java.util.concurrent.Executor getExecutor();
method @NonNull public android.window.TaskFragmentOrganizerToken getOrganizerToken();
method public void onTaskFragmentAppeared(@NonNull android.window.TaskFragmentInfo);
- method public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+ method @Deprecated public void onTaskFragmentError(@NonNull android.os.IBinder, @NonNull Throwable);
+ method public void onTaskFragmentError(@NonNull android.os.IBinder, @Nullable android.window.TaskFragmentInfo, int, @NonNull Throwable);
method public void onTaskFragmentInfoChanged(@NonNull android.window.TaskFragmentInfo);
method public void onTaskFragmentParentInfoChanged(@NonNull android.os.IBinder, @NonNull android.content.res.Configuration);
method public void onTaskFragmentVanished(@NonNull android.window.TaskFragmentInfo);
@@ -3403,7 +3401,8 @@ package android.window {
method @NonNull public android.window.WindowContainerTransaction reparentTasks(@Nullable android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, @Nullable int[], @Nullable int[], boolean);
method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
- method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean);
+ method @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken);
+ method @Deprecated @NonNull public android.window.WindowContainerTransaction setAdjacentRoots(@NonNull android.window.WindowContainerToken, @NonNull android.window.WindowContainerToken, boolean);
method @NonNull public android.window.WindowContainerTransaction setAdjacentTaskFragments(@NonNull android.os.IBinder, @Nullable android.os.IBinder, @Nullable android.window.WindowContainerTransaction.TaskFragmentAdjacentParams);
method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 7141259d7dce..90c37d1999e1 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6433,6 +6433,20 @@ public class Activity extends ContextThemeWrapper
}
/**
+ * Ensures the activity's result is immediately returned to the caller when {@link #finish()}
+ * is invoked
+ *
+ * <p>Should be invoked alongside {@link #setResult(int, Intent)}, so the provided results are
+ * in place before finishing. Must only be invoked during MediaProjection setup.
+ *
+ * @hide
+ */
+ @RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ public final void setForceSendResultForMediaProjection() {
+ ActivityClient.getInstance().setForceSendResultForMediaProjection(mToken);
+ }
+
+ /**
* Call this to set the result that your activity will return to its
* caller.
*
diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java
index 73678d9f2dda..482f456b5d83 100644
--- a/core/java/android/app/ActivityClient.java
+++ b/core/java/android/app/ActivityClient.java
@@ -17,6 +17,7 @@
package android.app;
import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
@@ -184,6 +185,15 @@ public class ActivityClient {
}
}
+ @RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)
+ void setForceSendResultForMediaProjection(IBinder token) {
+ try {
+ getActivityClientController().setForceSendResultForMediaProjection(token);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
public boolean isTopOfTask(IBinder token) {
try {
return getActivityClientController().isTopOfTask(token);
@@ -217,6 +227,18 @@ public class ActivityClient {
}
/**
+ * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not
+ * found.
+ */
+ public int getTaskWindowingMode(IBinder activityToken) {
+ try {
+ return getActivityClientController().getTaskWindowingMode(activityToken);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Returns the non-finishing activity token below in the same task if it belongs to the same
* process.
*/
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 5d1d225f4d2d..e25e374122cf 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -2210,7 +2210,6 @@ public class ActivityManager {
pw.print(((baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0));
pw.print(" activityType="); pw.print(activityTypeToString(getActivityType()));
pw.print(" windowingMode="); pw.print(windowingModeToString(getWindowingMode()));
- pw.print(" supportsSplitScreenMultiWindow="); pw.print(supportsSplitScreenMultiWindow);
pw.print(" supportsMultiWindow=");
pw.println(supportsMultiWindow);
if (taskDescription != null) {
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 00ab559697f4..3f769a161a8c 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1159,7 +1159,6 @@ public class ActivityOptions extends ComponentOptions {
opts.mLaunchIntoPipParams = new PictureInPictureParams.Builder(pictureInPictureParams)
.setIsLaunchIntoPip(true)
.build();
- opts.mLaunchBounds = new Rect(pictureInPictureParams.getSourceRectHint());
return opts;
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index ae8809d8ac29..f384fa9e6a0b 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3423,26 +3423,12 @@ public final class ActivityThread extends ClientTransactionHandler
}
}
- /**
- * Returns {@code true} if the {@link android.app.ActivityManager.ProcessState} of the current
- * process is cached.
- */
- @Override
- @VisibleForTesting
- public boolean isCachedProcessState() {
- synchronized (mAppThread) {
- return mLastProcessState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
- }
- }
-
@Override
public void updateProcessState(int processState, boolean fromIpc) {
- final boolean wasCached;
synchronized (mAppThread) {
if (mLastProcessState == processState) {
return;
}
- wasCached = isCachedProcessState();
mLastProcessState = processState;
updateVmProcessState(processState);
if (localLOGV) {
@@ -3450,22 +3436,6 @@ public final class ActivityThread extends ClientTransactionHandler
+ (fromIpc ? " (from ipc" : ""));
}
}
-
- // Handle the pending configuration if the process state is changed from cached to
- // non-cached. Except the case where there is a launching activity because the
- // LaunchActivityItem will handle it.
- if (wasCached && !isCachedProcessState() && mNumLaunchingActivities.get() == 0) {
- final Configuration pendingConfig =
- mConfigurationController.getPendingConfiguration(false /* clearPending */);
- if (pendingConfig == null) {
- return;
- }
- if (Looper.myLooper() == mH.getLooper()) {
- handleConfigurationChanged(pendingConfig);
- } else {
- sendMessage(H.CONFIGURATION_CHANGED, pendingConfig);
- }
- }
}
/** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
@@ -5893,20 +5863,20 @@ public final class ActivityThread extends ClientTransactionHandler
final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
displayId);
- final Configuration currentConfig = activity.mCurrentConfig;
- final int diff = currentConfig.diffPublicOnly(newConfig);
- final boolean hasPublicConfigChange = diff != 0;
+ final Configuration currentResConfig = activity.getResources().getConfiguration();
+ final int diff = currentResConfig.diffPublicOnly(newConfig);
+ final boolean hasPublicResConfigChange = diff != 0;
final ActivityClientRecord r = getActivityClient(activityToken);
// TODO(b/173090263): Use diff instead after the improvement of AssetManager and
// ResourcesImpl constructions.
- final boolean shouldUpdateResources = hasPublicConfigChange
- || shouldUpdateResources(activityToken, currentConfig, newConfig, amOverrideConfig,
- movedToDifferentDisplay, hasPublicConfigChange);
- final boolean shouldReportChange = shouldReportChange(diff, currentConfig, newConfig,
+ final boolean shouldUpdateResources = hasPublicResConfigChange
+ || shouldUpdateResources(activityToken, currentResConfig, newConfig,
+ amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
+ final boolean shouldReportChange = shouldReportChange(activity.mCurrentConfig, newConfig,
r != null ? r.mSizeConfigurations : null,
activity.mActivityInfo.getRealConfigChanged());
// Nothing significant, don't proceed with updating and reporting.
- if (!shouldUpdateResources) {
+ if (!shouldUpdateResources && !shouldReportChange) {
return null;
}
@@ -5926,9 +5896,6 @@ public final class ActivityThread extends ClientTransactionHandler
amOverrideConfig, contextThemeWrapperOverrideConfig);
mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig, displayId);
- activity.mConfigChangeFlags = 0;
- activity.mCurrentConfig = new Configuration(newConfig);
-
// Apply the ContextThemeWrapper override if necessary.
// NOTE: Make sure the configurations are not modified, as they are treated as immutable
// in many places.
@@ -5939,8 +5906,10 @@ public final class ActivityThread extends ClientTransactionHandler
activity.dispatchMovedToDisplay(displayId, configToReport);
}
+ activity.mConfigChangeFlags = 0;
if (shouldReportChange) {
activity.mCalled = false;
+ activity.mCurrentConfig = new Configuration(newConfig);
activity.onConfigurationChanged(configToReport);
if (!activity.mCalled) {
throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
@@ -5955,8 +5924,6 @@ public final class ActivityThread extends ClientTransactionHandler
* Returns {@code true} if {@link Activity#onConfigurationChanged(Configuration)} should be
* dispatched.
*
- * @param publicDiff Usually computed by {@link Configuration#diffPublicOnly(Configuration)}.
- * This parameter is to prevent we compute it again.
* @param currentConfig The current configuration cached in {@link Activity#mCurrentConfig}.
* It is {@code null} before the first config update from the server side.
* @param newConfig The updated {@link Configuration}
@@ -5965,9 +5932,10 @@ public final class ActivityThread extends ClientTransactionHandler
* @return {@code true} if the config change should be reported to the Activity
*/
@VisibleForTesting
- public static boolean shouldReportChange(int publicDiff, @Nullable Configuration currentConfig,
+ public static boolean shouldReportChange(@Nullable Configuration currentConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets sizeBuckets,
int handledConfigChanges) {
+ final int publicDiff = currentConfig.diffPublicOnly(newConfig);
// Don't report the change if there's no public diff between current and new config.
if (publicDiff == 0) {
return false;
diff --git a/core/java/android/app/ActivityThreadInternal.java b/core/java/android/app/ActivityThreadInternal.java
index b9ad5c337813..72506b9fcdbb 100644
--- a/core/java/android/app/ActivityThreadInternal.java
+++ b/core/java/android/app/ActivityThreadInternal.java
@@ -32,8 +32,6 @@ interface ActivityThreadInternal {
boolean isInDensityCompatMode();
- boolean isCachedProcessState();
-
Application getApplication();
ArrayList<ComponentCallbacks2> collectComponentCallbacks(boolean includeUiContexts);
diff --git a/core/java/android/app/ActivityTransitionState.java b/core/java/android/app/ActivityTransitionState.java
index 877e7d3b3bf7..57dacd024ba1 100644
--- a/core/java/android/app/ActivityTransitionState.java
+++ b/core/java/android/app/ActivityTransitionState.java
@@ -263,6 +263,11 @@ class ActivityTransitionState {
// After orientation change, the onResume can come in before the top Activity has
// left, so if the Activity is not top, wait a second for the top Activity to exit.
if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> {
+ mEnterTransitionCoordinator = null;
+ });
+ }
restoreExitedViews();
restoreReenteringViews();
} else {
@@ -271,6 +276,11 @@ class ActivityTransitionState {
public void run() {
if (mEnterTransitionCoordinator == null ||
mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
+ if (mEnterTransitionCoordinator != null) {
+ mEnterTransitionCoordinator.runAfterTransitionsComplete(() -> {
+ mEnterTransitionCoordinator = null;
+ });
+ }
restoreExitedViews();
restoreReenteringViews();
} else if (mEnterTransitionCoordinator.isReturning()) {
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 56f8760f6059..f0e14483d98a 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -53,6 +53,7 @@ public class BroadcastOptions extends ComponentOptions {
private String[] mRequireNoneOfPermissions;
private long mRequireCompatChangeId = CHANGE_INVALID;
private boolean mRequireCompatChangeEnabled = true;
+ private boolean mIsAlarmBroadcast = false;
private long mIdForResponseEvent;
/**
@@ -149,6 +150,13 @@ public class BroadcastOptions extends ComponentOptions {
"android:broadcast.requireCompatChangeEnabled";
/**
+ * Corresponds to {@link #setAlarmBroadcast(boolean)}
+ * @hide
+ */
+ public static final String KEY_ALARM_BROADCAST =
+ "android:broadcast.is_alarm";
+
+ /**
* @hide
* @deprecated Use {@link android.os.PowerExemptionManager#
* TEMPORARY_ALLOW_LIST_TYPE_FOREGROUND_SERVICE_ALLOWED} instead.
@@ -207,6 +215,7 @@ public class BroadcastOptions extends ComponentOptions {
mRequireCompatChangeId = opts.getLong(KEY_REQUIRE_COMPAT_CHANGE_ID, CHANGE_INVALID);
mRequireCompatChangeEnabled = opts.getBoolean(KEY_REQUIRE_COMPAT_CHANGE_ENABLED, true);
mIdForResponseEvent = opts.getLong(KEY_ID_FOR_RESPONSE_EVENT);
+ mIsAlarmBroadcast = opts.getBoolean(KEY_ALARM_BROADCAST, false);
}
/**
@@ -498,6 +507,27 @@ public class BroadcastOptions extends ComponentOptions {
mRequireCompatChangeEnabled = true;
}
+ /**
+ * When set, this broadcast will be understood as having originated from an
+ * alarm going off. Only the OS itself can use this option; uses by other
+ * senders will be ignored.
+ * @hide
+ *
+ * @param senderIsAlarm Whether the broadcast is alarm-triggered.
+ */
+ public void setAlarmBroadcast(boolean senderIsAlarm) {
+ mIsAlarmBroadcast = senderIsAlarm;
+ }
+
+ /**
+ * Did this broadcast originate from an alarm triggering?
+ * @return true if this broadcast is an alarm message, false otherwise
+ * @hide
+ */
+ public boolean isAlarmBroadcast() {
+ return mIsAlarmBroadcast;
+ }
+
/** {@hide} */
public long getRequireCompatChangeId() {
return mRequireCompatChangeId;
@@ -560,6 +590,9 @@ public class BroadcastOptions extends ComponentOptions {
b.putInt(KEY_TEMPORARY_APP_ALLOWLIST_REASON_CODE, mTemporaryAppAllowlistReasonCode);
b.putString(KEY_TEMPORARY_APP_ALLOWLIST_REASON, mTemporaryAppAllowlistReason);
}
+ if (mIsAlarmBroadcast) {
+ b.putBoolean(KEY_ALARM_BROADCAST, true);
+ }
if (mMinManifestReceiverApiLevel != 0) {
b.putInt(KEY_MIN_MANIFEST_RECEIVER_API_LEVEL, mMinManifestReceiverApiLevel);
}
diff --git a/core/java/android/app/ConfigurationController.java b/core/java/android/app/ConfigurationController.java
index 1a77b65c8ef6..18dc1ce18baf 100644
--- a/core/java/android/app/ConfigurationController.java
+++ b/core/java/android/app/ConfigurationController.java
@@ -124,12 +124,6 @@ class ConfigurationController {
* @param config The new configuration.
*/
void handleConfigurationChanged(@NonNull Configuration config) {
- if (mActivityThread.isCachedProcessState()) {
- updatePendingConfiguration(config);
- // If the process is in a cached state, delay the handling until the process is no
- // longer cached.
- return;
- }
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
handleConfigurationChanged(config, null /* compat */);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl
index 0138186974a6..f5e5cda9c639 100644
--- a/core/java/android/app/IActivityClientController.aidl
+++ b/core/java/android/app/IActivityClientController.aidl
@@ -67,11 +67,18 @@ interface IActivityClientController {
boolean finishActivityAffinity(in IBinder token);
/** Finish all activities that were started for result from the specified activity. */
void finishSubActivity(in IBinder token, in String resultWho, int requestCode);
+ /**
+ * Indicates that when the activity finsihes, the result should be immediately sent to the
+ * originating activity. Must only be invoked during MediaProjection setup.
+ */
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_MEDIA_PROJECTION)")
+ void setForceSendResultForMediaProjection(in IBinder token);
boolean isTopOfTask(in IBinder token);
boolean willActivityBeVisible(in IBinder token);
int getDisplayId(in IBinder activityToken);
int getTaskForActivity(in IBinder token, in boolean onlyRoot);
+ int getTaskWindowingMode(in IBinder activityToken);
IBinder getActivityTokenBelow(IBinder token);
ComponentName getCallingActivity(in IBinder token);
String getCallingPackage(in IBinder token);
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index fe75dd302beb..dc6825c85705 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -263,9 +263,12 @@ interface IActivityTaskManager {
* @param taskId the id of the task to retrieve the sAutoapshots for
* @param isLowResolution if set, if the snapshot needs to be loaded from disk, this will load
* a reduced resolution of it, which is much faster
+ * @param takeSnapshotIfNeeded if set, call {@link #takeTaskSnapshot} to trigger the snapshot
+ if no cache exists.
* @return a graphic buffer representing a screenshot of a task
*/
- android.window.TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution);
+ android.window.TaskSnapshot getTaskSnapshot(
+ int taskId, boolean isLowResolution, boolean takeSnapshotIfNeeded);
/**
* @param taskId the id of the task to take a snapshot of
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 8984c4292023..556058b567f9 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -783,6 +783,17 @@ public class Instrumentation {
return null;
}
+ /**
+ * This is called after starting an Activity and provides the result code that defined in
+ * {@link ActivityManager}, like {@link ActivityManager#START_SUCCESS}.
+ *
+ * @param result the result code that returns after starting an Activity.
+ * @param bOptions the bundle generated from {@link ActivityOptions} that originally
+ * being used to start the Activity.
+ * @hide
+ */
+ public void onStartActivityResult(int result, @NonNull Bundle bOptions) {}
+
final boolean match(Context who,
Activity activity,
Intent intent) {
@@ -1344,6 +1355,28 @@ public class Instrumentation {
return apk.getAppFactory();
}
+ /**
+ * This should be called before {@link #checkStartActivityResult(int, Object)}, because
+ * exceptions might be thrown while checking the results.
+ */
+ private void notifyStartActivityResult(int result, @Nullable Bundle options) {
+ if (mActivityMonitors == null) {
+ return;
+ }
+ synchronized (mSync) {
+ final int size = mActivityMonitors.size();
+ for (int i = 0; i < size; i++) {
+ final ActivityMonitor am = mActivityMonitors.get(i);
+ if (am.ignoreMatchingSpecificIntents()) {
+ if (options == null) {
+ options = ActivityOptions.makeBasic().toBundle();
+ }
+ am.onStartActivityResult(result, options);
+ }
+ }
+ }
+ }
+
private void prePerformCreate(Activity activity) {
if (mWaitingActivities != null) {
synchronized (mSync) {
@@ -1802,6 +1835,7 @@ public class Instrumentation {
who.getOpPackageName(), who.getAttributionTag(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()), token,
target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
+ notifyStartActivityResult(result, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
@@ -1876,6 +1910,7 @@ public class Instrumentation {
int result = ActivityTaskManager.getService().startActivities(whoThread,
who.getOpPackageName(), who.getAttributionTag(), intents, resolvedTypes,
token, options, userId);
+ notifyStartActivityResult(result, options);
checkStartActivityResult(result, intents[0]);
return result;
} catch (RemoteException e) {
@@ -1947,6 +1982,7 @@ public class Instrumentation {
who.getOpPackageName(), who.getAttributionTag(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()), token, target,
requestCode, 0, null, options);
+ notifyStartActivityResult(result, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
@@ -2017,6 +2053,7 @@ public class Instrumentation {
who.getOpPackageName(), who.getAttributionTag(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()), token, resultWho,
requestCode, 0, null, options, user.getIdentifier());
+ notifyStartActivityResult(result, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
@@ -2068,6 +2105,7 @@ public class Instrumentation {
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options,
ignoreTargetSecurity, userId);
+ notifyStartActivityResult(result, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
@@ -2115,6 +2153,7 @@ public class Instrumentation {
int result = appTask.startActivity(whoThread.asBinder(), who.getOpPackageName(),
who.getAttributionTag(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()), options);
+ notifyStartActivityResult(result, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 2a1883d5e0bb..d275c8336251 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -365,8 +365,8 @@ public class ResourcesManager {
@NonNull Configuration config) {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
config.densityDpi = dm.densityDpi;
- config.screenWidthDp = (int) (dm.widthPixels / dm.density);
- config.screenHeightDp = (int) (dm.heightPixels / dm.density);
+ config.screenWidthDp = (int) (dm.widthPixels / dm.density + 0.5f);
+ config.screenHeightDp = (int) (dm.heightPixels / dm.density + 0.5f);
int sl = Configuration.resetScreenLayout(config.screenLayout);
if (dm.widthPixels > dm.heightPixels) {
config.orientation = Configuration.ORIENTATION_LANDSCAPE;
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 7910f1a426c2..b09463e3074b 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -140,13 +140,6 @@ public class TaskInfo {
public LocusId mTopActivityLocusId;
/**
- * True if the task can go in the split-screen primary stack.
- * @hide
- */
- @UnsupportedAppUsage
- public boolean supportsSplitScreenMultiWindow;
-
- /**
* Whether this task supports multi windowing modes based on the device settings and the
* root activity resizability and configuration.
* @hide
@@ -499,7 +492,6 @@ public class TaskInfo {
lastActiveTime = source.readLong();
taskDescription = source.readTypedObject(ActivityManager.TaskDescription.CREATOR);
- supportsSplitScreenMultiWindow = source.readBoolean();
supportsMultiWindow = source.readBoolean();
resizeMode = source.readInt();
configuration.readFromParcel(source);
@@ -546,7 +538,6 @@ public class TaskInfo {
dest.writeLong(lastActiveTime);
dest.writeTypedObject(taskDescription, flags);
- dest.writeBoolean(supportsSplitScreenMultiWindow);
dest.writeBoolean(supportsMultiWindow);
dest.writeInt(resizeMode);
configuration.writeToParcel(dest, flags);
@@ -584,7 +575,6 @@ public class TaskInfo {
+ " realActivity=" + realActivity
+ " numActivities=" + numActivities
+ " lastActiveTime=" + lastActiveTime
- + " supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow
+ " supportsMultiWindow=" + supportsMultiWindow
+ " resizeMode=" + resizeMode
+ " isResizeable=" + isResizeable
diff --git a/core/java/android/app/WindowConfiguration.java b/core/java/android/app/WindowConfiguration.java
index e96a98697184..d0ea8d41d65c 100644
--- a/core/java/android/app/WindowConfiguration.java
+++ b/core/java/android/app/WindowConfiguration.java
@@ -104,19 +104,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
public static final int WINDOWING_MODE_FULLSCREEN = 1;
/** Always on-top (always visible). of other siblings in its parent container. */
public static final int WINDOWING_MODE_PINNED = 2;
- /** The primary container driving the screen to be in split-screen mode. */
- // TODO: Remove once split-screen is migrated to wm-shell.
- public static final int WINDOWING_MODE_SPLIT_SCREEN_PRIMARY = 3;
- /**
- * The containers adjacent to the {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} container in
- * split-screen mode.
- * NOTE: Containers launched with the windowing mode with APIs like
- * {@link ActivityOptions#setLaunchWindowingMode(int)} will be launched in
- * {@link #WINDOWING_MODE_FULLSCREEN} if the display isn't currently in split-screen windowing
- * mode
- */
- // TODO: Remove once split-screen is migrated to wm-shell.
- public static final int WINDOWING_MODE_SPLIT_SCREEN_SECONDARY = 4;
/** Can be freely resized within its parent container. */
// TODO: Remove once freeform is migrated to wm-shell.
public static final int WINDOWING_MODE_FREEFORM = 5;
@@ -129,8 +116,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
WINDOWING_MODE_FULLSCREEN,
WINDOWING_MODE_MULTI_WINDOW,
WINDOWING_MODE_PINNED,
- WINDOWING_MODE_SPLIT_SCREEN_PRIMARY,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
WINDOWING_MODE_FREEFORM,
})
public @interface WindowingMode {}
@@ -890,9 +875,8 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
}
/**
- * Returns true if this container can be put in either
- * {@link #WINDOWING_MODE_SPLIT_SCREEN_PRIMARY} or
- * {@link #WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on its current state.
+ * Returns true if this container can be put in {@link #WINDOWING_MODE_MULTI_WINDOW}
+ * windowing mode based on its current state.
* @hide
*/
public boolean supportSplitScreenWindowingMode() {
@@ -911,8 +895,6 @@ public class WindowConfiguration implements Parcelable, Comparable<WindowConfigu
case WINDOWING_MODE_FULLSCREEN: return "fullscreen";
case WINDOWING_MODE_MULTI_WINDOW: return "multi-window";
case WINDOWING_MODE_PINNED: return "pinned";
- case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return "split-screen-primary";
- case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return "split-screen-secondary";
case WINDOWING_MODE_FREEFORM: return "freeform";
}
return String.valueOf(windowingMode);
diff --git a/core/java/android/app/search/SearchAction.java b/core/java/android/app/search/SearchAction.java
index 9e40e7ebaef0..0c4508a4fb8d 100644
--- a/core/java/android/app/search/SearchAction.java
+++ b/core/java/android/app/search/SearchAction.java
@@ -67,7 +67,7 @@ public final class SearchAction implements Parcelable {
private final UserHandle mUserHandle;
@Nullable
- private Bundle mExtras;
+ private final Bundle mExtras;
SearchAction(Parcel in) {
mId = in.readString();
@@ -99,7 +99,7 @@ public final class SearchAction implements Parcelable {
mPendingIntent = pendingIntent;
mIntent = intent;
mUserHandle = userHandle;
- mExtras = extras;
+ mExtras = extras != null ? extras : new Bundle();
if (mPendingIntent == null && mIntent == null) {
throw new IllegalStateException("At least one type of intent should be available.");
diff --git a/core/java/android/app/search/SearchTarget.java b/core/java/android/app/search/SearchTarget.java
index a590a5d7b767..a3874f7cb007 100644
--- a/core/java/android/app/search/SearchTarget.java
+++ b/core/java/android/app/search/SearchTarget.java
@@ -185,7 +185,7 @@ public final class SearchTarget implements Parcelable {
mShortcutInfo = shortcutInfo;
mAppWidgetProviderInfo = appWidgetProviderInfo;
mSliceUri = sliceUri;
- mExtras = extras;
+ mExtras = extras != null ? extras : new Bundle();
int published = 0;
if (mSearchAction != null) published++;
diff --git a/core/java/android/app/usage/UsageStats.java b/core/java/android/app/usage/UsageStats.java
index d61abc64fae5..e213c934e95a 100644
--- a/core/java/android/app/usage/UsageStats.java
+++ b/core/java/android/app/usage/UsageStats.java
@@ -293,6 +293,17 @@ public final class UsageStats implements Parcelable {
}
/**
+ * Returns the last time the package was used - defined by the latest of
+ * mLastTimeUsed, mLastTimeVisible, mLastTimeForegroundServiceUsed, or mLastTimeComponentUsed.
+ * @hide
+ */
+ public long getLastTimePackageUsed() {
+ return Math.max(mLastTimeUsed,
+ Math.max(mLastTimeVisible,
+ Math.max(mLastTimeForegroundServiceUsed, mLastTimeComponentUsed)));
+ }
+
+ /**
* Returns the number of times the app was launched as an activity from outside of the app.
* Excludes intra-app activity transitions.
* @hide
diff --git a/core/java/android/app/usage/UsageStatsManager.java b/core/java/android/app/usage/UsageStatsManager.java
index c013fcd5adb9..1dfc7d48640e 100644
--- a/core/java/android/app/usage/UsageStatsManager.java
+++ b/core/java/android/app/usage/UsageStatsManager.java
@@ -220,6 +220,11 @@ public final class UsageStatsManager {
*/
public static final int REASON_SUB_DEFAULT_APP_UPDATE = 0x0001;
/**
+ * The app was restored.
+ * @hide
+ */
+ public static final int REASON_SUB_DEFAULT_APP_RESTORED = 0x0002;
+ /**
* The app was interacted with in some way by the system.
* @hide
*/
@@ -1209,6 +1214,9 @@ public final class UsageStatsManager {
case REASON_SUB_DEFAULT_APP_UPDATE:
sb.append("-au");
break;
+ case REASON_SUB_DEFAULT_APP_RESTORED:
+ sb.append("-ar");
+ break;
}
break;
case REASON_MAIN_FORCED_BY_SYSTEM:
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 18c638112480..a432b8dec2cb 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -1130,7 +1130,9 @@ public class AppWidgetManager {
* @param intent The intent of the service which will be providing the data to the
* RemoteViewsAdapter.
* @param connection The callback interface to be notified when a connection is made or lost.
- * @param flags Flags used for binding to the service
+ * @param flags Flags used for binding to the service. Currently only
+ * {@link Context#BIND_AUTO_CREATE} and
+ * {@link Context#BIND_FOREGROUND_SERVICE_WHILE_AWAKE} are supported.
*
* @see Context#getServiceDispatcher(ServiceConnection, Handler, int)
* @hide
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index f7f0235cd508..93748f81ffa1 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -55,6 +55,14 @@ public final class AssociationInfo implements Parcelable {
private final boolean mSelfManaged;
private final boolean mNotifyOnDeviceNearby;
+
+ /**
+ * Indicates that the association has been revoked (removed), but we keep the association
+ * record for final clean up (e.g. removing the app from the list of the role holders).
+ *
+ * @see CompanionDeviceManager#disassociate(int)
+ */
+ private final boolean mRevoked;
private final long mTimeApprovedMs;
/**
* A long value indicates the last time connected reported by selfManaged devices
@@ -71,7 +79,7 @@ public final class AssociationInfo implements Parcelable {
public AssociationInfo(int id, @UserIdInt int userId, @NonNull String packageName,
@Nullable MacAddress macAddress, @Nullable CharSequence displayName,
@Nullable String deviceProfile, boolean selfManaged, boolean notifyOnDeviceNearby,
- long timeApprovedMs, long lastTimeConnectedMs) {
+ boolean revoked, long timeApprovedMs, long lastTimeConnectedMs) {
if (id <= 0) {
throw new IllegalArgumentException("Association ID should be greater than 0");
}
@@ -91,6 +99,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = selfManaged;
mNotifyOnDeviceNearby = notifyOnDeviceNearby;
+ mRevoked = revoked;
mTimeApprovedMs = timeApprovedMs;
mLastTimeConnectedMs = lastTimeConnectedMs;
}
@@ -176,6 +185,14 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * @return if the association has been revoked (removed).
+ * @hide
+ */
+ public boolean isRevoked() {
+ return mRevoked;
+ }
+
+ /**
* @return the last time self reported disconnected for selfManaged only.
* @hide
*/
@@ -244,6 +261,7 @@ public final class AssociationInfo implements Parcelable {
+ ", mDeviceProfile='" + mDeviceProfile + '\''
+ ", mSelfManaged=" + mSelfManaged
+ ", mNotifyOnDeviceNearby=" + mNotifyOnDeviceNearby
+ + ", mRevoked=" + mRevoked
+ ", mTimeApprovedMs=" + new Date(mTimeApprovedMs)
+ ", mLastTimeConnectedMs=" + (
mLastTimeConnectedMs == Long.MAX_VALUE
@@ -260,6 +278,7 @@ public final class AssociationInfo implements Parcelable {
&& mUserId == that.mUserId
&& mSelfManaged == that.mSelfManaged
&& mNotifyOnDeviceNearby == that.mNotifyOnDeviceNearby
+ && mRevoked == that.mRevoked
&& mTimeApprovedMs == that.mTimeApprovedMs
&& mLastTimeConnectedMs == that.mLastTimeConnectedMs
&& Objects.equals(mPackageName, that.mPackageName)
@@ -271,7 +290,7 @@ public final class AssociationInfo implements Parcelable {
@Override
public int hashCode() {
return Objects.hash(mId, mUserId, mPackageName, mDeviceMacAddress, mDisplayName,
- mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mTimeApprovedMs,
+ mDeviceProfile, mSelfManaged, mNotifyOnDeviceNearby, mRevoked, mTimeApprovedMs,
mLastTimeConnectedMs);
}
@@ -293,6 +312,7 @@ public final class AssociationInfo implements Parcelable {
dest.writeBoolean(mSelfManaged);
dest.writeBoolean(mNotifyOnDeviceNearby);
+ dest.writeBoolean(mRevoked);
dest.writeLong(mTimeApprovedMs);
dest.writeLong(mLastTimeConnectedMs);
}
@@ -309,6 +329,7 @@ public final class AssociationInfo implements Parcelable {
mSelfManaged = in.readBoolean();
mNotifyOnDeviceNearby = in.readBoolean();
+ mRevoked = in.readBoolean();
mTimeApprovedMs = in.readLong();
mLastTimeConnectedMs = in.readLong();
}
@@ -352,11 +373,13 @@ public final class AssociationInfo implements Parcelable {
@NonNull
private final AssociationInfo mOriginalInfo;
private boolean mNotifyOnDeviceNearby;
+ private boolean mRevoked;
private long mLastTimeConnectedMs;
private Builder(@NonNull AssociationInfo info) {
mOriginalInfo = info;
mNotifyOnDeviceNearby = info.mNotifyOnDeviceNearby;
+ mRevoked = info.mRevoked;
mLastTimeConnectedMs = info.mLastTimeConnectedMs;
}
@@ -388,6 +411,17 @@ public final class AssociationInfo implements Parcelable {
}
/**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @Override
+ @NonNull
+ public Builder setRevoked(boolean revoked) {
+ mRevoked = revoked;
+ return this;
+ }
+
+ /**
* @hide
*/
@NonNull
@@ -401,6 +435,7 @@ public final class AssociationInfo implements Parcelable {
mOriginalInfo.mDeviceProfile,
mOriginalInfo.mSelfManaged,
mNotifyOnDeviceNearby,
+ mRevoked,
mOriginalInfo.mTimeApprovedMs,
mLastTimeConnectedMs
);
@@ -433,5 +468,12 @@ public final class AssociationInfo implements Parcelable {
*/
@NonNull
Builder setLastTimeConnected(long lastTimeConnectedMs);
+
+ /**
+ * Should only be used by the CompanionDeviceManagerService.
+ * @hide
+ */
+ @NonNull
+ Builder setRevoked(boolean revoked);
}
}
diff --git a/core/java/android/content/ClipboardManager.java b/core/java/android/content/ClipboardManager.java
index d41cda102103..85af87722ed2 100644
--- a/core/java/android/content/ClipboardManager.java
+++ b/core/java/android/content/ClipboardManager.java
@@ -129,7 +129,11 @@ public class ClipboardManager extends android.text.ClipboardManager {
try {
Objects.requireNonNull(clip);
clip.prepareToLeaveProcess(true);
- mService.setPrimaryClip(clip, mContext.getOpPackageName(), mContext.getUserId());
+ mService.setPrimaryClip(
+ clip,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -154,7 +158,11 @@ public class ClipboardManager extends android.text.ClipboardManager {
Objects.requireNonNull(sourcePackage);
clip.prepareToLeaveProcess(true);
mService.setPrimaryClipAsPackage(
- clip, mContext.getOpPackageName(), mContext.getUserId(), sourcePackage);
+ clip,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId(),
+ sourcePackage);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -167,7 +175,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public void clearPrimaryClip() {
try {
- mService.clearPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
+ mService.clearPrimaryClip(
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -183,24 +194,29 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public @Nullable ClipData getPrimaryClip() {
try {
- return mService.getPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
+ return mService.getPrimaryClip(
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
- * Returns a description of the current primary clip on the clipboard
- * but not a copy of its data.
+ * Returns a description of the current primary clip on the clipboard but not a copy of its
+ * data.
*
- * <em>If the application is not the default IME or does not have input focus this return
+ * <p><em>If the application is not the default IME or does not have input focus this return
* {@code null}.</em>
*
* @see #setPrimaryClip(ClipData)
*/
public @Nullable ClipDescription getPrimaryClipDescription() {
try {
- return mService.getPrimaryClipDescription(mContext.getOpPackageName(),
+ return mService.getPrimaryClipDescription(
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -215,7 +231,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
*/
public boolean hasPrimaryClip() {
try {
- return mService.hasPrimaryClip(mContext.getOpPackageName(), mContext.getUserId());
+ return mService.hasPrimaryClip(
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -226,7 +245,9 @@ public class ClipboardManager extends android.text.ClipboardManager {
if (mPrimaryClipChangedListeners.isEmpty()) {
try {
mService.addPrimaryClipChangedListener(
- mPrimaryClipChangedServiceListener, mContext.getOpPackageName(),
+ mPrimaryClipChangedServiceListener,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -242,7 +263,9 @@ public class ClipboardManager extends android.text.ClipboardManager {
if (mPrimaryClipChangedListeners.isEmpty()) {
try {
mService.removePrimaryClipChangedListener(
- mPrimaryClipChangedServiceListener, mContext.getOpPackageName(),
+ mPrimaryClipChangedServiceListener,
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
@@ -280,7 +303,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
@Deprecated
public boolean hasText() {
try {
- return mService.hasClipboardText(mContext.getOpPackageName(), mContext.getUserId());
+ return mService.hasClipboardText(
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -297,7 +323,10 @@ public class ClipboardManager extends android.text.ClipboardManager {
@RequiresPermission(Manifest.permission.SET_CLIP_SOURCE)
public String getPrimaryClipSource() {
try {
- return mService.getPrimaryClipSource(mContext.getOpPackageName(), mContext.getUserId());
+ return mService.getPrimaryClipSource(
+ mContext.getOpPackageName(),
+ mContext.getAttributionTag(),
+ mContext.getUserId());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
diff --git a/core/java/android/content/IClipboard.aidl b/core/java/android/content/IClipboard.aidl
index 102b8e798a5c..46ece2bc3f5e 100644
--- a/core/java/android/content/IClipboard.aidl
+++ b/core/java/android/content/IClipboard.aidl
@@ -26,22 +26,22 @@ import android.content.IOnPrimaryClipChangedListener;
* {@hide}
*/
interface IClipboard {
- void setPrimaryClip(in ClipData clip, String callingPackage, int userId);
- void setPrimaryClipAsPackage(in ClipData clip, String callingPackage, int userId,
+ void setPrimaryClip(in ClipData clip, String callingPackage, String attributionTag, int userId);
+ void setPrimaryClipAsPackage(in ClipData clip, String callingPackage, String attributionTag, int userId,
String sourcePackage);
- void clearPrimaryClip(String callingPackage, int userId);
- ClipData getPrimaryClip(String pkg, int userId);
- ClipDescription getPrimaryClipDescription(String callingPackage, int userId);
- boolean hasPrimaryClip(String callingPackage, int userId);
+ void clearPrimaryClip(String callingPackage, String attributionTag, int userId);
+ ClipData getPrimaryClip(String pkg, String attributionTag, int userId);
+ ClipDescription getPrimaryClipDescription(String callingPackage, String attributionTag, int userId);
+ boolean hasPrimaryClip(String callingPackage, String attributionTag, int userId);
void addPrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener,
- String callingPackage, int userId);
+ String callingPackage, String attributionTag, int userId);
void removePrimaryClipChangedListener(in IOnPrimaryClipChangedListener listener,
- String callingPackage, int userId);
+ String callingPackage, String attributionTag, int userId);
/**
* Returns true if the clipboard contains text; false otherwise.
*/
- boolean hasClipboardText(String callingPackage, int userId);
+ boolean hasClipboardText(String callingPackage, String attributionTag, int userId);
- String getPrimaryClipSource(String callingPackage, int userId);
+ String getPrimaryClipSource(String callingPackage, String attributionTag, int userId);
}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 0673b3ad5b0a..a3d595ef6575 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -106,6 +106,24 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
public @interface LaunchMode {
}
+ /** @hide */
+ public static String launchModeToString(@LaunchMode int launchMode) {
+ switch(launchMode) {
+ case LAUNCH_MULTIPLE:
+ return "LAUNCH_MULTIPLE";
+ case LAUNCH_SINGLE_TOP:
+ return "LAUNCH_SINGLE_TOP";
+ case LAUNCH_SINGLE_TASK:
+ return "LAUNCH_SINGLE_TASK";
+ case LAUNCH_SINGLE_INSTANCE:
+ return "LAUNCH_SINGLE_INSTANCE";
+ case LAUNCH_SINGLE_INSTANCE_PER_TASK:
+ return "LAUNCH_SINGLE_INSTANCE_PER_TASK";
+ default:
+ return "unknown=" + launchMode;
+ }
+ }
+
/**
* The launch mode style requested by the activity. From the
* {@link android.R.attr#launchMode} attribute.
@@ -1585,7 +1603,7 @@ public class ActivityInfo extends ComponentInfo implements Parcelable {
+ " persistableMode=" + persistableModeToString());
}
if (launchMode != 0 || flags != 0 || privateFlags != 0 || theme != 0) {
- pw.println(prefix + "launchMode=" + launchMode
+ pw.println(prefix + "launchMode=" + launchModeToString(launchMode)
+ " flags=0x" + Integer.toHexString(flags)
+ " privateFlags=0x" + Integer.toHexString(privateFlags)
+ " theme=0x" + Integer.toHexString(theme));
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 44dc28d2b0fa..c15b3e0b80c3 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -713,6 +713,15 @@ public class PackageParser {
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
return null;
}
+
+ final ApplicationInfo applicationInfo;
+ if ((flags & (PackageManager.GET_ACTIVITIES | PackageManager.GET_RECEIVERS
+ | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS)) != 0) {
+ applicationInfo = generateApplicationInfo(p, flags, state, userId);
+ } else {
+ applicationInfo = null;
+ }
+
PackageInfo pi = new PackageInfo();
pi.packageName = p.packageName;
pi.splitNames = p.splitNames;
@@ -773,7 +782,7 @@ public class PackageParser {
if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(a.className)) {
continue;
}
- res[num++] = generateActivityInfo(a, flags, state, userId);
+ res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo);
}
}
pi.activities = ArrayUtils.trimToSize(res, num);
@@ -787,7 +796,7 @@ public class PackageParser {
for (int i = 0; i < N; i++) {
final Activity a = p.receivers.get(i);
if (isMatch(state, a.info, flags)) {
- res[num++] = generateActivityInfo(a, flags, state, userId);
+ res[num++] = generateActivityInfo(a, flags, state, userId, applicationInfo);
}
}
pi.receivers = ArrayUtils.trimToSize(res, num);
@@ -801,7 +810,7 @@ public class PackageParser {
for (int i = 0; i < N; i++) {
final Service s = p.services.get(i);
if (isMatch(state, s.info, flags)) {
- res[num++] = generateServiceInfo(s, flags, state, userId);
+ res[num++] = generateServiceInfo(s, flags, state, userId, applicationInfo);
}
}
pi.services = ArrayUtils.trimToSize(res, num);
@@ -815,7 +824,8 @@ public class PackageParser {
for (int i = 0; i < N; i++) {
final Provider pr = p.providers.get(i);
if (isMatch(state, pr.info, flags)) {
- res[num++] = generateProviderInfo(pr, flags, state, userId);
+ res[num++] = generateProviderInfo(pr, flags, state, userId,
+ applicationInfo);
}
}
pi.providers = ArrayUtils.trimToSize(res, num);
@@ -8216,6 +8226,11 @@ public class PackageParser {
@UnsupportedAppUsage
public static final ActivityInfo generateActivityInfo(Activity a, int flags,
FrameworkPackageUserState state, int userId) {
+ return generateActivityInfo(a, flags, state, userId, null);
+ }
+
+ private static ActivityInfo generateActivityInfo(Activity a, int flags,
+ FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
if (a == null) return null;
if (!checkUseInstalledOrHidden(flags, state, a.owner.applicationInfo)) {
return null;
@@ -8227,7 +8242,12 @@ public class PackageParser {
// Make shallow copies so we can store the metadata safely
ActivityInfo ai = new ActivityInfo(a.info);
ai.metaData = a.metaData;
- ai.applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(a.owner, flags, state, userId);
+ }
+ ai.applicationInfo = applicationInfo;
+
return ai;
}
@@ -8308,6 +8328,11 @@ public class PackageParser {
@UnsupportedAppUsage
public static final ServiceInfo generateServiceInfo(Service s, int flags,
FrameworkPackageUserState state, int userId) {
+ return generateServiceInfo(s, flags, state, userId, null);
+ }
+
+ private static ServiceInfo generateServiceInfo(Service s, int flags,
+ FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
if (s == null) return null;
if (!checkUseInstalledOrHidden(flags, state, s.owner.applicationInfo)) {
return null;
@@ -8319,7 +8344,12 @@ public class PackageParser {
// Make shallow copies so we can store the metadata safely
ServiceInfo si = new ServiceInfo(s.info);
si.metaData = s.metaData;
- si.applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(s.owner, flags, state, userId);
+ }
+ si.applicationInfo = applicationInfo;
+
return si;
}
@@ -8406,13 +8436,18 @@ public class PackageParser {
@UnsupportedAppUsage
public static final ProviderInfo generateProviderInfo(Provider p, int flags,
FrameworkPackageUserState state, int userId) {
+ return generateProviderInfo(p, flags, state, userId, null);
+ }
+
+ private static ProviderInfo generateProviderInfo(Provider p, int flags,
+ FrameworkPackageUserState state, int userId, ApplicationInfo applicationInfo) {
if (p == null) return null;
if (!checkUseInstalledOrHidden(flags, state, p.owner.applicationInfo)) {
return null;
}
if (!copyNeeded(flags, p.owner, state, p.metaData, userId)
&& ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) != 0
- || p.info.uriPermissionPatterns == null)) {
+ || p.info.uriPermissionPatterns == null)) {
updateApplicationInfo(p.info.applicationInfo, flags, state);
return p.info;
}
@@ -8422,7 +8457,12 @@ public class PackageParser {
if ((flags & PackageManager.GET_URI_PERMISSION_PATTERNS) == 0) {
pi.uriPermissionPatterns = null;
}
- pi.applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+
+ if (applicationInfo == null) {
+ applicationInfo = generateApplicationInfo(p.owner, flags, state, userId);
+ }
+ pi.applicationInfo = applicationInfo;
+
return pi;
}
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index d6e13ac90f82..9baa6ba2fb49 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -143,6 +143,22 @@ public class UserInfo implements Parcelable {
public static final int FLAG_PROFILE = 0x00001000;
/**
+ * Indicates that this user is created in ephemeral mode via
+ * {@link IUserManager} create user.
+ *
+ * When a user is created with {@link #FLAG_EPHEMERAL}, {@link #FLAG_EPHEMERAL_ON_CREATE}
+ * is set internally within the user manager.
+ *
+ * When {@link #FLAG_EPHEMERAL_ON_CREATE} is set {@link IUserManager.setUserEphemeral}
+ * has no effect because a user that was created ephemeral can never be made non-ephemeral.
+ *
+ * {@link #FLAG_EPHEMERAL_ON_CREATE} should NOT be set by client's of user manager
+ *
+ * @hide
+ */
+ public static final int FLAG_EPHEMERAL_ON_CREATE = 0x00002000;
+
+ /**
* @hide
*/
@IntDef(flag = true, prefix = "FLAG_", value = {
@@ -158,7 +174,8 @@ public class UserInfo implements Parcelable {
FLAG_DEMO,
FLAG_FULL,
FLAG_SYSTEM,
- FLAG_PROFILE
+ FLAG_PROFILE,
+ FLAG_EPHEMERAL_ON_CREATE
})
@Retention(RetentionPolicy.SOURCE)
public @interface UserInfoFlag {
diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 439c6396f1d0..608e34bd07b4 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -420,7 +420,10 @@ public class CompatibilityInfo implements Parcelable {
* Translate a Rect in screen coordinates into the app window's coordinates.
*/
@UnsupportedAppUsage
- public void translateRectInScreenToAppWindow(Rect rect) {
+ public void translateRectInScreenToAppWindow(@Nullable Rect rect) {
+ if (rect == null) {
+ return;
+ }
rect.scale(applicationInvertedScale);
}
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 99e4febe205d..943eee461809 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -157,6 +157,11 @@ public interface BiometricConstants {
int BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED = 18;
/**
+ * A power press stopped this biometric operation.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+ /**
* This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
* because the authentication attempt was unsuccessful.
* @hide
diff --git a/core/java/android/hardware/biometrics/BiometricFaceConstants.java b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
index fd46f243874b..2b62b98529a9 100644
--- a/core/java/android/hardware/biometrics/BiometricFaceConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFaceConstants.java
@@ -70,6 +70,7 @@ public interface BiometricFaceConstants {
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
BIOMETRIC_ERROR_RE_ENROLL,
FACE_ERROR_UNKNOWN,
+ BIOMETRIC_ERROR_POWER_PRESSED,
})
@Retention(RetentionPolicy.SOURCE)
@interface FaceError {}
@@ -184,6 +185,12 @@ public interface BiometricFaceConstants {
int FACE_ERROR_UNKNOWN = 17;
/**
+ * A power press stopped this biometric operation.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+
+ /**
* Vendor codes received from the HAL start at 0. Codes that the framework exposes to keyguard
* append this value for some reason. We should probably remove this and just send the actual
* vendor code.
diff --git a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
index d8ebb628452a..98f571b80949 100644
--- a/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricFingerprintConstants.java
@@ -61,7 +61,8 @@ public interface BiometricFingerprintConstants {
BIOMETRIC_ERROR_RE_ENROLL,
BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
FINGERPRINT_ERROR_UNKNOWN,
- FINGERPRINT_ERROR_BAD_CALIBRATION})
+ FINGERPRINT_ERROR_BAD_CALIBRATION,
+ BIOMETRIC_ERROR_POWER_PRESSED})
@Retention(RetentionPolicy.SOURCE)
@interface FingerprintError {}
@@ -188,6 +189,12 @@ public interface BiometricFingerprintConstants {
int FINGERPRINT_ERROR_BAD_CALIBRATION = 18;
/**
+ * A power press stopped this biometric operation.
+ * @hide
+ */
+ int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+
+ /**
* @hide
*/
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
diff --git a/core/java/android/hardware/biometrics/BiometricStateListener.java b/core/java/android/hardware/biometrics/BiometricStateListener.java
index 2ac0c1edaef0..b167cc6323d3 100644
--- a/core/java/android/hardware/biometrics/BiometricStateListener.java
+++ b/core/java/android/hardware/biometrics/BiometricStateListener.java
@@ -46,6 +46,14 @@ public abstract class BiometricStateListener extends IBiometricStateListener.Stu
public @interface State {
}
+ // The sensor received a touch.
+ public static final int ACTION_SENSOR_TOUCH = 0;
+
+ @IntDef({ACTION_SENSOR_TOUCH})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Action {
+ }
+
/**
* Defines behavior in response to state update
* @param newState new state of the biometric sensor
@@ -53,6 +61,13 @@ public abstract class BiometricStateListener extends IBiometricStateListener.Stu
public void onStateChanged(@BiometricStateListener.State int newState) {
}
+
+ /**
+ * Invoked when a biometric action has occurred.
+ */
+ public void onBiometricAction(@BiometricStateListener.Action int action) {
+ }
+
/**
* Invoked when enrollment state changes for the specified user
*/
diff --git a/core/java/android/hardware/biometrics/IBiometricStateListener.aidl b/core/java/android/hardware/biometrics/IBiometricStateListener.aidl
index 5bdced034017..6bb170d1cb62 100644
--- a/core/java/android/hardware/biometrics/IBiometricStateListener.aidl
+++ b/core/java/android/hardware/biometrics/IBiometricStateListener.aidl
@@ -22,5 +22,6 @@ package android.hardware.biometrics;
*/
oneway interface IBiometricStateListener {
void onStateChanged(int newState);
+ void onBiometricAction(int action);
void onEnrollmentsChanged(int userId, int sensorId, boolean hasEnrollments);
}
diff --git a/core/java/android/hardware/display/AmbientDisplayConfiguration.java b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
index 7d8f2ff92200..8c71b363eb7b 100644
--- a/core/java/android/hardware/display/AmbientDisplayConfiguration.java
+++ b/core/java/android/hardware/display/AmbientDisplayConfiguration.java
@@ -89,7 +89,8 @@ public class AmbientDisplayConfiguration {
/** @hide */
public boolean pulseOnNotificationAvailable() {
- return ambientDisplayAvailable();
+ return mContext.getResources().getBoolean(R.bool.config_pulseOnNotificationsAvailable)
+ && ambientDisplayAvailable();
}
/** @hide */
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java
index 69c6ba9a0f43..8bc11cbc61de 100644
--- a/core/java/android/hardware/display/DisplayManager.java
+++ b/core/java/android/hardware/display/DisplayManager.java
@@ -559,21 +559,18 @@ public final class DisplayManager {
* @see #DISPLAY_CATEGORY_PRESENTATION
*/
public Display[] getDisplays(String category) {
- boolean includeDisabledDisplays = (category != null
- && category.equals(DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED));
- final int[] displayIds = mGlobal.getDisplayIds(includeDisabledDisplays);
+ final int[] displayIds = mGlobal.getDisplayIds();
synchronized (mLock) {
try {
- if (category != null && category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
+ if (category == null
+ || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category)) {
+ addAllDisplaysLocked(mTempDisplays, displayIds);
+ } else if (category.equals(DISPLAY_CATEGORY_PRESENTATION)) {
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_WIFI);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_EXTERNAL);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_OVERLAY);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_VIRTUAL);
addPresentationDisplaysLocked(mTempDisplays, displayIds, Display.TYPE_INTERNAL);
- } else if ((category == null
- || DISPLAY_CATEGORY_ALL_INCLUDING_DISABLED.equals(category))) {
- // All displays requested.
- addAllDisplaysLocked(mTempDisplays, displayIds);
}
return mTempDisplays.toArray(new Display[mTempDisplays.size()]);
} finally {
@@ -1451,5 +1448,15 @@ public final class DisplayManager {
* @hide
*/
String KEY_HIGH_REFRESH_RATE_BLACKLIST = "high_refresh_rate_blacklist";
+
+ /**
+ * Key for the brightness throttling data as a String formatted:
+ * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
+ * Where the latter part is repeated for each throttling level, and the entirety is repeated
+ * for each display, separated by a semicolon.
+ * For example:
+ * 123,1,critical,0.8;456,2,moderate,0.9,critical,0.7
+ */
+ String KEY_BRIGHTNESS_THROTTLING_DATA = "brightness_throttling_data";
}
}
diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java
index da3a5802ee55..74356ddecc76 100644
--- a/core/java/android/hardware/display/DisplayManagerGlobal.java
+++ b/core/java/android/hardware/display/DisplayManagerGlobal.java
@@ -206,16 +206,6 @@ public final class DisplayManagerGlobal {
*/
@UnsupportedAppUsage
public int[] getDisplayIds() {
- return getDisplayIds(/* includeDisabledDisplays= */ false);
- }
-
- /**
- * Gets all valid logical display ids and invalid ones if specified.
- *
- * @return An array containing all display ids.
- */
- @UnsupportedAppUsage
- public int[] getDisplayIds(boolean includeDisabledDisplays) {
try {
synchronized (mLock) {
if (USE_CACHE) {
@@ -224,8 +214,7 @@ public final class DisplayManagerGlobal {
}
}
- int[] displayIds =
- mDm.getDisplayIds(includeDisabledDisplays);
+ int[] displayIds = mDm.getDisplayIds();
if (USE_CACHE) {
mDisplayIdCache = displayIds;
}
diff --git a/core/java/android/hardware/display/IDisplayManager.aidl b/core/java/android/hardware/display/IDisplayManager.aidl
index a4115d178f6f..ca3e58094400 100644
--- a/core/java/android/hardware/display/IDisplayManager.aidl
+++ b/core/java/android/hardware/display/IDisplayManager.aidl
@@ -36,7 +36,7 @@ import android.view.Surface;
interface IDisplayManager {
@UnsupportedAppUsage
DisplayInfo getDisplayInfo(int displayId);
- int[] getDisplayIds(boolean includeDisabled);
+ int[] getDisplayIds();
boolean isUidPresentOnDisplay(int uid, int displayId);
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 28f1f02b7a93..c614cdbc3199 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -101,6 +101,7 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
private static final int MSG_FINGERPRINT_DETECTED = 107;
private static final int MSG_UDFPS_POINTER_DOWN = 108;
private static final int MSG_UDFPS_POINTER_UP = 109;
+ private static final int MSG_POWER_BUTTON_PRESSED = 110;
/**
* @hide
@@ -984,6 +985,16 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
}
/**
+ * This is triggered by SideFpsEventHandler
+ * @hide
+ */
+ @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+ public void onPowerPressed() {
+ Slog.i(TAG, "onPowerPressed");
+ mHandler.obtainMessage(MSG_POWER_BUTTON_PRESSED).sendToTarget();
+ }
+
+ /**
* Determine if there is at least one fingerprint enrolled.
*
* @return true if at least one fingerprint is enrolled, false otherwise
@@ -1196,6 +1207,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
case MSG_UDFPS_POINTER_UP:
sendUdfpsPointerUp(msg.arg1 /* sensorId */);
break;
+ case MSG_POWER_BUTTON_PRESSED:
+ sendPowerPressed();
+ break;
default:
Slog.w(TAG, "Unknown message: " + msg.what);
@@ -1325,6 +1339,14 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
mAuthenticationCallback.onUdfpsPointerUp(sensorId);
}
+ private void sendPowerPressed() {
+ try {
+ mService.onPowerPressed();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Error sending power press", e);
+ }
+ }
+
/**
* @hide
*/
@@ -1461,6 +1483,9 @@ public class FingerprintManager implements BiometricAuthenticator, BiometricFing
case FINGERPRINT_ERROR_BAD_CALIBRATION:
return context.getString(
com.android.internal.R.string.fingerprint_error_bad_calibration);
+ case BIOMETRIC_ERROR_POWER_PRESSED:
+ return context.getString(
+ com.android.internal.R.string.fingerprint_error_power_pressed);
case FINGERPRINT_ERROR_VENDOR: {
String[] msgArray = context.getResources().getStringArray(
com.android.internal.R.array.fingerprint_error_vendor);
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 0b6344617663..20cc58cc78a7 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -171,4 +171,7 @@ interface IFingerprintService {
// Registers BiometricStateListener.
void registerBiometricStateListener(IBiometricStateListener listener);
+
+ // Sends a power button pressed event to all listeners.
+ oneway void onPowerPressed();
}
diff --git a/core/java/android/hardware/radio/ProgramList.java b/core/java/android/hardware/radio/ProgramList.java
index 3a042a5dee4d..f2525d17e30a 100644
--- a/core/java/android/hardware/radio/ProgramList.java
+++ b/core/java/android/hardware/radio/ProgramList.java
@@ -22,11 +22,11 @@ import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArrayMap;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -42,7 +42,7 @@ public final class ProgramList implements AutoCloseable {
private final Object mLock = new Object();
private final Map<ProgramSelector.Identifier, RadioManager.ProgramInfo> mPrograms =
- new HashMap<>();
+ new ArrayMap<>();
private final List<ListCallback> mListCallbacks = new ArrayList<>();
private final List<OnCompleteListener> mOnCompleteListeners = new ArrayList<>();
@@ -173,38 +173,69 @@ public final class ProgramList implements AutoCloseable {
}
}
- void apply(@NonNull Chunk chunk) {
+ void apply(Chunk chunk) {
+ List<ProgramSelector.Identifier> removedList = new ArrayList<>();
+ List<ProgramSelector.Identifier> changedList = new ArrayList<>();
+ List<ProgramList.ListCallback> listCallbacksCopied;
+ List<OnCompleteListener> onCompleteListenersCopied = new ArrayList<>();
synchronized (mLock) {
if (mIsClosed) return;
mIsComplete = false;
+ listCallbacksCopied = new ArrayList<>(mListCallbacks);
if (chunk.isPurge()) {
- new HashSet<>(mPrograms.keySet()).stream().forEach(id -> removeLocked(id));
+ Iterator<Map.Entry<ProgramSelector.Identifier, RadioManager.ProgramInfo>>
+ programsIterator = mPrograms.entrySet().iterator();
+ while (programsIterator.hasNext()) {
+ RadioManager.ProgramInfo removed = programsIterator.next().getValue();
+ if (removed != null) {
+ removedList.add(removed.getSelector().getPrimaryId());
+ }
+ programsIterator.remove();
+ }
}
- chunk.getRemoved().stream().forEach(id -> removeLocked(id));
- chunk.getModified().stream().forEach(info -> putLocked(info));
+ chunk.getRemoved().stream().forEach(id -> removeLocked(id, removedList));
+ chunk.getModified().stream().forEach(info -> putLocked(info, changedList));
if (chunk.isComplete()) {
mIsComplete = true;
- mOnCompleteListeners.forEach(cb -> cb.onComplete());
+ onCompleteListenersCopied = new ArrayList<>(mOnCompleteListeners);
+ }
+ }
+
+ for (int i = 0; i < removedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemRemoved(removedList.get(i));
+ }
+ }
+ for (int i = 0; i < changedList.size(); i++) {
+ for (int cbIndex = 0; cbIndex < listCallbacksCopied.size(); cbIndex++) {
+ listCallbacksCopied.get(cbIndex).onItemChanged(changedList.get(i));
+ }
+ }
+ if (chunk.isComplete()) {
+ for (int cbIndex = 0; cbIndex < onCompleteListenersCopied.size(); cbIndex++) {
+ onCompleteListenersCopied.get(cbIndex).onComplete();
}
}
}
- private void putLocked(@NonNull RadioManager.ProgramInfo value) {
+ private void putLocked(RadioManager.ProgramInfo value,
+ List<ProgramSelector.Identifier> changedIdentifierList) {
ProgramSelector.Identifier key = value.getSelector().getPrimaryId();
mPrograms.put(Objects.requireNonNull(key), value);
ProgramSelector.Identifier sel = value.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemChanged(sel));
+ changedIdentifierList.add(sel);
}
- private void removeLocked(@NonNull ProgramSelector.Identifier key) {
+ private void removeLocked(ProgramSelector.Identifier key,
+ List<ProgramSelector.Identifier> removedIdentifierList) {
RadioManager.ProgramInfo removed = mPrograms.remove(Objects.requireNonNull(key));
if (removed == null) return;
ProgramSelector.Identifier sel = removed.getSelector().getPrimaryId();
- mListCallbacks.forEach(cb -> cb.onItemRemoved(sel));
+ removedIdentifierList.add(sel);
}
/**
diff --git a/core/java/android/nfc/INfcTag.aidl b/core/java/android/nfc/INfcTag.aidl
index e1ccc4fb740b..170df71385bb 100644
--- a/core/java/android/nfc/INfcTag.aidl
+++ b/core/java/android/nfc/INfcTag.aidl
@@ -46,6 +46,5 @@ interface INfcTag
int getMaxTransceiveLength(int technology);
boolean getExtendedLengthApdusSupported();
- void setTagUpToDate(long cookie);
boolean isTagUpToDate(long cookie);
}
diff --git a/core/java/android/nfc/Tag.java b/core/java/android/nfc/Tag.java
index 731d1ba78299..500038f14d9d 100644
--- a/core/java/android/nfc/Tag.java
+++ b/core/java/android/nfc/Tag.java
@@ -34,7 +34,6 @@ import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteException;
-import android.os.SystemClock;
import java.io.IOException;
import java.util.Arrays;
@@ -119,17 +118,17 @@ public final class Tag implements Parcelable {
final String[] mTechStringList;
final Bundle[] mTechExtras;
final int mServiceHandle; // for use by NFC service, 0 indicates a mock
+ final long mCookie; // for accessibility checking
final INfcTag mTagService; // interface to NFC service, will be null if mock tag
int mConnectedTechnology;
- long mCookie;
/**
* Hidden constructor to be used by NFC service and internal classes.
* @hide
*/
public Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle,
- INfcTag tagService) {
+ long cookie, INfcTag tagService) {
if (techList == null) {
throw new IllegalArgumentException("rawTargets cannot be null");
}
@@ -139,20 +138,13 @@ public final class Tag implements Parcelable {
// Ensure mTechExtras is as long as mTechList
mTechExtras = Arrays.copyOf(techListExtras, techList.length);
mServiceHandle = serviceHandle;
+ mCookie = cookie;
mTagService = tagService;
-
mConnectedTechnology = -1;
- mCookie = SystemClock.elapsedRealtime();
if (tagService == null) {
return;
}
-
- try {
- tagService.setTagUpToDate(mCookie);
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
}
/**
@@ -165,9 +157,10 @@ public final class Tag implements Parcelable {
* @return freshly constructed tag
* @hide
*/
- public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras) {
+ public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras,
+ long cookie) {
// set serviceHandle to 0 and tagService to null to indicate mock tag
- return new Tag(id, techList, techListExtras, 0, null);
+ return new Tag(id, techList, techListExtras, 0, cookie, null);
}
private String[] generateTechStringList(int[] techList) {
@@ -445,6 +438,7 @@ public final class Tag implements Parcelable {
dest.writeIntArray(mTechList);
dest.writeTypedArray(mTechExtras, 0);
dest.writeInt(mServiceHandle);
+ dest.writeLong(mCookie);
dest.writeInt(isMock);
if (isMock == 0) {
dest.writeStrongBinder(mTagService.asBinder());
@@ -463,6 +457,7 @@ public final class Tag implements Parcelable {
in.readIntArray(techList);
Bundle[] techExtras = in.createTypedArray(Bundle.CREATOR);
int serviceHandle = in.readInt();
+ long cookie = in.readLong();
int isMock = in.readInt();
if (isMock == 0) {
tagService = INfcTag.Stub.asInterface(in.readStrongBinder());
@@ -471,7 +466,7 @@ public final class Tag implements Parcelable {
tagService = null;
}
- return new Tag(id, techList, techExtras, serviceHandle, tagService);
+ return new Tag(id, techList, techExtras, serviceHandle, cookie, tagService);
}
@Override
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 3cde0319efd3..e5de3e157c88 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -131,4 +131,5 @@ interface IUserManager {
String getUserName();
long getUserStartRealtime();
long getUserUnlockRealtime();
+ boolean setUserEphemeral(int userId, boolean enableEphemeral);
}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 0ffdfc6cbcb1..d6566048f2c4 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2001,13 +2001,22 @@ public class UserManager {
* @return Whether guest user is always ephemeral
* @hide
*/
- @TestApi
- public static boolean isGuestUserEphemeral() {
+ public static boolean isGuestUserAlwaysEphemeral() {
return Resources.getSystem()
.getBoolean(com.android.internal.R.bool.config_guestUserEphemeral);
}
/**
+ * @return true, when we want to enable user manager API and UX to allow
+ * guest user ephemeral state change based on user input
+ * @hide
+ */
+ public static boolean isGuestUserAllowEphemeralStateChange() {
+ return Resources.getSystem()
+ .getBoolean(com.android.internal.R.bool.config_guestUserAllowEphemeralStateChange);
+ }
+
+ /**
* Checks whether the device is running in a headless system user mode.
*
* <p>Headless system user mode means the {@link #isSystemUser() system user} runs system
@@ -3431,6 +3440,20 @@ public class UserManager {
if (guest != null) {
Settings.Secure.putStringForUser(context.getContentResolver(),
Settings.Secure.SKIP_FIRST_USE_HINTS, "1", guest.id);
+
+ if (UserManager.isGuestUserAllowEphemeralStateChange()) {
+ // Mark guest as (changeably) ephemeral if REMOVE_GUEST_ON_EXIT is 1
+ // This is done so that a user via a UI controller can choose to
+ // make a guest as ephemeral or not.
+ // Settings.Global.REMOVE_GUEST_ON_EXIT holds the choice on what the guest state
+ // should be, with default being ephemeral.
+ boolean resetGuestOnExit = Settings.Global.getInt(context.getContentResolver(),
+ Settings.Global.REMOVE_GUEST_ON_EXIT, 1) == 1;
+
+ if (resetGuestOnExit && !guest.isEphemeral()) {
+ setUserEphemeral(guest.id, true);
+ }
+ }
}
return guest;
} catch (ServiceSpecificException e) {
@@ -4948,6 +4971,31 @@ public class UserManager {
}
/**
+ * Set the user as ephemeral or non-ephemeral.
+ *
+ * If the user was initially created as ephemeral then this
+ * method has no effect and false is returned.
+ *
+ * @param userId the user's integer id
+ * @param enableEphemeral true: change user state to ephemeral,
+ * false: change user state to non-ephemeral
+ * @return true: user now has the desired ephemeral state,
+ * false: desired user ephemeral state could not be set
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {
+ android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ public boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
+ try {
+ return mService.setUserEphemeral(userId, enableEphemeral);
+ } catch (RemoteException re) {
+ throw re.rethrowFromSystemServer();
+ }
+ }
+
+ /**
* Updates the context user's name.
*
* @param name the new name for the user
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index df6b827f5cb3..7a5b4a21c0ad 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -9141,6 +9141,57 @@ public final class Settings {
public static final String SCREENSAVER_ENABLED_COMPLICATIONS =
"screensaver_enabled_complications";
+
+ /**
+ * Default, indicates that the user has not yet started the dock setup flow.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_NOT_STARTED = 0;
+
+ /**
+ * Indicates that the user has started but not yet completed dock setup.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_STARTED = 1;
+
+ /**
+ * Indicates that the user has snoozed dock setup and will complete it later.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_PAUSED = 2;
+
+ /**
+ * Indicates that the user has completed dock setup.
+ * One of the possible states for {@link #DOCK_SETUP_STATE}.
+ *
+ * @hide
+ */
+ public static final int DOCK_SETUP_COMPLETED = 10;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DOCK_SETUP_NOT_STARTED,
+ DOCK_SETUP_STARTED,
+ DOCK_SETUP_PAUSED,
+ DOCK_SETUP_COMPLETED
+ })
+ public @interface DockSetupState {
+ }
+
+ /**
+ * Defines the user's current state of dock setup.
+ * The possible states are defined in {@link DockSetupState}.
+ *
+ * @hide
+ */
+ public static final String DOCK_SETUP_STATE = "dock_setup_state";
+
/**
* The default NFC payment component
* @hide
@@ -9653,6 +9704,43 @@ public final class Settings {
public static final String FACE_UNLOCK_RE_ENROLL = "face_unlock_re_enroll";
/**
+ * The time (in millis) to wait for a power button before sending a
+ * successful auth in to keyguard(for side fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW =
+ "fingerprint_side_fps_kg_power_window";
+
+ /**
+ * The time (in millis) to wait for a power button before sending
+ * a successful auth in biometric prompt(for side fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW =
+ "fingerprint_side_fps_bp_power_window";
+
+ /**
+ * The time (in millis) that a finger tap will wait for a power button
+ * before dismissing the power dialog during enrollment(for side
+ * fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW =
+ "fingerprint_side_fps_enroll_tap_window";
+
+ /**
+ * The time (in millis) that a power event will ignore future authentications
+ * (for side fingerprint)
+ * @hide
+ */
+ @Readable
+ public static final String FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME =
+ "fingerprint_side_fps_auth_downtime";
+
+ /**
* Whether or not debugging is enabled.
* @hide
*/
@@ -10820,6 +10908,23 @@ public final class Settings {
"launcher_taskbar_education_showing";
/**
+ * Whether or not adaptive charging feature is enabled by user.
+ * Type: int (0 for false, 1 for true)
+ * Default: 1
+ *
+ * @hide
+ */
+ public static final String ADAPTIVE_CHARGING_ENABLED = "adaptive_charging_enabled";
+
+ /**
+ * Whether battery saver is currently set to different schedule mode.
+ *
+ * @hide
+ */
+ public static final String EXTRA_AUTOMATIC_POWER_SAVE_MODE =
+ "extra_automatic_power_save_mode";
+
+ /**
* These entries are considered common between the personal and the managed profile,
* since the managed profile doesn't get to change them.
*/
@@ -10945,6 +11050,14 @@ public final class Settings {
public static final String ADD_USERS_WHEN_LOCKED = "add_users_when_locked";
/**
+ * Whether guest user should be removed on exit from guest mode.
+ * <p>
+ * Type: int
+ * @hide
+ */
+ public static final String REMOVE_GUEST_ON_EXIT = "remove_guest_on_exit";
+
+ /**
* Whether applying ramping ringer on incoming phone call ringtone.
* <p>1 = apply ramping ringer
* <p>0 = do not apply ramping ringer
@@ -11233,6 +11346,7 @@ public final class Settings {
* <li>{@link BatteryManager#BATTERY_PLUGGED_AC} to stay on for AC charger</li>
* <li>{@link BatteryManager#BATTERY_PLUGGED_USB} to stay on for USB charger</li>
* <li>{@link BatteryManager#BATTERY_PLUGGED_WIRELESS} to stay on for wireless charger</li>
+ * <li>{@link BatteryManager#BATTERY_PLUGGED_DOCK} to stay on for dock charger</li>
* </ul>
* These values can be OR-ed together.
*/
diff --git a/core/java/android/service/dreams/DreamActivity.java b/core/java/android/service/dreams/DreamActivity.java
index bf64d06d4ff0..f6a7c8eb8c4b 100644
--- a/core/java/android/service/dreams/DreamActivity.java
+++ b/core/java/android/service/dreams/DreamActivity.java
@@ -21,8 +21,6 @@ import android.app.Activity;
import android.os.Bundle;
import android.text.TextUtils;
-import com.android.internal.R;
-
/**
* The Activity used by the {@link DreamService} to draw screensaver content
* on the screen. This activity runs in dream application's process, but is started by a
@@ -66,17 +64,4 @@ public class DreamActivity extends Activity {
callback.onActivityCreated(this);
}
}
-
- @Override
- public void onResume() {
- super.onResume();
- overridePendingTransition(R.anim.dream_activity_open_enter,
- R.anim.dream_activity_open_exit);
- }
-
- @Override
- public void finishAndRemoveTask() {
- super.finishAndRemoveTask();
- overridePendingTransition(0, R.anim.dream_activity_close_exit);
- }
}
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 1e22856c1bde..1df7dbc0cd0b 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -24,7 +24,6 @@ import static android.graphics.Matrix.MSKEW_X;
import static android.graphics.Matrix.MSKEW_Y;
import static android.view.SurfaceControl.METADATA_WINDOW_TYPE;
import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import android.animation.AnimationHandler;
@@ -41,7 +40,6 @@ import android.app.Service;
import android.app.WallpaperColors;
import android.app.WallpaperInfo;
import android.app.WallpaperManager;
-import android.app.WindowConfiguration;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.Intent;
@@ -260,8 +258,6 @@ public abstract class WallpaperService extends Service {
private final Point mLastSurfaceSize = new Point();
private final Matrix mTmpMatrix = new Matrix();
private final float[] mTmpValues = new float[9];
- private final WindowLayout mWindowLayout = new WindowLayout();
- private final Rect mTempRect = new Rect();
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
@@ -1100,8 +1096,7 @@ public abstract class WallpaperService extends Service {
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
final Configuration config = mMergedConfiguration.getMergedConfiguration();
- final WindowConfiguration winConfig = config.windowConfiguration;
- final Rect maxBounds = winConfig.getMaxBounds();
+ final Rect maxBounds = config.windowConfiguration.getMaxBounds();
if (myWidth == ViewGroup.LayoutParams.MATCH_PARENT
&& myHeight == ViewGroup.LayoutParams.MATCH_PARENT) {
mLayout.width = myWidth;
@@ -1139,7 +1134,7 @@ public abstract class WallpaperService extends Service {
if (mSession.addToDisplay(mWindow, mLayout, View.VISIBLE,
mDisplay.getDisplayId(), mRequestedVisibilities, inputChannel,
- mInsetsState, mTempControls) < 0) {
+ mInsetsState, mTempControls, new Rect()) < 0) {
Log.w(TAG, "Failed to add window while updating wallpaper surface.");
return;
}
@@ -1158,29 +1153,9 @@ public abstract class WallpaperService extends Service {
} else {
mLayout.surfaceInsets.set(0, 0, 0, 0);
}
-
- int relayoutResult = 0;
- if (LOCAL_LAYOUT) {
- if (!mSurfaceControl.isValid()) {
- relayoutResult = mSession.updateVisibility(mWindow, mLayout,
- View.VISIBLE, mMergedConfiguration, mSurfaceControl,
- mInsetsState, mTempControls);
- }
-
- final Rect displayCutoutSafe = mTempRect;
- mInsetsState.getDisplayCutoutSafe(displayCutoutSafe);
- mWindowLayout.computeFrames(mLayout, mInsetsState, displayCutoutSafe,
- winConfig.getBounds(), winConfig.getWindowingMode(), mWidth,
- mHeight, mRequestedVisibilities, null /* attachedWindowFrame */,
- 1f /* compatScale */, mWinFrames);
-
- mSession.updateLayout(mWindow, mLayout, 0 /* flags */, mWinFrames, mWidth,
- mHeight);
- } else {
- relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
- View.VISIBLE, 0, mWinFrames, mMergedConfiguration,
- mSurfaceControl, mInsetsState, mTempControls, mSyncSeqIdBundle);
- }
+ final int relayoutResult = mSession.relayout(mWindow, mLayout, mWidth, mHeight,
+ View.VISIBLE, 0, mWinFrames, mMergedConfiguration, mSurfaceControl,
+ mInsetsState, mTempControls, mSyncSeqIdBundle);
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
@@ -1229,7 +1204,7 @@ public abstract class WallpaperService extends Service {
null /* ignoringVisibilityState */, config.isScreenRound(),
false /* alwaysConsumeSystemBars */, mLayout.softInputMode,
mLayout.flags, SYSTEM_UI_FLAG_VISIBLE, mLayout.type,
- winConfig.getWindowingMode(), null /* typeSideMap */);
+ config.windowConfiguration.getWindowingMode(), null /* typeSideMap */);
if (!fixedSize) {
final Rect padding = mIWallpaperEngine.mDisplayPadding;
@@ -1539,8 +1514,9 @@ public abstract class WallpaperService extends Service {
// may have been destroyed so now we need to make
// sure it is re-created.
doOffsetsChanged(false);
- // force relayout to get new surface
- updateSurface(true, false, false);
+ // It will check mSurfaceCreated so no need to force relayout.
+ updateSurface(false /* forceRelayout */, false /* forceReport */,
+ false /* redrawNeeded */);
}
onVisibilityChanged(visible);
if (mReportedVisible && mFrozenRequested) {
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 24ded932b636..195cf82bc36e 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -83,6 +83,16 @@ public class FeatureFlagUtils {
public static final String SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE =
"settings_hide_second_layer_page_navigate_up_button_in_two_pane";
+ /** Flag to enable/disable guest mode UX changes as mentioned in b/214031645
+ * @hide
+ */
+ public static final String SETTINGS_GUEST_MODE_UX_CHANGES = "settings_guest_mode_ux_changes";
+
+ /** Support Clear Calling feature.
+ * @hide
+ */
+ public static final String SETTINGS_ENABLE_CLEAR_CALLING = "settings_enable_clear_calling";
+
private static final Map<String, String> DEFAULT_FLAGS;
static {
@@ -110,6 +120,8 @@ public class FeatureFlagUtils {
DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true");
DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true");
DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
+ DEFAULT_FLAGS.put(SETTINGS_GUEST_MODE_UX_CHANGES, "true");
+ DEFAULT_FLAGS.put(SETTINGS_ENABLE_CLEAR_CALLING, "false");
}
private static final Set<String> PERSISTENT_FLAGS;
diff --git a/core/java/android/util/SafetyProtectionUtils.java b/core/java/android/util/SafetyProtectionUtils.java
new file mode 100644
index 000000000000..af985c573063
--- /dev/null
+++ b/core/java/android/util/SafetyProtectionUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.util;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.provider.DeviceConfig;
+
+/**
+ * Util class for whether we should show the safety protection resources.
+ *
+ * @hide
+ */
+public class SafetyProtectionUtils {
+ private static final String SAFETY_PROTECTION_RESOURCES_ENABLED = "safety_protection_enabled";
+
+ /**
+ * Determines whether we should show the safety protection resources.
+ * We show the resources only if
+ * (1) the feature flag safety_protection_enabled is enabled and
+ * (2) the config value config_safetyProtectionEnabled is enabled/true and
+ * (3) the resources exist (currently the resources only exist on GMS devices)
+ *
+ * TODO: make this an API in U
+ *
+ * @hide
+ */
+ public static boolean shouldShowSafetyProtectionResources(Context context) {
+ return DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+ SAFETY_PROTECTION_RESOURCES_ENABLED, false)
+ && context.getResources().getBoolean(
+ Resources.getSystem()
+ .getIdentifier("config_safetyProtectionEnabled",
+ "bool", "android"))
+ && context.getDrawable(android.R.drawable.ic_safety_protection) != null
+ && context.getString(android.R.string.safety_protection_display_text) != null
+ && !context.getString(android.R.string.safety_protection_display_text).isEmpty();
+ }
+}
diff --git a/core/java/android/view/IDisplayWindowRotationCallback.aidl b/core/java/android/view/IDisplayChangeWindowCallback.aidl
index 1ffe2dde40f1..00a5b7b831ca 100644
--- a/core/java/android/view/IDisplayWindowRotationCallback.aidl
+++ b/core/java/android/view/IDisplayChangeWindowCallback.aidl
@@ -17,13 +17,15 @@
package android.view;
import android.window.WindowContainerTransaction;
+import android.window.DisplayAreaInfo;
/**
- * Interface to be invoked by the controller when it has finished preparing for a display rotation.
+ * Interface to be invoked by the controller when it has finished preparing for a display
+ * size change.
*
- * @see IDisplayWindowRotationController
+ * @see IDisplayChangeWindowController
* @hide
*/
-interface IDisplayWindowRotationCallback {
- void continueRotateDisplay(int targetRotation, in WindowContainerTransaction t);
+interface IDisplayChangeWindowCallback {
+ void continueDisplayChange(in WindowContainerTransaction t);
}
diff --git a/core/java/android/view/IDisplayWindowRotationController.aidl b/core/java/android/view/IDisplayChangeWindowController.aidl
index c1c7464c3168..8c0bb6a54528 100644
--- a/core/java/android/view/IDisplayWindowRotationController.aidl
+++ b/core/java/android/view/IDisplayChangeWindowController.aidl
@@ -16,11 +16,12 @@
package android.view;
-import android.view.IDisplayWindowRotationCallback;
+import android.view.IDisplayChangeWindowCallback;
+import android.window.DisplayAreaInfo;
/**
- * Singular controller of a "remote" display rotation. When a display rotation is started, WM
- * freezes the screen. It will then call into this controller and wait for a response via the
+ * Singular controller of a "remote" display change. When a display rotation or change is started,
+ * WM freezes the screen. It will then call into this controller and wait for a response via the
* callback.
*
* This needs to provide configuration changes because those changes need to be applied in sync
@@ -36,17 +37,18 @@ import android.view.IDisplayWindowRotationCallback;
*
* @hide
*/
-oneway interface IDisplayWindowRotationController {
+oneway interface IDisplayChangeWindowController {
/**
- * Called when WM needs to know how to update tasks in response to a display rotation.
- * If this isn't called, a timeout will continue the rotation in WM.
+ * Called when WM needs to know how to update tasks in response to a display change.
+ * If this isn't called, a timeout will continue the change in WM.
*
- * @param displayId the display that is rotating.
- * @param fromRotation the rotation the display is rotating from.
- * @param toRotation the rotation the display is rotating to.
+ * @param fromRotation the old rotation
+ * @param newRotation the new rotation
+ * @param newDisplayAreaInfo the new display area info after the change
* @param callback A callback to be called when this has calculated updated configs.
*/
- void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- in IDisplayWindowRotationCallback callback);
+ void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ in DisplayAreaInfo newDisplayAreaInfo, in IDisplayChangeWindowCallback callback);
+
}
diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl
index f4a0dfaa4432..1940042a1052 100644
--- a/core/java/android/view/IDisplayWindowInsetsController.aidl
+++ b/core/java/android/view/IDisplayWindowInsetsController.aidl
@@ -16,6 +16,7 @@
package android.view;
+import android.content.ComponentName;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
@@ -30,10 +31,11 @@ oneway interface IDisplayWindowInsetsController {
/**
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
- * @param packageName: Passes the top package name
+ * @param component: Passes the top application component in the focused window.
* @param requestedVisibilities The insets visibilities requested by the focussed window.
*/
- void topFocusedWindowChanged(String packageName, in InsetsVisibilities insetsVisibilities);
+ void topFocusedWindowChanged(in ComponentName component,
+ in InsetsVisibilities insetsVisibilities);
/**
* @see IWindow#insetsChanged
diff --git a/core/java/android/view/IRecentsAnimationController.aidl b/core/java/android/view/IRecentsAnimationController.aidl
index 61f524f51786..c4d307073d12 100644
--- a/core/java/android/view/IRecentsAnimationController.aidl
+++ b/core/java/android/view/IRecentsAnimationController.aidl
@@ -80,11 +80,6 @@ interface IRecentsAnimationController {
void setAnimationTargetsBehindSystemBars(boolean behindSystemBars);
/**
- * Hides the current input method if one is showing.
- */
- void hideCurrentInputMethod();
-
- /**
* Clean up the screenshot of previous task which was created during recents animation that
* was cancelled by a stack order change.
*
diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl
index fb562d8e97db..acdff4fb52fd 100644
--- a/core/java/android/view/IWindowManager.aidl
+++ b/core/java/android/view/IWindowManager.aidl
@@ -39,7 +39,7 @@ import android.view.ICrossWindowBlurEnabledListener;
import android.view.IDisplayWindowInsetsController;
import android.view.IDisplayWindowListener;
import android.view.IDisplayFoldListener;
-import android.view.IDisplayWindowRotationController;
+import android.view.IDisplayChangeWindowController;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedTaskListener;
import android.view.IScrollCaptureResponseListener;
@@ -145,7 +145,7 @@ interface IWindowManager
* controller is called after the display has "frozen" for a rotation and display rotation will
* only continue once the controller has finished calculating associated configurations.
*/
- void setDisplayWindowRotationController(IDisplayWindowRotationController controller);
+ void setDisplayChangeWindowController(IDisplayChangeWindowController controller);
/**
* Adds a root container that a client shell can populate with its own windows (usually via
@@ -250,18 +250,6 @@ interface IWindowManager
*/
void refreshScreenCaptureDisabled();
- // These can only be called with the SET_ORIENTATION permission.
- /**
- * Update the current screen rotation based on the current state of
- * the world.
- * @param alwaysSendConfiguration Flag to force a new configuration to
- * be evaluated. This can be used when there are other parameters in
- * configuration that are changing.
- * @param forceRelayout If true, the window manager will always do a relayout
- * of its windows even if the rotation hasn't changed.
- */
- void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout);
-
/**
* Retrieve the current orientation of the primary screen.
* @return Constant as per {@link android.view.Surface.Rotation}.
@@ -442,11 +430,6 @@ interface IWindowManager
boolean isSafeModeEnabled();
/**
- * Enables the screen if all conditions are met.
- */
- void enableScreenIfNeeded();
-
- /**
* Clears the frame statistics for a given window.
*
* @param token The window token.
@@ -560,6 +543,21 @@ interface IWindowManager
boolean isWindowTraceEnabled();
/**
+ * Starts a transition trace.
+ */
+ void startTransitionTrace();
+
+ /**
+ * Stops a transition trace.
+ */
+ void stopTransitionTrace();
+
+ /**
+ * Returns true if transition trace is enabled.
+ */
+ boolean isTransitionTraceEnabled();
+
+ /**
* Gets the windowing mode of the display.
*
* @param displayId The id of the display.
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index 649accd1126d..ef57b1ddd4b3 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -50,13 +50,15 @@ interface IWindowSession {
int addToDisplay(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in InsetsVisibilities requestedVisibilities,
out InputChannel outInputChannel, out InsetsState insetsState,
- out InsetsSourceControl[] activeControls);
+ out InsetsSourceControl[] activeControls, out Rect attachedFrame);
int addToDisplayAsUser(IWindow window, in WindowManager.LayoutParams attrs,
in int viewVisibility, in int layerStackId, in int userId,
in InsetsVisibilities requestedVisibilities, out InputChannel outInputChannel,
- out InsetsState insetsState, out InsetsSourceControl[] activeControls);
+ out InsetsState insetsState, out InsetsSourceControl[] activeControls,
+ out Rect attachedFrame);
int addToDisplayWithoutInputChannel(IWindow window, in WindowManager.LayoutParams attrs,
- in int viewVisibility, in int layerStackId, out InsetsState insetsState);
+ in int viewVisibility, in int layerStackId, out InsetsState insetsState,
+ out Rect attachedFrame);
@UnsupportedAppUsage
void remove(IWindow window);
@@ -107,41 +109,6 @@ interface IWindowSession {
out InsetsState insetsState, out InsetsSourceControl[] activeControls,
out Bundle bundle);
- /**
- * Changes the view visibility and the attributes of a window. This should only be called when
- * the visibility of the root view is changed. This returns a valid surface if the root view is
- * visible. This also returns the latest information for the caller to compute its window frame.
- *
- * @param window The window being updated.
- * @param attrs If non-null, new attributes to apply to the window.
- * @param viewVisibility Window root view's visibility.
- * @param outMergedConfiguration New config container that holds global, override and merged
- * config for window, if it is now becoming visible and the merged configuration has changed
- * since it was last displayed.
- * @param outSurfaceControl Object in which is placed the new display surface.
- * @param outInsetsState The current insets state in the system.
- * @param outActiveControls The insets source controls for the caller to override the insets
- * state in the system.
- *
- * @return int Result flags: {@link WindowManagerGlobal#RELAYOUT_FIRST_TIME}.
- */
- int updateVisibility(IWindow window, in WindowManager.LayoutParams attrs, int viewVisibility,
- out MergedConfiguration outMergedConfiguration, out SurfaceControl outSurfaceControl,
- out InsetsState outInsetsState, out InsetsSourceControl[] outActiveControls);
-
- /**
- * Reports the layout results and the attributes of a window to the server.
- *
- * @param window The window being reported.
- * @param attrs If non-null, new attributes to apply to the window.
- * @param flags Request flags: {@link WindowManagerGlobal#RELAYOUT_INSETS_PENDING}.
- * @param clientFrames the window frames computed by the client.
- * @param requestedWidth The width the window wants to be.
- * @param requestedHeight The height the window wants to be.
- */
- oneway void updateLayout(IWindow window, in WindowManager.LayoutParams attrs, int flags,
- in ClientWindowFrames clientFrames, int requestedWidth, int requestedHeight);
-
/*
* Notify the window manager that an application is relaunching and
* windows should be prepared for replacement.
@@ -384,4 +351,9 @@ interface IWindowSession {
* Clears a touchable region set by {@link #setInsets}.
*/
void clearTouchableRegion(IWindow window);
+
+ /**
+ * Returns whether this window needs to cancel draw and retry later.
+ */
+ boolean cancelDraw(IWindow window);
}
diff --git a/core/java/android/view/InsetsFrameProvider.java b/core/java/android/view/InsetsFrameProvider.java
new file mode 100644
index 000000000000..eb8687c47bed
--- /dev/null
+++ b/core/java/android/view/InsetsFrameProvider.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
+
+import android.graphics.Insets;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Insets provided by a window.
+ *
+ * The insets frame will by default as the window frame size. If the providers are set, the
+ * calculation result based on the source size will be used as the insets frame.
+ * @hide
+ */
+public class InsetsFrameProvider implements Parcelable {
+
+ /**
+ * If specified in source field, the insets calculation will be based on the display frame.
+ */
+ public static final int SOURCE_DISPLAY = 0;
+
+ /**
+ * If specified in source field, the insets calculation will be based on the window bounds. The
+ * container bounds can sometimes be different from the window frame. For example, when a task
+ * bar needs the entire screen to be prepared to showing the apps, the window container can take
+ * the entire display, or display area, but the window frame, as a result of the layout, will
+ * stay small until it actually taking the entire display to draw their view.
+ */
+ public static final int SOURCE_CONTAINER_BOUNDS = 1;
+
+ /**
+ * If specified in source field, the insets calculation will be based on the window frame. This
+ * is also the default value of the source.
+ */
+ public static final int SOURCE_FRAME = 2;
+
+ private static final int HAS_INSETS_SIZE = 1;
+ private static final int HAS_INSETS_SIZE_OVERRIDE = 2;
+
+ private static Rect sTmpRect = new Rect();
+
+ /**
+ * The type of insets to provide.
+ */
+ public @InsetsState.InternalInsetsType int type;
+
+ /**
+ * The source of frame. By default, all adjustment will be based on the window frame, it
+ * can be set to window bounds or display bounds instead.
+ */
+ public int source = SOURCE_FRAME;
+
+ /**
+ * The provided insets size based on the source frame. The result will be used as the insets
+ * size to windows other than IME. Only one side should be set.
+ *
+ * For example, when the given source frame is (0, 0) - (100, 200), and the insetsSize is null,
+ * the source frame will be directly used as the final insets frame. If the insetsSize is set to
+ * (0, 0, 0, 50) instead, the insets frame will be a frame starting from the bottom side of the
+ * source frame with height of 50, i.e., (0, 150) - (100, 200).
+ */
+ public Insets insetsSize = null;
+
+ /**
+ * If null, the size set in insetsSize will be applied to all window types. If it contains
+ * element of some types, the insets reported to the window with that types will be overridden.
+ */
+ public InsetsSizeOverride[] insetsSizeOverrides = null;
+
+ public InsetsFrameProvider(int type) {
+ this(type, SOURCE_FRAME, null, null);
+ }
+
+ public InsetsFrameProvider(int type, Insets insetsSize) {
+ this(type, SOURCE_FRAME, insetsSize, null);
+ }
+
+ public InsetsFrameProvider(int type, int source, Insets insetsSize,
+ InsetsSizeOverride[] insetsSizeOverride) {
+ this.type = type;
+ this.source = source;
+ this.insetsSize = insetsSize;
+ this.insetsSizeOverrides = insetsSizeOverride;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(32);
+ sb.append("InsetsFrameProvider: {");
+ sb.append("type=").append(InsetsState.typeToString(type));
+ sb.append(", source=");
+ switch (source) {
+ case SOURCE_DISPLAY:
+ sb.append("SOURCE_DISPLAY");
+ break;
+ case SOURCE_CONTAINER_BOUNDS:
+ sb.append("SOURCE_CONTAINER_BOUNDS Bounds");
+ break;
+ case SOURCE_FRAME:
+ sb.append("SOURCE_FRAME");
+ break;
+ }
+ if (insetsSize != null) {
+ sb.append(", insetsSize=").append(insetsSize);
+ }
+ if (insetsSizeOverrides != null) {
+ sb.append(", insetsSizeOverrides=").append(Arrays.toString(insetsSizeOverrides));
+ }
+ sb.append("}");
+ return sb.toString();
+ }
+
+ public InsetsFrameProvider(Parcel in) {
+ int insetsSizeModified = in.readInt();
+ type = in.readInt();
+ source = in.readInt();
+ if ((insetsSizeModified & HAS_INSETS_SIZE) != 0) {
+ insetsSize = Insets.CREATOR.createFromParcel(in);
+ }
+ if ((insetsSizeModified & HAS_INSETS_SIZE_OVERRIDE) != 0) {
+ insetsSizeOverrides = in.createTypedArray(InsetsSizeOverride.CREATOR);
+ }
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ int insetsSizeModified = 0;
+ if (insetsSize != null) {
+ insetsSizeModified |= HAS_INSETS_SIZE;
+ }
+ if (insetsSizeOverrides != null) {
+ insetsSizeModified |= HAS_INSETS_SIZE_OVERRIDE;
+ }
+ out.writeInt(insetsSizeModified);
+ out.writeInt(type);
+ out.writeInt(source);
+ if (insetsSize != null) {
+ insetsSize.writeToParcel(out, flags);
+ }
+ if (insetsSizeOverrides != null) {
+ out.writeTypedArray(insetsSizeOverrides, flags);
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ InsetsFrameProvider other = (InsetsFrameProvider) o;
+ return type == other.type && source == other.source
+ && Objects.equals(insetsSize, other.insetsSize)
+ && Arrays.equals(insetsSizeOverrides, other.insetsSizeOverrides);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(type, source, insetsSize, Arrays.hashCode(insetsSizeOverrides));
+ }
+
+ public static final @android.annotation.NonNull Parcelable.Creator<InsetsFrameProvider>
+ CREATOR = new Parcelable.Creator<InsetsFrameProvider>() {
+ @Override
+ public InsetsFrameProvider createFromParcel(Parcel in) {
+ return new InsetsFrameProvider(in);
+ }
+
+ @Override
+ public InsetsFrameProvider[] newArray(int size) {
+ return new InsetsFrameProvider[size];
+ }
+ };
+
+ public static void calculateInsetsFrame(Rect displayFrame, Rect containerBounds,
+ Rect displayCutoutSafe, Rect inOutFrame, int source, Insets insetsSize,
+ @WindowManager.LayoutParams.PrivateFlags int privateFlags) {
+ boolean extendByCutout = false;
+ if (source == InsetsFrameProvider.SOURCE_DISPLAY) {
+ inOutFrame.set(displayFrame);
+ } else if (source == InsetsFrameProvider.SOURCE_CONTAINER_BOUNDS) {
+ inOutFrame.set(containerBounds);
+ } else {
+ extendByCutout = (privateFlags & PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT) != 0;
+ }
+ if (insetsSize == null) {
+ return;
+ }
+ // Only one side of the provider shall be applied. Check in the order of left - top -
+ // right - bottom, only the first non-zero value will be applied.
+ if (insetsSize.left != 0) {
+ inOutFrame.right = inOutFrame.left + insetsSize.left;
+ } else if (insetsSize.top != 0) {
+ inOutFrame.bottom = inOutFrame.top + insetsSize.top;
+ } else if (insetsSize.right != 0) {
+ inOutFrame.left = inOutFrame.right - insetsSize.right;
+ } else if (insetsSize.bottom != 0) {
+ inOutFrame.top = inOutFrame.bottom - insetsSize.bottom;
+ } else {
+ inOutFrame.setEmpty();
+ }
+
+ if (extendByCutout) {
+ WindowLayout.extendFrameByCutout(displayCutoutSafe, displayFrame, inOutFrame, sTmpRect);
+ }
+ }
+
+ /**
+ * Class to describe the insets size to be provided to window with specific window type. If not
+ * used, same insets size will be sent as instructed in the insetsSize and source.
+ */
+ public static class InsetsSizeOverride implements Parcelable {
+ public final int windowType;
+ public Insets insetsSize;
+
+ protected InsetsSizeOverride(Parcel in) {
+ windowType = in.readInt();
+ insetsSize = in.readParcelable(null, android.graphics.Insets.class);
+ }
+
+ public InsetsSizeOverride(int type, Insets size) {
+ windowType = type;
+ insetsSize = size;
+ }
+
+ public static final Creator<InsetsSizeOverride> CREATOR =
+ new Creator<InsetsSizeOverride>() {
+ @Override
+ public InsetsSizeOverride createFromParcel(Parcel in) {
+ return new InsetsSizeOverride(in);
+ }
+
+ @Override
+ public InsetsSizeOverride[] newArray(int size) {
+ return new InsetsSizeOverride[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeInt(windowType);
+ out.writeParcelable(insetsSize, flags);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(32);
+ sb.append("TypedInsetsSize: {");
+ sb.append("windowType=").append(windowType);
+ sb.append(", insetsSize=").append(insetsSize);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(windowType, insetsSize);
+ }
+ }
+}
+
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index f6e4f6e7ef2b..c198098cb6ff 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -90,8 +90,10 @@ public class InsetsState implements Parcelable {
ITYPE_IME,
ITYPE_CLIMATE_BAR,
ITYPE_EXTRA_NAVIGATION_BAR,
- ITYPE_LOCAL_NAVIGATION_BAR_1,
- ITYPE_LOCAL_NAVIGATION_BAR_2
+ ITYPE_LEFT_GENERIC_OVERLAY,
+ ITYPE_TOP_GENERIC_OVERLAY,
+ ITYPE_RIGHT_GENERIC_OVERLAY,
+ ITYPE_BOTTOM_GENERIC_OVERLAY
})
public @interface InternalInsetsType {}
@@ -135,10 +137,12 @@ public class InsetsState implements Parcelable {
public static final int ITYPE_EXTRA_NAVIGATION_BAR = 21;
/** Additional types for local insets. **/
- public static final int ITYPE_LOCAL_NAVIGATION_BAR_1 = 22;
- public static final int ITYPE_LOCAL_NAVIGATION_BAR_2 = 23;
+ public static final int ITYPE_LEFT_GENERIC_OVERLAY = 22;
+ public static final int ITYPE_TOP_GENERIC_OVERLAY = 23;
+ public static final int ITYPE_RIGHT_GENERIC_OVERLAY = 24;
+ public static final int ITYPE_BOTTOM_GENERIC_OVERLAY = 25;
- static final int LAST_TYPE = ITYPE_LOCAL_NAVIGATION_BAR_2;
+ static final int LAST_TYPE = ITYPE_BOTTOM_GENERIC_OVERLAY;
public static final int SIZE = LAST_TYPE + 1;
// Derived types
@@ -404,27 +408,20 @@ public class InsetsState implements Parcelable {
if (source == null) {
continue;
}
- if (!canControlSide(frame, getInsetSide(
- source.calculateInsets(frame, true /* ignoreVisibility */)))) {
+ if (!canControlSource(frame, source)) {
blocked |= toPublicType(type);
}
}
return blocked;
}
- private boolean canControlSide(Rect frame, int side) {
- switch (side) {
- case ISIDE_LEFT:
- case ISIDE_RIGHT:
- return frame.left == mDisplayFrame.left && frame.right == mDisplayFrame.right;
- case ISIDE_TOP:
- case ISIDE_BOTTOM:
- return frame.top == mDisplayFrame.top && frame.bottom == mDisplayFrame.bottom;
- case ISIDE_FLOATING:
- return true;
- default:
- return false;
- }
+ private static boolean canControlSource(Rect frame, InsetsSource source) {
+ final Insets insets = source.calculateInsets(frame, true /* ignoreVisibility */);
+ final Rect sourceFrame = source.getFrame();
+ final int sourceWidth = sourceFrame.width();
+ final int sourceHeight = sourceFrame.height();
+ return insets.left == sourceWidth || insets.right == sourceWidth
+ || insets.top == sourceHeight || insets.bottom == sourceHeight;
}
private void processSource(InsetsSource source, Rect relativeFrame, boolean ignoreVisibility,
@@ -705,8 +702,12 @@ public class InsetsState implements Parcelable {
if ((types & Type.NAVIGATION_BARS) != 0) {
result.add(ITYPE_NAVIGATION_BAR);
result.add(ITYPE_EXTRA_NAVIGATION_BAR);
- result.add(ITYPE_LOCAL_NAVIGATION_BAR_1);
- result.add(ITYPE_LOCAL_NAVIGATION_BAR_2);
+ }
+ if ((types & Type.GENERIC_OVERLAYS) != 0) {
+ result.add(ITYPE_LEFT_GENERIC_OVERLAY);
+ result.add(ITYPE_TOP_GENERIC_OVERLAY);
+ result.add(ITYPE_RIGHT_GENERIC_OVERLAY);
+ result.add(ITYPE_BOTTOM_GENERIC_OVERLAY);
}
if ((types & Type.CAPTION_BAR) != 0) {
result.add(ITYPE_CAPTION_BAR);
@@ -747,9 +748,12 @@ public class InsetsState implements Parcelable {
return Type.STATUS_BARS;
case ITYPE_NAVIGATION_BAR:
case ITYPE_EXTRA_NAVIGATION_BAR:
- case ITYPE_LOCAL_NAVIGATION_BAR_1:
- case ITYPE_LOCAL_NAVIGATION_BAR_2:
return Type.NAVIGATION_BARS;
+ case ITYPE_LEFT_GENERIC_OVERLAY:
+ case ITYPE_TOP_GENERIC_OVERLAY:
+ case ITYPE_RIGHT_GENERIC_OVERLAY:
+ case ITYPE_BOTTOM_GENERIC_OVERLAY:
+ return Type.GENERIC_OVERLAYS;
case ITYPE_CAPTION_BAR:
return Type.CAPTION_BAR;
case ITYPE_IME:
@@ -868,10 +872,14 @@ public class InsetsState implements Parcelable {
return "ITYPE_CLIMATE_BAR";
case ITYPE_EXTRA_NAVIGATION_BAR:
return "ITYPE_EXTRA_NAVIGATION_BAR";
- case ITYPE_LOCAL_NAVIGATION_BAR_1:
- return "ITYPE_LOCAL_NAVIGATION_BAR_1";
- case ITYPE_LOCAL_NAVIGATION_BAR_2:
- return "ITYPE_LOCAL_NAVIGATION_BAR_2";
+ case ITYPE_LEFT_GENERIC_OVERLAY:
+ return "ITYPE_LEFT_GENERIC_OVERLAY";
+ case ITYPE_TOP_GENERIC_OVERLAY:
+ return "ITYPE_TOP_GENERIC_OVERLAY";
+ case ITYPE_RIGHT_GENERIC_OVERLAY:
+ return "ITYPE_RIGHT_GENERIC_OVERLAY";
+ case ITYPE_BOTTOM_GENERIC_OVERLAY:
+ return "ITYPE_BOTTOM_GENERIC_OVERLAY";
default:
return "ITYPE_UNKNOWN_" + type;
}
@@ -916,6 +924,11 @@ public class InsetsState implements Parcelable {
if (source == null && otherSource == null) {
continue;
}
+ if (excludeInvisibleImeFrames && i == ITYPE_IME
+ && ((source == null && !otherSource.isVisible())
+ || (otherSource == null && !source.isVisible()))) {
+ continue;
+ }
if (source == null || otherSource == null) {
return false;
}
diff --git a/core/java/android/view/RemoteAccessibilityController.java b/core/java/android/view/RemoteAccessibilityController.java
index 28b567d94e6e..b0911d789fe1 100644
--- a/core/java/android/view/RemoteAccessibilityController.java
+++ b/core/java/android/view/RemoteAccessibilityController.java
@@ -24,6 +24,8 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
+import java.lang.ref.WeakReference;
+
class RemoteAccessibilityController {
private static final String TAG = "RemoteAccessibilityController";
private int mHostId;
@@ -80,12 +82,17 @@ class RemoteAccessibilityController {
/**
* Wrapper of accessibility embedded connection for embedded view hierarchy.
*/
- private final class RemoteAccessibilityEmbeddedConnection implements IBinder.DeathRecipient {
+ private static final class RemoteAccessibilityEmbeddedConnection
+ implements IBinder.DeathRecipient {
+ private final WeakReference<RemoteAccessibilityController> mController;
private final IAccessibilityEmbeddedConnection mConnection;
private final IBinder mLeashToken;
- RemoteAccessibilityEmbeddedConnection(IAccessibilityEmbeddedConnection connection,
+ RemoteAccessibilityEmbeddedConnection(
+ RemoteAccessibilityController controller,
+ IAccessibilityEmbeddedConnection connection,
IBinder leashToken) {
+ mController = new WeakReference<>(controller);
mConnection = connection;
mLeashToken = leashToken;
}
@@ -109,9 +116,13 @@ class RemoteAccessibilityController {
@Override
public void binderDied() {
unlinkToDeath();
- runOnUiThread(() -> {
- if (mConnectionWrapper == this) {
- mConnectionWrapper = null;
+ RemoteAccessibilityController controller = mController.get();
+ if (controller == null) {
+ return;
+ }
+ controller.runOnUiThread(() -> {
+ if (controller.mConnectionWrapper == this) {
+ controller.mConnectionWrapper = null;
}
});
}
@@ -128,7 +139,7 @@ class RemoteAccessibilityController {
}
if (connection != null && leashToken != null) {
mConnectionWrapper =
- new RemoteAccessibilityEmbeddedConnection(connection, leashToken);
+ new RemoteAccessibilityEmbeddedConnection(this, connection, leashToken);
mConnectionWrapper.linkToDeath();
}
} catch (RemoteException e) {
diff --git a/core/java/android/view/RemoteAnimationTarget.java b/core/java/android/view/RemoteAnimationTarget.java
index e98d046e8c6c..a468951c5020 100644
--- a/core/java/android/view/RemoteAnimationTarget.java
+++ b/core/java/android/view/RemoteAnimationTarget.java
@@ -223,6 +223,12 @@ public class RemoteAnimationTarget implements Parcelable {
public boolean hasAnimatingParent;
/**
+ * Whether an activity has enabled {@link android.R.styleable#Animation_showBackdrop} for
+ * transition.
+ */
+ public boolean showBackdrop;
+
+ /**
* The background color of animation in case the task info is not available if the transition
* is activity level.
*/
@@ -287,6 +293,11 @@ public class RemoteAnimationTarget implements Parcelable {
windowType = in.readInt();
hasAnimatingParent = in.readBoolean();
backgroundColor = in.readInt();
+ showBackdrop = in.readBoolean();
+ }
+
+ public void setShowBackdrop(boolean shouldShowBackdrop) {
+ showBackdrop = shouldShowBackdrop;
}
@Override
@@ -316,6 +327,7 @@ public class RemoteAnimationTarget implements Parcelable {
dest.writeInt(windowType);
dest.writeBoolean(hasAnimatingParent);
dest.writeInt(backgroundColor);
+ dest.writeBoolean(showBackdrop);
}
public void dump(PrintWriter pw, String prefix) {
@@ -337,6 +349,7 @@ public class RemoteAnimationTarget implements Parcelable {
pw.print(prefix); pw.print("windowType="); pw.print(windowType);
pw.print(prefix); pw.print("hasAnimatingParent="); pw.print(hasAnimatingParent);
pw.print(prefix); pw.print("backgroundColor="); pw.print(backgroundColor);
+ pw.print(prefix); pw.print("showBackdrop="); pw.print(showBackdrop);
}
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
diff --git a/core/java/android/view/ScrollCaptureConnection.java b/core/java/android/view/ScrollCaptureConnection.java
index d70de74ee75f..c50f70a08063 100644
--- a/core/java/android/view/ScrollCaptureConnection.java
+++ b/core/java/android/view/ScrollCaptureConnection.java
@@ -214,7 +214,7 @@ public class ScrollCaptureConnection extends IScrollCaptureConnection.Stub imple
@BinderThread
@Override
- public void close() {
+ public synchronized void close() {
Trace.instantForTrack(TRACE_TAG_GRAPHICS, TRACE_TRACK, "close");
if (mActive) {
Log.w(TAG, "close(): capture session still active! Ending now.");
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index a1ce39e974e3..92bdfdd4c558 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -121,6 +121,7 @@ public final class SurfaceControl implements Parcelable {
private static native void nativeSetAnimationTransaction(long transactionObj);
private static native void nativeSetEarlyWakeupStart(long transactionObj);
private static native void nativeSetEarlyWakeupEnd(long transactionObj);
+ private static native long nativeGetTransactionId(long transactionObj);
private static native void nativeSetLayer(long transactionObj, long nativeObject, int zorder);
private static native void nativeSetRelativeLayer(long transactionObj, long nativeObject,
@@ -3537,6 +3538,15 @@ public final class SurfaceControl implements Parcelable {
}
/**
+ * @hide
+ * @return The transaction's current id.
+ * The id changed every time the transaction is applied.
+ */
+ public long getId() {
+ return nativeGetTransactionId(mNativeObject);
+ }
+
+ /**
* Sets an arbitrary piece of metadata on the surface. This is a helper for int data.
* @hide
*/
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index f7bca5bfe188..d75ff2fc7dc2 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -29,8 +29,6 @@ import android.os.Parcelable;
import android.os.RemoteException;
import android.util.Log;
import android.window.WindowTokenClient;
-import android.view.InsetsState;
-import android.view.WindowManagerGlobal;
import android.view.accessibility.IAccessibilityEmbeddedConnection;
import java.util.Objects;
@@ -280,7 +278,7 @@ public class SurfaceControlViewHost {
public SurfaceControlViewHost(@NonNull Context c, @NonNull Display d,
@NonNull WindowlessWindowManager wwm, boolean useSfChoreographer) {
mWm = wwm;
- mViewRoot = new ViewRootImpl(c, d, mWm, useSfChoreographer);
+ mViewRoot = new ViewRootImpl(c, d, mWm, new WindowlessWindowLayout(), useSfChoreographer);
addConfigCallback(c, d);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
@@ -310,7 +308,7 @@ public class SurfaceControlViewHost {
mWm = new WindowlessWindowManager(context.getResources().getConfiguration(),
mSurfaceControl, hostToken);
- mViewRoot = new ViewRootImpl(context, display, mWm);
+ mViewRoot = new ViewRootImpl(context, display, mWm, new WindowlessWindowLayout());
addConfigCallback(context, display);
WindowManagerGlobal.getInstance().addWindowlessRoot(mViewRoot);
diff --git a/core/java/android/view/ThreadedRenderer.java b/core/java/android/view/ThreadedRenderer.java
index 785735c2b1e1..c97eb73c120b 100644
--- a/core/java/android/view/ThreadedRenderer.java
+++ b/core/java/android/view/ThreadedRenderer.java
@@ -25,11 +25,11 @@ import android.graphics.BLASTBufferQueue;
import android.graphics.FrameInfo;
import android.graphics.HardwareRenderer;
import android.graphics.Picture;
-import android.graphics.Point;
import android.graphics.RecordingCanvas;
import android.graphics.Rect;
import android.graphics.RenderNode;
import android.os.Trace;
+import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Surface.OutOfResourcesException;
import android.view.View.AttachInfo;
@@ -596,11 +596,18 @@ public final class ThreadedRenderer extends HardwareRenderer {
*/
void setLightCenter(AttachInfo attachInfo) {
// Adjust light position for window offsets.
- final Point displaySize = attachInfo.mPoint;
- attachInfo.mDisplay.getRealSize(displaySize);
- final float lightX = displaySize.x / 2f - attachInfo.mWindowLeft;
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ attachInfo.mDisplay.getRealMetrics(displayMetrics);
+ final float lightX = displayMetrics.widthPixels / 2f - attachInfo.mWindowLeft;
final float lightY = mLightY - attachInfo.mWindowTop;
- setLightSourceGeometry(lightX, lightY, mLightZ, mLightRadius);
+ // To prevent shadow distortion on larger screens, scale the z position of the light source
+ // relative to the smallest screen dimension.
+ final float zRatio = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels)
+ / (450f * displayMetrics.density);
+ final float zWeightedAdjustment = (zRatio + 2) / 3f;
+ final float lightZ = mLightZ * zWeightedAdjustment;
+
+ setLightSourceGeometry(lightX, lightY, lightZ, mLightRadius);
}
/**
@@ -849,12 +856,18 @@ public final class ThreadedRenderer extends HardwareRenderer {
public void setLightCenter(final Display display,
final int windowLeft, final int windowTop) {
// Adjust light position for window offsets.
- final Point displaySize = new Point();
- display.getRealSize(displaySize);
- final float lightX = displaySize.x / 2f - windowLeft;
+ DisplayMetrics displayMetrics = new DisplayMetrics();
+ display.getRealMetrics(displayMetrics);
+ final float lightX = displayMetrics.widthPixels / 2f - windowLeft;
final float lightY = mLightY - windowTop;
-
- setLightSourceGeometry(lightX, lightY, mLightZ, mLightRadius);
+ // To prevent shadow distortion on larger screens, scale the z position of the light
+ // source relative to the smallest screen dimension.
+ final float zRatio = Math.min(displayMetrics.widthPixels, displayMetrics.heightPixels)
+ / (450f * displayMetrics.density);
+ final float zWeightedAdjustment = (zRatio + 2) / 3f;
+ final float lightZ = mLightZ * zWeightedAdjustment;
+
+ setLightSourceGeometry(lightX, lightY, lightZ, mLightRadius);
}
public RenderNode getRootNode() {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 12331bc9d6cb..3347163a1683 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -60,13 +60,11 @@ import static android.view.WindowInsetsController.BEHAVIOR_DEFAULT;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
-import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_FULLSCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION;
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
-import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_APPEARANCE_CONTROLLED;
@@ -77,13 +75,12 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INSET_PARENT_
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
@@ -564,9 +561,7 @@ public final class ViewRootImpl implements ViewParent,
private final Rect mVisRect = new Rect(); // used to retrieve visible rect of focused view.
private final Rect mTempRect = new Rect();
- private final WindowLayout mWindowLayout = new WindowLayout();
-
- private ViewRootImpl mParentViewRoot;
+ private final WindowLayout mWindowLayout;
// This is used to reduce the race between window focus changes being dispatched from
// the window manager and input events coming through the input system.
@@ -601,9 +596,20 @@ public final class ViewRootImpl implements ViewParent,
*/
private boolean mSyncBuffer = false;
+ /**
+ * Flag to determine whether the client needs to check with WMS if it can draw. WMS will notify
+ * the client that it can't draw if we're still in the middle of a sync set that includes this
+ * window. Once the sync is complete, the window can resume drawing. This is to ensure we don't
+ * deadlock the client by trying to request draws when there may not be any buffers available.
+ */
+ private boolean mCheckIfCanDraw = false;
+
+ private boolean mDrewOnceForSync = false;
+
int mSyncSeqId = 0;
int mLastSyncSeqId = 0;
+ private boolean mUpdateSurfaceNeeded;
boolean mFullRedrawNeeded;
boolean mNewSurfaceNeeded;
boolean mForceNextWindowRelayout;
@@ -881,18 +887,20 @@ public final class ViewRootImpl implements ViewParent,
private String mTag = TAG;
public ViewRootImpl(Context context, Display display) {
- this(context, display, WindowManagerGlobal.getWindowSession(),
+ this(context, display, WindowManagerGlobal.getWindowSession(), new WindowLayout(),
false /* useSfChoreographer */);
}
- public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session) {
- this(context, display, session, false /* useSfChoreographer */);
+ public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
+ WindowLayout windowLayout) {
+ this(context, display, session, windowLayout, false /* useSfChoreographer */);
}
public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session,
- boolean useSfChoreographer) {
+ WindowLayout windowLayout, boolean useSfChoreographer) {
mContext = context;
mWindowSession = session;
+ mWindowLayout = windowLayout;
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
@@ -1183,7 +1191,6 @@ public final class ViewRootImpl implements ViewParent,
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
- mParentViewRoot = panelParentView.getViewRootImpl();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
@@ -1214,14 +1221,21 @@ public final class ViewRootImpl implements ViewParent,
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
controlInsetsForCompatibility(mWindowAttributes);
+
+ Rect attachedFrame = new Rect();
res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId,
mInsetsController.getRequestedVisibilities(), inputChannel, mTempInsets,
- mTempControls);
+ mTempControls, attachedFrame);
+ if (!attachedFrame.isValid()) {
+ attachedFrame = null;
+ }
if (mTranslator != null) {
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
+ mTmpFrames.attachedFrame = attachedFrame;
} catch (RemoteException e) {
mAdded = false;
mView = null;
@@ -1248,8 +1262,8 @@ public final class ViewRootImpl implements ViewParent,
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
- mInsetsController.getRequestedVisibilities(),
- getAttachedWindowFrame(), 1f /* compactScale */, mTmpFrames);
+ mInsetsController.getRequestedVisibilities(), 1f /* compactScale */,
+ mTmpFrames);
setFrame(mTmpFrames.frame);
registerBackCallbackOnWindow();
if (!WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) {
@@ -1374,14 +1388,6 @@ public final class ViewRootImpl implements ViewParent,
}
}
- private Rect getAttachedWindowFrame() {
- final int type = mWindowAttributes.type;
- final boolean layoutAttached = (mParentViewRoot != null
- && type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW
- && type != TYPE_APPLICATION_ATTACHED_DIALOG);
- return layoutAttached ? mParentViewRoot.mWinFrame : null;
- }
-
/**
* Register any kind of listeners if setView was success.
*/
@@ -1739,16 +1745,20 @@ public final class ViewRootImpl implements ViewParent,
final Rect frame = frames.frame;
final Rect displayFrame = frames.displayFrame;
+ final Rect attachedFrame = frames.attachedFrame;
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(frame);
mTranslator.translateRectInScreenToAppWindow(displayFrame);
+ mTranslator.translateRectInScreenToAppWindow(attachedFrame);
}
final boolean frameChanged = !mWinFrame.equals(frame);
final boolean configChanged = !mLastReportedMergedConfiguration.equals(mergedConfiguration);
+ final boolean attachedFrameChanged = LOCAL_LAYOUT
+ && !Objects.equals(mTmpFrames.attachedFrame, attachedFrame);
final boolean displayChanged = mDisplay.getDisplayId() != displayId;
final boolean resizeModeChanged = mResizeMode != resizeMode;
- if (msg == MSG_RESIZED && !frameChanged && !configChanged && !displayChanged
- && !resizeModeChanged && !forceNextWindowRelayout) {
+ if (msg == MSG_RESIZED && !frameChanged && !configChanged && !attachedFrameChanged
+ && !displayChanged && !resizeModeChanged && !forceNextWindowRelayout) {
return;
}
@@ -1766,6 +1776,9 @@ public final class ViewRootImpl implements ViewParent,
setFrame(frame);
mTmpFrames.displayFrame.set(displayFrame);
+ if (mTmpFrames.attachedFrame != null && attachedFrame != null) {
+ mTmpFrames.attachedFrame.set(attachedFrame);
+ }
if (mDragResizing && mUseMTRenderer) {
boolean fullscreen = frame.equals(mPendingBackDropFrame);
@@ -1775,7 +1788,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
- mForceNextWindowRelayout = forceNextWindowRelayout;
+ mForceNextWindowRelayout |= forceNextWindowRelayout;
mPendingAlwaysConsumeSystemBars = args.argi2 != 0;
mSyncSeqId = args.argi4 > mSyncSeqId ? args.argi4 : mSyncSeqId;
@@ -2699,6 +2712,9 @@ public final class ViewRootImpl implements ViewParent,
mIsInTraversal = true;
mWillDrawSoon = true;
+ boolean cancelDraw = false;
+ boolean isSyncRequest = false;
+
boolean windowSizeMayChange = false;
WindowManager.LayoutParams lp = mWindowAttributes;
@@ -2787,6 +2803,7 @@ public final class ViewRootImpl implements ViewParent,
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
+ mAttachInfo.mTreeObserver.dispatchOnWindowVisibilityChange(viewVisibility);
if (viewUserVisibilityChanged) {
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
@@ -2913,7 +2930,6 @@ public final class ViewRootImpl implements ViewParent,
final int surfaceGenerationId = mSurface.getGenerationId();
final boolean isViewVisible = viewVisibility == View.VISIBLE;
- final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
boolean surfaceSizeChanged = false;
boolean surfaceCreated = false;
boolean surfaceDestroyed = false;
@@ -2977,6 +2993,8 @@ public final class ViewRootImpl implements ViewParent,
mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
}
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
+ cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
+ == RELAYOUT_RES_CANCEL_AND_REDRAW;
final boolean dragResizing = mPendingDragResizing;
if (mSyncSeqId > mLastSyncSeqId) {
mLastSyncSeqId = mSyncSeqId;
@@ -2985,6 +3003,10 @@ public final class ViewRootImpl implements ViewParent,
}
reportNextDraw();
mSyncBuffer = true;
+ isSyncRequest = true;
+ if (!cancelDraw) {
+ mDrewOnceForSync = false;
+ }
}
final boolean surfaceControlChanged =
@@ -3017,6 +3039,8 @@ public final class ViewRootImpl implements ViewParent,
!mFirst, INVALID_DISPLAY /* same display */);
updatedConfiguration = true;
}
+ final boolean updateSurfaceNeeded = mUpdateSurfaceNeeded;
+ mUpdateSurfaceNeeded = false;
surfaceSizeChanged = false;
if (!mLastSurfaceSize.equals(mSurfaceSize)) {
@@ -3095,8 +3119,7 @@ public final class ViewRootImpl implements ViewParent,
if (isHardwareEnabled()) {
mAttachInfo.mThreadedRenderer.destroy();
}
- } else if ((surfaceReplaced
- || surfaceSizeChanged || windowRelayoutWasForced)
+ } else if ((surfaceReplaced || surfaceSizeChanged || updateSurfaceNeeded)
&& mSurfaceHolder == null
&& mAttachInfo.mThreadedRenderer != null
&& mSurface.isValid()) {
@@ -3272,6 +3295,19 @@ public final class ViewRootImpl implements ViewParent,
}
}
} else {
+ // If a relayout isn't going to happen, we still need to check if this window can draw
+ // when mCheckIfCanDraw is set. This is because it means we had a sync in the past, but
+ // have not been told by WMS that the sync is complete and that we can continue to draw
+ if (mCheckIfCanDraw) {
+ try {
+ cancelDraw = mWindowSession.cancelDraw(mWindow);
+ if (DEBUG_BLAST) {
+ Log.d(mTag, "cancelDraw returned " + cancelDraw);
+ }
+ } catch (RemoteException e) {
+ }
+ }
+
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
@@ -3490,9 +3526,13 @@ public final class ViewRootImpl implements ViewParent,
reportNextDraw();
}
- boolean cancelAndRedraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
+ mCheckIfCanDraw = isSyncRequest || cancelDraw;
+
+ boolean cancelAndRedraw =
+ mAttachInfo.mTreeObserver.dispatchOnPreDraw() || (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
createSyncIfNeeded();
+ mDrewOnceForSync = true;
}
if (!isViewVisible) {
@@ -3721,6 +3761,7 @@ public final class ViewRootImpl implements ViewParent,
}
}
mFirstInputStage.onWindowFocusChanged(hasWindowFocus);
+ mOnBackInvokedDispatcher.onWindowFocusChanged(hasWindowFocus);
// NOTE: there's no view visibility (appeared / disapparead) events when the windows focus
// is lost, so we don't need to to force a flush - there might be other events such as
@@ -5228,6 +5269,21 @@ public final class ViewRootImpl implements ViewParent,
throw new IllegalArgumentException("No merged config provided.");
}
+ final int lastRotation = mLastReportedMergedConfiguration.getMergedConfiguration()
+ .windowConfiguration.getRotation();
+ final int newRotation = mergedConfiguration.getMergedConfiguration()
+ .windowConfiguration.getRotation();
+ if (lastRotation != newRotation) {
+ // Trigger ThreadedRenderer#updateSurface() if the surface control doesn't change.
+ // Because even if the actual surface size is not changed, e.g. flip 180 degrees,
+ // the buffers may still have content in previous rotation. And the next draw may
+ // not update all regions, that causes some afterimages to flicker.
+ mUpdateSurfaceNeeded = true;
+ if (!mIsInTraversal) {
+ mForceNextWindowRelayout = true;
+ }
+ }
+
Configuration globalConfig = mergedConfiguration.getGlobalConfiguration();
final Configuration overrideConfig = mergedConfiguration.getOverrideConfiguration();
if (DEBUG_CONFIGURATION) Log.v(mTag,
@@ -8000,69 +8056,20 @@ public final class ViewRootImpl implements ViewParent,
final int requestedWidth = (int) (mView.getMeasuredWidth() * appScale + 0.5f);
final int requestedHeight = (int) (mView.getMeasuredHeight() * appScale + 0.5f);
- int relayoutResult = 0;
- WindowConfiguration winConfig = getConfiguration().windowConfiguration;
- if (LOCAL_LAYOUT) {
- if (mFirst || viewVisibility != mViewVisibility) {
- relayoutResult = mWindowSession.updateVisibility(mWindow, params, viewVisibility,
- mPendingMergedConfiguration, mSurfaceControl, mTempInsets, mTempControls);
- if (mTranslator != null) {
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
- }
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
-
- mPendingAlwaysConsumeSystemBars =
- (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
- }
- final InsetsState state = mInsetsController.getState();
- final Rect displayCutoutSafe = mTempRect;
- state.getDisplayCutoutSafe(displayCutoutSafe);
- if (mWindowAttributes.type == TYPE_APPLICATION_STARTING) {
- // TODO(b/210378379): Remove the special logic.
- // Letting starting window use the window bounds from the pending config is for the
- // fixed rotation, because the config is not overridden before the starting window
- // is created.
- winConfig = mPendingMergedConfiguration.getMergedConfiguration()
- .windowConfiguration;
- }
- mWindowLayout.computeFrames(mWindowAttributes, state, displayCutoutSafe,
- winConfig.getBounds(), winConfig.getWindowingMode(), requestedWidth,
- requestedHeight, mInsetsController.getRequestedVisibilities(),
- getAttachedWindowFrame(), 1f /* compatScale */, mTmpFrames);
-
- mWindowSession.updateLayout(mWindow, params,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mTmpFrames,
- requestedWidth, requestedHeight);
-
- } else {
- relayoutResult = mWindowSession.relayout(mWindow, params,
- requestedWidth, requestedHeight, viewVisibility,
- insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
- mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
- mTempControls, mRelayoutBundle);
- final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
- if (maybeSyncSeqId > 0) {
- mSyncSeqId = maybeSyncSeqId;
- }
-
- if (mTranslator != null) {
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
- mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
- mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
- mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
- }
- mInsetsController.onStateChanged(mTempInsets);
- mInsetsController.onControlsChanged(mTempControls);
-
- mPendingAlwaysConsumeSystemBars =
- (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
+ int relayoutResult = mWindowSession.relayout(mWindow, params,
+ requestedWidth, requestedHeight, viewVisibility,
+ insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
+ mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
+ mTempControls, mRelayoutBundle);
+ final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
+ if (maybeSyncSeqId > 0) {
+ mSyncSeqId = maybeSyncSeqId;
}
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplayInstallOrientation + mDisplay.getRotation()) % 4);
+ final WindowConfiguration winConfig = getConfiguration().windowConfiguration;
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
requestedHeight, mTmpFrames.frame, mPendingDragResizing, mSurfaceSize);
@@ -8111,10 +8118,23 @@ public final class ViewRootImpl implements ViewParent,
destroySurface();
}
+ mPendingAlwaysConsumeSystemBars =
+ (relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
+
if (restore) {
params.restore();
}
+
+ if (mTranslator != null) {
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
+ mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
+ mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
+ mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls);
+ }
setFrame(mTmpFrames.frame);
+ mInsetsController.onStateChanged(mTempInsets);
+ mInsetsController.onControlsChanged(mTempControls);
return relayoutResult;
}
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 5a99ab2f5bdb..ed8350afc109 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -43,6 +43,7 @@ public final class ViewTreeObserver {
// Recursive listeners use CopyOnWriteArrayList
private CopyOnWriteArrayList<OnWindowFocusChangeListener> mOnWindowFocusListeners;
private CopyOnWriteArrayList<OnWindowAttachListener> mOnWindowAttachListeners;
+ private CopyOnWriteArrayList<OnWindowVisibilityChangeListener> mOnWindowVisibilityListeners;
private CopyOnWriteArrayList<OnGlobalFocusChangeListener> mOnGlobalFocusListeners;
@UnsupportedAppUsage
private CopyOnWriteArrayList<OnTouchModeChangeListener> mOnTouchModeChangeListeners;
@@ -106,6 +107,21 @@ public final class ViewTreeObserver {
}
/**
+ * Interface definition for a callback to be invoked when the view hierarchy's window
+ * visibility changes.
+ *
+ * @hide
+ */
+ public interface OnWindowVisibilityChangeListener {
+ /**
+ * Callback method to be invoked when the window visibility changes in the view tree.
+ *
+ * @param visibility The new visibility of the window.
+ */
+ void onWindowVisibilityChanged(@View.Visibility int visibility);
+ }
+
+ /**
* Interface definition for a callback to be invoked when the focus state within
* the view tree changes.
*/
@@ -386,6 +402,14 @@ public final class ViewTreeObserver {
}
}
+ if (observer.mOnWindowVisibilityListeners != null) {
+ if (mOnWindowVisibilityListeners != null) {
+ mOnWindowVisibilityListeners.addAll(observer.mOnWindowVisibilityListeners);
+ } else {
+ mOnWindowVisibilityListeners = observer.mOnWindowVisibilityListeners;
+ }
+ }
+
if (observer.mOnGlobalFocusListeners != null) {
if (mOnGlobalFocusListeners != null) {
mOnGlobalFocusListeners.addAll(observer.mOnGlobalFocusListeners);
@@ -540,6 +564,49 @@ public final class ViewTreeObserver {
}
/**
+ * Register a callback to be invoked when the window visibility changes.
+ *
+ * @param listener The callback to add
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @hide
+ */
+ public void addOnWindowVisibilityChangeListener(
+ @NonNull OnWindowVisibilityChangeListener listener) {
+ checkIsAlive();
+
+ if (mOnWindowVisibilityListeners == null) {
+ mOnWindowVisibilityListeners =
+ new CopyOnWriteArrayList<OnWindowVisibilityChangeListener>();
+ }
+
+ mOnWindowVisibilityListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed window visibility callback.
+ *
+ * @param victim The callback to remove
+ *
+ * @throws IllegalStateException If {@link #isAlive()} returns false
+ *
+ * @see #addOnWindowVisibilityChangeListener(
+ * android.view.ViewTreeObserver.OnWindowVisibilityChangeListener)
+ *
+ * @hide
+ */
+ public void removeOnWindowVisibilityChangeListener(
+ @NonNull OnWindowVisibilityChangeListener victim) {
+ checkIsAlive();
+ if (mOnWindowVisibilityListeners == null) {
+ return;
+ }
+
+ mOnWindowVisibilityListeners.remove(victim);
+ }
+
+ /*
* Register a callback to be invoked when the focus state within the view tree changes.
*
* @param listener The callback to add
@@ -1026,6 +1093,23 @@ public final class ViewTreeObserver {
}
/**
+ * Notifies registered listeners that window visibility has changed.
+ */
+ void dispatchOnWindowVisibilityChange(int visibility) {
+ // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
+ // perform the dispatching. The iterator is a safe guard against listeners that
+ // could mutate the list by calling the various add/remove methods. This prevents
+ // the array from being modified while we iterate it.
+ final CopyOnWriteArrayList<OnWindowVisibilityChangeListener> listeners =
+ mOnWindowVisibilityListeners;
+ if (listeners != null && listeners.size() > 0) {
+ for (OnWindowVisibilityChangeListener listener : listeners) {
+ listener.onWindowVisibilityChanged(visibility);
+ }
+ }
+ }
+
+ /**
* Notifies registered listeners that focus has changed.
*/
@UnsupportedAppUsage
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index c846175699f2..c1dddbee408f 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -1423,9 +1423,11 @@ public final class WindowInsets {
static final int DISPLAY_CUTOUT = 1 << 7;
- static final int LAST = 1 << 8;
- static final int SIZE = 9;
- static final int WINDOW_DECOR = LAST;
+ static final int WINDOW_DECOR = 1 << 8;
+
+ static final int GENERIC_OVERLAYS = 1 << 9;
+ static final int LAST = GENERIC_OVERLAYS;
+ static final int SIZE = 10;
static int indexOf(@InsetsType int type) {
switch (type) {
@@ -1447,6 +1449,8 @@ public final class WindowInsets {
return 7;
case WINDOW_DECOR:
return 8;
+ case GENERIC_OVERLAYS:
+ return 9;
default:
throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+ " type=" + type);
@@ -1482,6 +1486,9 @@ public final class WindowInsets {
if ((types & WINDOW_DECOR) != 0) {
result.append("windowDecor |");
}
+ if ((types & GENERIC_OVERLAYS) != 0) {
+ result.append("genericOverlays |");
+ }
if (result.length() > 0) {
result.delete(result.length() - 2, result.length());
}
@@ -1494,7 +1501,8 @@ public final class WindowInsets {
/** @hide */
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {STATUS_BARS, NAVIGATION_BARS, CAPTION_BAR, IME, WINDOW_DECOR,
- SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT})
+ SYSTEM_GESTURES, MANDATORY_SYSTEM_GESTURES, TAPPABLE_ELEMENT, DISPLAY_CUTOUT,
+ GENERIC_OVERLAYS})
public @interface InsetsType {
}
@@ -1586,7 +1594,7 @@ public final class WindowInsets {
* {@link #navigationBars()}, but not {@link #ime()}.
*/
public static @InsetsType int systemBars() {
- return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR;
+ return STATUS_BARS | NAVIGATION_BARS | CAPTION_BAR | GENERIC_OVERLAYS;
}
/**
diff --git a/core/java/android/view/WindowLayout.java b/core/java/android/view/WindowLayout.java
index c320b262ebd7..57a0330e3c18 100644
--- a/core/java/android/view/WindowLayout.java
+++ b/core/java/android/view/WindowLayout.java
@@ -16,8 +16,6 @@
package android.view;
-import static android.view.Gravity.DISPLAY_CLIP_HORIZONTAL;
-import static android.view.Gravity.DISPLAY_CLIP_VERTICAL;
import static android.view.InsetsState.ITYPE_IME;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.ITYPE_STATUS_BAR;
@@ -66,14 +64,15 @@ public class WindowLayout {
public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
- Rect attachedWindowFrame, float compatScale, ClientWindowFrames outFrames) {
+ float compatScale, ClientWindowFrames frames) {
final int type = attrs.type;
final int fl = attrs.flags;
final int pfl = attrs.privateFlags;
final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
- final Rect outDisplayFrame = outFrames.displayFrame;
- final Rect outParentFrame = outFrames.parentFrame;
- final Rect outFrame = outFrames.frame;
+ final Rect attachedWindowFrame = frames.attachedFrame;
+ final Rect outDisplayFrame = frames.displayFrame;
+ final Rect outParentFrame = frames.parentFrame;
+ final Rect outFrame = frames.frame;
// Compute bounds restricted by insets
final Insets insets = state.calculateInsets(windowBounds, attrs.getFitInsetsTypes(),
@@ -104,7 +103,7 @@ public class WindowLayout {
final DisplayCutout cutout = state.getDisplayCutout();
final Rect displayCutoutSafeExceptMaybeBars = mTempDisplayCutoutSafeExceptMaybeBarsRect;
displayCutoutSafeExceptMaybeBars.set(displayCutoutSafe);
- outFrames.isParentFrameClippedByDisplayCutout = false;
+ frames.isParentFrameClippedByDisplayCutout = false;
if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS && !cutout.isEmpty()) {
// Ensure that windows with a non-ALWAYS display cutout mode are laid out in
// the cutout safe zone.
@@ -167,7 +166,7 @@ public class WindowLayout {
if (!attachedInParent && !floatingInScreenWindow) {
mTempRect.set(outParentFrame);
outParentFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
- outFrames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
+ frames.isParentFrameClippedByDisplayCutout = !mTempRect.equals(outParentFrame);
}
outDisplayFrame.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
}
@@ -274,25 +273,14 @@ public class WindowLayout {
Gravity.applyDisplay(attrs.gravity, outDisplayFrame, outFrame);
}
- if (extendedByCutout && !displayCutoutSafe.contains(outFrame)) {
- mTempRect.set(outFrame);
-
- // Move the frame into displayCutoutSafe.
- final int clipFlags = DISPLAY_CLIP_VERTICAL | DISPLAY_CLIP_HORIZONTAL;
- Gravity.applyDisplay(attrs.gravity & ~clipFlags, displayCutoutSafe,
+ if (extendedByCutout) {
+ extendFrameByCutout(displayCutoutSafe, outDisplayFrame, outFrame,
mTempRect);
-
- if (mTempRect.intersect(outDisplayFrame)) {
- outFrame.union(mTempRect);
- }
}
- if (DEBUG) Log.d(TAG, "computeWindowFrames " + attrs.getTitle()
- + " outFrames=" + outFrames
+ if (DEBUG) Log.d(TAG, "computeFrames " + attrs.getTitle()
+ + " frames=" + frames
+ " windowBounds=" + windowBounds.toShortString()
- + " attachedWindowFrame=" + (attachedWindowFrame != null
- ? attachedWindowFrame.toShortString()
- : "null")
+ " requestedWidth=" + requestedWidth
+ " requestedHeight=" + requestedHeight
+ " compatScale=" + compatScale
@@ -303,6 +291,21 @@ public class WindowLayout {
+ " requestedVisibilities=" + requestedVisibilities);
}
+ public static void extendFrameByCutout(Rect displayCutoutSafe,
+ Rect displayFrame, Rect inOutFrame, Rect tempRect) {
+ if (displayCutoutSafe.contains(inOutFrame)) {
+ return;
+ }
+ tempRect.set(inOutFrame);
+
+ // Move the frame into displayCutoutSafe.
+ Gravity.applyDisplay(0 /* gravity */, displayCutoutSafe, tempRect);
+
+ if (tempRect.intersect(displayFrame)) {
+ inOutFrame.union(tempRect);
+ }
+ }
+
public static void computeSurfaceSize(WindowManager.LayoutParams attrs, Rect maxBounds,
int requestedWidth, int requestedHeight, Rect winFrame, boolean dragResizing,
Point outSurfaceSize) {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 00052f6015d5..63d42c0ca915 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -98,7 +98,6 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.Bitmap;
-import android.graphics.Insets;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
@@ -341,6 +340,18 @@ public interface WindowManager extends ViewManager {
int TRANSIT_OLD_TASK_FRAGMENT_CHANGE = 30;
/**
+ * A dream activity is being opened.
+ * @hide
+ */
+ int TRANSIT_OLD_DREAM_ACTIVITY_OPEN = 31;
+
+ /**
+ * A dream activity is being closed.
+ * @hide
+ */
+ int TRANSIT_OLD_DREAM_ACTIVITY_CLOSE = 32;
+
+ /**
* @hide
*/
@IntDef(prefix = { "TRANSIT_OLD_" }, value = {
@@ -368,7 +379,9 @@ public interface WindowManager extends ViewManager {
TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE,
TRANSIT_OLD_TASK_FRAGMENT_OPEN,
TRANSIT_OLD_TASK_FRAGMENT_CLOSE,
- TRANSIT_OLD_TASK_FRAGMENT_CHANGE
+ TRANSIT_OLD_TASK_FRAGMENT_CHANGE,
+ TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
+ TRANSIT_OLD_DREAM_ACTIVITY_CLOSE
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionOldType {}
@@ -2354,6 +2367,14 @@ public interface WindowManager extends ViewManager {
public static final int SYSTEM_FLAG_SHOW_FOR_ALL_USERS = 0x00000010;
/**
+ * Flag to allow this window to have unrestricted gesture exclusion.
+ *
+ * @see View#setSystemGestureExclusionRects(List)
+ * @hide
+ */
+ public static final int PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION = 0x00000020;
+
+ /**
* Never animate position changes of the window.
*
* {@hide}
@@ -2573,11 +2594,13 @@ public interface WindowManager extends ViewManager {
PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED,
PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS,
SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
+ PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
PRIVATE_FLAG_NO_MOVE_ANIMATION,
PRIVATE_FLAG_COMPATIBLE_WINDOW,
PRIVATE_FLAG_SYSTEM_ERROR,
PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
+ PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH,
PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -2619,6 +2642,10 @@ public interface WindowManager extends ViewManager {
equals = SYSTEM_FLAG_SHOW_FOR_ALL_USERS,
name = "SHOW_FOR_ALL_USERS"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
+ equals = PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION,
+ name = "UNRESTRICTED_GESTURE_EXCLUSION"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_NO_MOVE_ANIMATION,
equals = PRIVATE_FLAG_NO_MOVE_ANIMATION,
name = "NO_MOVE_ANIMATION"),
@@ -2639,6 +2666,10 @@ public interface WindowManager extends ViewManager {
equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
name = "FORCE_STATUS_BAR_VISIBLE"),
@ViewDebug.FlagToString(
+ mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
+ equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
+ name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
+ @ViewDebug.FlagToString(
mask = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
equals = PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
name = "FORCE_DECOR_VIEW_VISIBILITY"),
@@ -3589,34 +3620,17 @@ public interface WindowManager extends ViewManager {
private boolean mFitInsetsIgnoringVisibility = false;
/**
- * {@link InsetsState.InternalInsetsType}s to be applied to the window
- * If {@link #type} has the predefined insets (like {@link #TYPE_STATUS_BAR} or
- * {@link #TYPE_NAVIGATION_BAR}), this field will be ignored.
+ * If set, the specified insets types will be provided by the window and the insets frame
+ * will be calculated based on the provider's parameters. The insets types and the array
+ * should not be modified after the window is added. If multiple layout parameters are
+ * provided for different rotations in {@link LayoutParams#paramsForRotation}, the types in
+ * the providedInsets array should be the same in all rotations, including the base one.
+ * All other params can be adjusted at runtime.
+ * See {@link InsetsFrameProvider}.
*
- * <p>Note: provide only one inset corresponding to the window type (like
- * {@link InsetsState.InternalInsetsType#ITYPE_STATUS_BAR} or
- * {@link InsetsState.InternalInsetsType#ITYPE_NAVIGATION_BAR})</p>
* @hide
*/
- public @InsetsState.InternalInsetsType int[] providesInsetsTypes;
-
- /**
- * If specified, the insets provided by this window will be our window frame minus the
- * insets specified by providedInternalInsets for each type. This should not be used
- * together with {@link WindowState#mGivenContentInsets}. If both of them are set, both will
- * be applied.
- *
- * @hide
- */
- public Insets[] providedInternalInsets;
-
- /**
- * If specified, the insets provided by this window for the IME will be our window frame
- * minus the insets specified by providedInternalImeInsets.
- *
- * @hide
- */
- public Insets[] providedInternalImeInsets;
+ public InsetsFrameProvider[] providedInsets;
/**
* If specified, the frame that used to calculate relative {@link RoundedCorner} will be
@@ -3748,6 +3762,18 @@ public interface WindowManager extends ViewManager {
}
}
+ /**
+ * @see #paramsForRotation
+ * @hide
+ */
+ public LayoutParams forRotation(int rotation) {
+ if (paramsForRotation == null || paramsForRotation.length <= rotation
+ || paramsForRotation[rotation] == null) {
+ return this;
+ }
+ return paramsForRotation[rotation];
+ }
+
public LayoutParams() {
super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
type = TYPE_APPLICATION;
@@ -3996,32 +4022,10 @@ public interface WindowManager extends ViewManager {
out.writeBoolean(mFitInsetsIgnoringVisibility);
out.writeBoolean(preferMinimalPostProcessing);
out.writeInt(mBlurBehindRadius);
- if (providesInsetsTypes != null) {
- out.writeInt(providesInsetsTypes.length);
- out.writeIntArray(providesInsetsTypes);
- } else {
- out.writeInt(0);
- }
- if (providedInternalInsets != null) {
- out.writeInt(providedInternalInsets.length);
- out.writeTypedArray(providedInternalInsets, 0 /* parcelableFlags */);
- } else {
- out.writeInt(0);
- }
- if (providedInternalImeInsets != null) {
- out.writeInt(providedInternalImeInsets.length);
- out.writeTypedArray(providedInternalImeInsets, 0 /* parcelableFlags */);
- } else {
- out.writeInt(0);
- }
out.writeBoolean(insetsRoundedCornerFrame);
- if (paramsForRotation != null) {
- checkNonRecursiveParams();
- out.writeInt(paramsForRotation.length);
- out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
- } else {
- out.writeInt(0);
- }
+ out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
+ checkNonRecursiveParams();
+ out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
}
public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4088,27 +4092,9 @@ public interface WindowManager extends ViewManager {
mFitInsetsIgnoringVisibility = in.readBoolean();
preferMinimalPostProcessing = in.readBoolean();
mBlurBehindRadius = in.readInt();
- int insetsTypesLength = in.readInt();
- if (insetsTypesLength > 0) {
- providesInsetsTypes = new int[insetsTypesLength];
- in.readIntArray(providesInsetsTypes);
- }
- int providedInternalInsetsLength = in.readInt();
- if (providedInternalInsetsLength > 0) {
- providedInternalInsets = new Insets[providedInternalInsetsLength];
- in.readTypedArray(providedInternalInsets, Insets.CREATOR);
- }
- int providedInternalImeInsetsLength = in.readInt();
- if (providedInternalImeInsetsLength > 0) {
- providedInternalImeInsets = new Insets[providedInternalImeInsetsLength];
- in.readTypedArray(providedInternalImeInsets, Insets.CREATOR);
- }
insetsRoundedCornerFrame = in.readBoolean();
- int paramsForRotationLength = in.readInt();
- if (paramsForRotationLength > 0) {
- paramsForRotation = new LayoutParams[paramsForRotationLength];
- in.readTypedArray(paramsForRotation, LayoutParams.CREATOR);
- }
+ providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
+ paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
}
@SuppressWarnings({"PointlessBitwiseExpression"})
@@ -4400,18 +4386,8 @@ public interface WindowManager extends ViewManager {
changes |= LAYOUT_CHANGED;
}
- if (!Arrays.equals(providesInsetsTypes, o.providesInsetsTypes)) {
- providesInsetsTypes = o.providesInsetsTypes;
- changes |= LAYOUT_CHANGED;
- }
-
- if (!Arrays.equals(providedInternalInsets, o.providedInternalInsets)) {
- providedInternalInsets = o.providedInternalInsets;
- changes |= LAYOUT_CHANGED;
- }
-
- if (!Arrays.equals(providedInternalImeInsets, o.providedInternalImeInsets)) {
- providedInternalImeInsets = o.providedInternalImeInsets;
+ if (!Arrays.equals(providedInsets, o.providedInsets)) {
+ providedInsets = o.providedInsets;
changes |= LAYOUT_CHANGED;
}
@@ -4613,28 +4589,12 @@ public interface WindowManager extends ViewManager {
sb.append(System.lineSeparator());
sb.append(prefix).append(" fitIgnoreVis");
}
- if (providesInsetsTypes != null) {
- sb.append(System.lineSeparator());
- sb.append(prefix).append(" insetsTypes=");
- for (int i = 0; i < providesInsetsTypes.length; ++i) {
- if (i > 0) sb.append(' ');
- sb.append(InsetsState.typeToString(providesInsetsTypes[i]));
- }
- }
- if (providedInternalInsets != null) {
- sb.append(System.lineSeparator());
- sb.append(" providedInternalInsets=");
- for (int i = 0; i < providedInternalInsets.length; ++i) {
- if (i > 0) sb.append(' ');
- sb.append((providedInternalInsets[i]));
- }
- }
- if (providedInternalImeInsets != null) {
+ if (providedInsets != null) {
sb.append(System.lineSeparator());
- sb.append(" providedInternalImeInsets=");
- for (int i = 0; i < providedInternalImeInsets.length; ++i) {
+ sb.append(" providedInsets=");
+ for (int i = 0; i < providedInsets.length; ++i) {
if (i > 0) sb.append(' ');
- sb.append((providedInternalImeInsets[i]));
+ sb.append((providedInsets[i]));
}
}
if (insetsRoundedCornerFrame) {
diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java
index aae930edb729..d37756551db3 100644
--- a/core/java/android/view/WindowManagerGlobal.java
+++ b/core/java/android/view/WindowManagerGlobal.java
@@ -83,6 +83,11 @@ public final class WindowManagerGlobal {
public static final int RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS = 1 << 3;
/**
+ * The window manager has told the window it cannot draw this frame and should retry again.
+ */
+ public static final int RELAYOUT_RES_CANCEL_AND_REDRAW = 1 << 4;
+
+ /**
* Flag for relayout: the client will be later giving
* internal insets; as a result, the window will not impact other window
* layouts until the insets are given.
@@ -386,7 +391,7 @@ public final class WindowManagerGlobal {
root = new ViewRootImpl(view.getContext(), display);
} else {
root = new ViewRootImpl(view.getContext(), display,
- windowlessSession);
+ windowlessSession, new WindowlessWindowLayout());
}
view.setLayoutParams(wparams);
@@ -399,9 +404,10 @@ public final class WindowManagerGlobal {
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
+ final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
// BadTokenException or InvalidDisplayException, clean up.
- if (index >= 0) {
- removeViewLocked(index, true);
+ if (viewIndex >= 0) {
+ removeViewLocked(viewIndex, true);
}
throw e;
}
diff --git a/core/java/android/view/WindowManagerPolicyConstants.java b/core/java/android/view/WindowManagerPolicyConstants.java
index 4d07171d3086..43d427db2c75 100644
--- a/core/java/android/view/WindowManagerPolicyConstants.java
+++ b/core/java/android/view/WindowManagerPolicyConstants.java
@@ -237,10 +237,6 @@ public interface WindowManagerPolicyConstants {
*/
int LAYER_OFFSET_THUMBNAIL = WINDOW_LAYER_MULTIPLIER - 1;
- // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and
- // make app pair split only have single root then we can just attach the
- // divider to the single root task in shell.
- int SPLIT_DIVIDER_LAYER = TYPE_LAYER_MULTIPLIER * 3;
int WATERMARK_LAYER = TYPE_LAYER_MULTIPLIER * 100;
int STRICT_MODE_LAYER = TYPE_LAYER_MULTIPLIER * 101;
int WINDOW_FREEZE_LAYER = TYPE_LAYER_MULTIPLIER * 200;
diff --git a/core/java/android/view/WindowlessWindowLayout.java b/core/java/android/view/WindowlessWindowLayout.java
new file mode 100644
index 000000000000..5bec5b6b6722
--- /dev/null
+++ b/core/java/android/view/WindowlessWindowLayout.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.app.WindowConfiguration.WindowingMode;
+import android.graphics.Rect;
+import android.window.ClientWindowFrames;
+
+/**
+ * Computes window frames for the windowless window.
+ * @hide
+ */
+public class WindowlessWindowLayout extends WindowLayout {
+
+ @Override
+ public void computeFrames(WindowManager.LayoutParams attrs, InsetsState state,
+ Rect displayCutoutSafe, Rect windowBounds, @WindowingMode int windowingMode,
+ int requestedWidth, int requestedHeight, InsetsVisibilities requestedVisibilities,
+ float compatScale, ClientWindowFrames frames) {
+ frames.frame.set(0, 0, attrs.width, attrs.height);
+ frames.displayFrame.set(frames.frame);
+ frames.parentFrame.set(frames.frame);
+ }
+}
+
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index a212348a2315..94da2741f71a 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -149,7 +149,7 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
final SurfaceControl.Builder b = new SurfaceControl.Builder(mSurfaceSession)
.setFormat(attrs.format)
.setBLASTLayer()
@@ -181,6 +181,7 @@ public class WindowlessWindowManager implements IWindowSession {
synchronized (this) {
mStateForWindow.put(window.asBinder(), state);
}
+ outAttachedFrame.set(0, 0, -1, -1);
final int res = WindowManagerGlobal.ADD_OKAY | WindowManagerGlobal.ADD_FLAG_APP_VISIBLE |
WindowManagerGlobal.ADD_FLAG_USE_BLAST;
@@ -196,15 +197,15 @@ public class WindowlessWindowManager implements IWindowSession {
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return addToDisplay(window, attrs, viewVisibility, displayId, requestedVisibilities,
- outInputChannel, outInsetsState, outActiveControls);
+ outInputChannel, outInsetsState, outActiveControls, outAttachedFrame);
}
@Override
public int addToDisplayWithoutInputChannel(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs, int viewVisibility, int layerStackId,
- android.view.InsetsState insetsState) {
+ android.view.InsetsState insetsState, Rect outAttachedFrame) {
return 0;
}
@@ -337,21 +338,6 @@ public class WindowlessWindowManager implements IWindowSession {
}
@Override
- public int updateVisibility(IWindow window, WindowManager.LayoutParams inAttrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- // TODO(b/161810301): Finish the implementation.
- return 0;
- }
-
- @Override
- public void updateLayout(IWindow window, WindowManager.LayoutParams inAttrs, int flags,
- ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
- // TODO(b/161810301): Finish the implementation.
- }
-
- @Override
public void prepareToReplaceWindows(android.os.IBinder appToken, boolean childrenOnly) {
}
@@ -552,4 +538,9 @@ public class WindowlessWindowManager implements IWindowSession {
}
}
}
+
+ @Override
+ public boolean cancelDraw(IWindow window) {
+ return false;
+ }
}
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 23393ffe885c..2268bef2c1d9 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -6440,9 +6440,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
public void setText(CharSequence text, BufferType type) {
setText(text, type, true, 0);
- if (mCharWrapper != null) {
- mCharWrapper.mChars = null;
- }
+ // drop any potential mCharWrappper leaks
+ mCharWrapper = null;
}
@UnsupportedAppUsage
@@ -6653,11 +6652,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
* since the TextView has no way to know that the text
* has changed and that it needs to invalidate and re-layout.
*
+ * @throws NullPointerException if text is null
+ * @throws IndexOutOfBoundsException if start or start+len are not in 0 to text.length
+ *
* @param text char array to be displayed
* @param start start index in the char array
* @param len length of char count after {@code start}
*/
- public final void setText(char[] text, int start, int len) {
+ public final void setText(/* @NonNull */ char[] text, int start, int len) {
int oldlen = 0;
if (start < 0 || len < 0 || start + len > text.length) {
@@ -13888,16 +13890,17 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener
}
private static class CharWrapper implements CharSequence, GetChars, GraphicsOperations {
+ @NonNull
private char[] mChars;
private int mStart, mLength;
- public CharWrapper(char[] chars, int start, int len) {
+ CharWrapper(@NonNull char[] chars, int start, int len) {
mChars = chars;
mStart = start;
mLength = len;
}
- /* package */ void set(char[] chars, int start, int len) {
+ /* package */ void set(@NonNull char[] chars, int start, int len) {
mChars = chars;
mStart = start;
mLength = len;
diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java
index dbf3570b1d24..ca57c84a1631 100644
--- a/core/java/android/widget/Toast.java
+++ b/core/java/android/widget/Toast.java
@@ -32,6 +32,7 @@ import android.compat.annotation.EnabledAfter;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.Context;
import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
@@ -494,19 +495,39 @@ public class Toast {
*/
public static Toast makeText(@NonNull Context context, @Nullable Looper looper,
@NonNull CharSequence text, @Duration int duration) {
+ Toast result = new Toast(context, looper);
+
if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) {
- Toast result = new Toast(context, looper);
result.mText = text;
- result.mDuration = duration;
- return result;
} else {
- Toast result = new Toast(context, looper);
- View v = ToastPresenter.getTextToastView(context, text);
- result.mNextView = v;
- result.mDuration = duration;
+ result.mNextView = ToastPresenter.getTextToastView(context, text);
+ }
+
+ result.mDuration = duration;
+ return result;
+ }
- return result;
+ /**
+ * Make a standard toast with an icon to display using the specified looper.
+ * If looper is null, Looper.myLooper() is used.
+ *
+ * The toast will be a custom view that's rendered by the app (instead of by SystemUI).
+ * In Android version R and above, non-system apps can only render the toast
+ * when it's in the foreground.
+ *
+ * @hide
+ */
+ public static Toast makeCustomToastWithIcon(@NonNull Context context, @Nullable Looper looper,
+ @NonNull CharSequence text, @Duration int duration, @NonNull Drawable icon) {
+ if (icon == null) {
+ throw new IllegalArgumentException("Drawable icon should not be null "
+ + "for makeCustomToastWithIcon");
}
+
+ Toast result = new Toast(context, looper);
+ result.mNextView = ToastPresenter.getTextToastViewWithIcon(context, text, icon);
+ result.mDuration = duration;
+ return result;
}
/**
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index eccff0692fab..7467100838ec 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -25,6 +25,7 @@ import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
@@ -55,6 +56,8 @@ public class ToastPresenter {
@VisibleForTesting
public static final int TEXT_TOAST_LAYOUT = R.layout.transient_notification;
+ @VisibleForTesting
+ public static final int TEXT_TOAST_LAYOUT_WITH_ICON = R.layout.transient_notification_with_icon;
/**
* Returns the default text toast view for message {@code text}.
@@ -66,6 +69,24 @@ public class ToastPresenter {
return view;
}
+ /**
+ * Returns the default icon text toast view for message {@code text} and the icon {@code icon}.
+ */
+ public static View getTextToastViewWithIcon(Context context, CharSequence text, Drawable icon) {
+ if (icon == null) {
+ return getTextToastView(context, text);
+ }
+
+ View view = LayoutInflater.from(context).inflate(TEXT_TOAST_LAYOUT_WITH_ICON, null);
+ TextView textView = view.findViewById(com.android.internal.R.id.message);
+ textView.setText(text);
+ ImageView imageView = view.findViewById(com.android.internal.R.id.icon);
+ if (imageView != null) {
+ imageView.setImageDrawable(icon);
+ }
+ return view;
+ }
+
private final Context mContext;
private final Resources mResources;
private final WindowManager mWindowManager;
diff --git a/core/java/android/window/ClientWindowFrames.java b/core/java/android/window/ClientWindowFrames.java
index 51f3fe2551ad..929e81ed9044 100644
--- a/core/java/android/window/ClientWindowFrames.java
+++ b/core/java/android/window/ClientWindowFrames.java
@@ -17,6 +17,7 @@
package android.window;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
@@ -40,6 +41,12 @@ public class ClientWindowFrames implements Parcelable {
*/
public final @NonNull Rect parentFrame = new Rect();
+ /**
+ * The frame this window attaches to. If this is not null, this is the frame of the parent
+ * window.
+ */
+ public @Nullable Rect attachedFrame;
+
public boolean isParentFrameClippedByDisplayCutout;
public ClientWindowFrames() {
@@ -49,6 +56,9 @@ public class ClientWindowFrames implements Parcelable {
frame.set(other.frame);
displayFrame.set(other.displayFrame);
parentFrame.set(other.parentFrame);
+ if (other.attachedFrame != null) {
+ attachedFrame = new Rect(other.attachedFrame);
+ }
isParentFrameClippedByDisplayCutout = other.isParentFrameClippedByDisplayCutout;
}
@@ -61,6 +71,7 @@ public class ClientWindowFrames implements Parcelable {
frame.readFromParcel(in);
displayFrame.readFromParcel(in);
parentFrame.readFromParcel(in);
+ attachedFrame = in.readTypedObject(Rect.CREATOR);
isParentFrameClippedByDisplayCutout = in.readBoolean();
}
@@ -69,6 +80,7 @@ public class ClientWindowFrames implements Parcelable {
frame.writeToParcel(dest, flags);
displayFrame.writeToParcel(dest, flags);
parentFrame.writeToParcel(dest, flags);
+ dest.writeTypedObject(attachedFrame, flags);
dest.writeBoolean(isParentFrameClippedByDisplayCutout);
}
@@ -78,6 +90,7 @@ public class ClientWindowFrames implements Parcelable {
return "ClientWindowFrames{frame=" + frame.toShortString(sb)
+ " display=" + displayFrame.toShortString(sb)
+ " parentFrame=" + parentFrame.toShortString(sb)
+ + (attachedFrame != null ? " attachedFrame=" + attachedFrame.toShortString() : "")
+ " parentClippedByDisplayCutout=" + isParentFrameClippedByDisplayCutout + "}";
}
diff --git a/core/java/android/window/ITaskFragmentOrganizer.aidl b/core/java/android/window/ITaskFragmentOrganizer.aidl
index 8dfda7d41c2d..3335c9c073e0 100644
--- a/core/java/android/window/ITaskFragmentOrganizer.aidl
+++ b/core/java/android/window/ITaskFragmentOrganizer.aidl
@@ -45,10 +45,11 @@ oneway interface ITaskFragmentOrganizer {
*
* @param errorCallbackToken Token set through {@link
* WindowContainerTransaction#setErrorCallbackToken(IBinder)}
- * @param exceptionBundle Bundle containing the exception. Should be created with
- * {@link TaskFragmentOrganizer#putExceptionInBundle}.
+ * @param errorBundle Bundle containing the exception, operation type and TaskFragmentInfo
+ * if any. Should be created with
+ * {@link TaskFragmentOrganizer#putErrorInfoInBundle}.
*/
- void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle exceptionBundle);
+ void onTaskFragmentError(in IBinder errorCallbackToken, in Bundle errorBundle);
/**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java
index 5924844aa3b2..f1a052b61c59 100644
--- a/core/java/android/window/ImeOnBackInvokedDispatcher.java
+++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java
@@ -81,8 +81,10 @@ public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parc
@OnBackInvokedDispatcher.Priority int priority,
@NonNull OnBackInvokedCallback callback) {
final Bundle bundle = new Bundle();
+ // Always invoke back for ime without checking the window focus.
final IOnBackInvokedCallback iCallback =
- new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback);
+ new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback,
+ () -> true);
bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder());
bundle.putInt(RESULT_KEY_PRIORITY, priority);
bundle.putInt(RESULT_KEY_ID, callback.hashCode());
diff --git a/core/java/android/window/PictureInPictureSurfaceTransaction.java b/core/java/android/window/PictureInPictureSurfaceTransaction.java
index 0a751c38cfff..3b1fdf5e4c38 100644
--- a/core/java/android/window/PictureInPictureSurfaceTransaction.java
+++ b/core/java/android/window/PictureInPictureSurfaceTransaction.java
@@ -51,6 +51,8 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
private final Rect mWindowCrop;
+ private boolean mShouldDisableCanAffectSystemUiFlags;
+
private PictureInPictureSurfaceTransaction(Parcel in) {
mAlpha = in.readFloat();
mPosition = in.readTypedObject(PointF.CREATOR);
@@ -60,6 +62,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
mCornerRadius = in.readFloat();
mShadowRadius = in.readFloat();
mWindowCrop = in.readTypedObject(Rect.CREATOR);
+ mShouldDisableCanAffectSystemUiFlags = in.readBoolean();
}
private PictureInPictureSurfaceTransaction(float alpha, @Nullable PointF position,
@@ -84,6 +87,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
this(other.mAlpha, other.mPosition,
other.mFloat9, other.mRotation, other.mCornerRadius, other.mShadowRadius,
other.mWindowCrop);
+ mShouldDisableCanAffectSystemUiFlags = other.mShouldDisableCanAffectSystemUiFlags;
}
/** @return {@link Matrix} from {@link #mFloat9} */
@@ -103,6 +107,16 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
return mShadowRadius > 0;
}
+ /** Sets the internal {@link #mShouldDisableCanAffectSystemUiFlags}. */
+ public void setShouldDisableCanAffectSystemUiFlags(boolean shouldDisable) {
+ mShouldDisableCanAffectSystemUiFlags = shouldDisable;
+ }
+
+ /** @return {@code true} if we should disable Task#setCanAffectSystemUiFlags. */
+ public boolean getShouldDisableCanAffectSystemUiFlags() {
+ return mShouldDisableCanAffectSystemUiFlags;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -114,13 +128,16 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
&& Objects.equals(mRotation, that.mRotation)
&& Objects.equals(mCornerRadius, that.mCornerRadius)
&& Objects.equals(mShadowRadius, that.mShadowRadius)
- && Objects.equals(mWindowCrop, that.mWindowCrop);
+ && Objects.equals(mWindowCrop, that.mWindowCrop)
+ && mShouldDisableCanAffectSystemUiFlags
+ == that.mShouldDisableCanAffectSystemUiFlags;
}
@Override
public int hashCode() {
return Objects.hash(mAlpha, mPosition, Arrays.hashCode(mFloat9),
- mRotation, mCornerRadius, mShadowRadius, mWindowCrop);
+ mRotation, mCornerRadius, mShadowRadius, mWindowCrop,
+ mShouldDisableCanAffectSystemUiFlags);
}
@Override
@@ -137,6 +154,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
out.writeFloat(mCornerRadius);
out.writeFloat(mShadowRadius);
out.writeTypedObject(mWindowCrop, 0 /* flags */);
+ out.writeBoolean(mShouldDisableCanAffectSystemUiFlags);
}
@Override
@@ -150,6 +168,7 @@ public final class PictureInPictureSurfaceTransaction implements Parcelable {
+ " cornerRadius=" + mCornerRadius
+ " shadowRadius=" + mShadowRadius
+ " crop=" + mWindowCrop
+ + " shouldDisableCanAffectSystemUiFlags" + mShouldDisableCanAffectSystemUiFlags
+ ")";
}
diff --git a/core/java/android/window/SizeConfigurationBuckets.java b/core/java/android/window/SizeConfigurationBuckets.java
index f474f0a76cc6..998bec0892ae 100644
--- a/core/java/android/window/SizeConfigurationBuckets.java
+++ b/core/java/android/window/SizeConfigurationBuckets.java
@@ -104,24 +104,15 @@ public final class SizeConfigurationBuckets implements Parcelable {
/**
* Get the changes between two configurations but don't count changes in sizes if they don't
* cross boundaries that are important to the app.
- *
- * This is a static helper to deal with null `buckets`. When no buckets have been specified,
- * this actually filters out all 3 size-configs. This is legacy behavior.
*/
public static int filterDiff(int diff, @NonNull Configuration oldConfig,
@NonNull Configuration newConfig, @Nullable SizeConfigurationBuckets buckets) {
- final boolean nonSizeLayoutFieldsUnchanged =
- areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if (buckets == null) {
- // Only unflip CONFIG_SCREEN_LAYOUT if non-size-related attributes of screen layout do
- // not change.
- if (nonSizeLayoutFieldsUnchanged) {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE
- | CONFIG_SCREEN_LAYOUT);
- } else {
- return diff & ~(CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE);
- }
+ return diff;
}
+
+ final boolean nonSizeLayoutFieldsUnchanged =
+ areNonSizeLayoutFieldsUnchanged(oldConfig.screenLayout, newConfig.screenLayout);
if ((diff & CONFIG_SCREEN_SIZE) != 0) {
final boolean crosses = buckets.crossesHorizontalSizeThreshold(oldConfig.screenWidthDp,
newConfig.screenWidthDp)
diff --git a/core/java/android/window/TaskFragmentOrganizer.java b/core/java/android/window/TaskFragmentOrganizer.java
index 2ef49c3e2aac..e4a6ad87053c 100644
--- a/core/java/android/window/TaskFragmentOrganizer.java
+++ b/core/java/android/window/TaskFragmentOrganizer.java
@@ -18,6 +18,7 @@ package android.window;
import android.annotation.CallSuper;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.annotation.TestApi;
import android.content.Intent;
import android.content.res.Configuration;
@@ -39,16 +40,23 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* Key to the exception in {@link Bundle} in {@link ITaskFragmentOrganizer#onTaskFragmentError}.
*/
private static final String KEY_ERROR_CALLBACK_EXCEPTION = "fragment_exception";
+ private static final String KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO = "task_fragment_info";
+ private static final String KEY_ERROR_CALLBACK_OP_TYPE = "operation_type";
/**
- * Creates a {@link Bundle} with an exception that can be passed to
- * {@link ITaskFragmentOrganizer#onTaskFragmentError}.
+ * Creates a {@link Bundle} with an exception, operation type and TaskFragmentInfo (if any)
+ * that can be passed to {@link ITaskFragmentOrganizer#onTaskFragmentError}.
* @hide
*/
- public static Bundle putExceptionInBundle(@NonNull Throwable exception) {
- final Bundle exceptionBundle = new Bundle();
- exceptionBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
- return exceptionBundle;
+ public static @NonNull Bundle putErrorInfoInBundle(@NonNull Throwable exception,
+ @Nullable TaskFragmentInfo info, int opType) {
+ final Bundle errorBundle = new Bundle();
+ errorBundle.putSerializable(KEY_ERROR_CALLBACK_EXCEPTION, exception);
+ if (info != null) {
+ errorBundle.putParcelable(KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, info);
+ }
+ errorBundle.putInt(KEY_ERROR_CALLBACK_OP_TYPE, opType);
+ return errorBundle;
}
/**
@@ -151,11 +159,34 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
* @param errorCallbackToken token set in
* {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
* @param exception exception from the server side.
+ *
+ * @deprecated Use {@link #onTaskFragmentError(IBinder, TaskFragmentInfo, int, Throwable)}
+ * instead.
*/
+ @Deprecated
public void onTaskFragmentError(
@NonNull IBinder errorCallbackToken, @NonNull Throwable exception) {}
/**
+ * Called when the {@link WindowContainerTransaction} created with
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)} failed on the server side.
+ *
+ * @param errorCallbackToken token set in
+ * {@link WindowContainerTransaction#setErrorCallbackToken(IBinder)}
+ * @param taskFragmentInfo The {@link TaskFragmentInfo}. This could be {@code null} if no
+ * TaskFragment created.
+ * @param opType The {@link WindowContainerTransaction.HierarchyOp} of the failed
+ * transaction operation.
+ * @param exception exception from the server side.
+ */
+ public void onTaskFragmentError(
+ @NonNull IBinder errorCallbackToken, @Nullable TaskFragmentInfo taskFragmentInfo,
+ int opType, @NonNull Throwable exception) {
+ // Doing so to keep compatibility. This will be removed in the next release.
+ onTaskFragmentError(errorCallbackToken, exception);
+ }
+
+ /**
* Called when an Activity is reparented to the Task with organized TaskFragment. For example,
* when an Activity enters and then exits Picture-in-picture, it will be reparented back to its
* orginial Task. In this case, we need to notify the organizer so that it can check if the
@@ -217,10 +248,16 @@ public class TaskFragmentOrganizer extends WindowOrganizer {
@Override
public void onTaskFragmentError(
- @NonNull IBinder errorCallbackToken, @NonNull Bundle exceptionBundle) {
- mExecutor.execute(() -> TaskFragmentOrganizer.this.onTaskFragmentError(
- errorCallbackToken,
- (Throwable) exceptionBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION)));
+ @NonNull IBinder errorCallbackToken, @NonNull Bundle errorBundle) {
+ mExecutor.execute(() -> {
+ final TaskFragmentInfo info = errorBundle.getParcelable(
+ KEY_ERROR_CALLBACK_TASK_FRAGMENT_INFO, TaskFragmentInfo.class);
+ TaskFragmentOrganizer.this.onTaskFragmentError(
+ errorCallbackToken, info,
+ errorBundle.getInt(KEY_ERROR_CALLBACK_OP_TYPE),
+ (Throwable) errorBundle.getSerializable(KEY_ERROR_CALLBACK_EXCEPTION,
+ java.lang.Throwable.class));
+ });
}
@Override
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 51da61ffb9c9..1f3841ad8b58 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -107,8 +107,14 @@ public final class TransitionInfo implements Parcelable {
*/
public static final int FLAG_DISPLAY_HAS_ALERT_WINDOWS = 1 << 7;
+ /** The container is an input-method window. */
+ public static final int FLAG_IS_INPUT_METHOD = 1 << 8;
+
+ /** The container is ActivityEmbedding embedded. */
+ public static final int FLAG_IS_EMBEDDED = 1 << 9;
+
/** The first unused bit. This can be used by remotes to attach custom flags to this change. */
- public static final int FLAG_FIRST_CUSTOM = 1 << 8;
+ public static final int FLAG_FIRST_CUSTOM = 1 << 10;
/** @hide */
@IntDef(prefix = { "FLAG_" }, value = {
@@ -121,6 +127,8 @@ public final class TransitionInfo implements Parcelable {
FLAG_IS_DISPLAY,
FLAG_OCCLUDES_KEYGUARD,
FLAG_DISPLAY_HAS_ALERT_WINDOWS,
+ FLAG_IS_INPUT_METHOD,
+ FLAG_IS_EMBEDDED,
FLAG_FIRST_CUSTOM
})
public @interface ChangeFlags {}
@@ -300,6 +308,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_IS_WALLPAPER) != 0) {
sb.append("IS_WALLPAPER");
}
+ if ((flags & FLAG_IS_INPUT_METHOD) != 0) {
+ sb.append("IS_INPUT_METHOD");
+ }
if ((flags & FLAG_TRANSLUCENT) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "TRANSLUCENT");
}
@@ -318,6 +329,9 @@ public final class TransitionInfo implements Parcelable {
if ((flags & FLAG_DISPLAY_HAS_ALERT_WINDOWS) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "DISPLAY_HAS_ALERT_WINDOWS");
}
+ if ((flags & FLAG_IS_EMBEDDED) != 0) {
+ sb.append((sb.length() == 0 ? "" : "|") + "IS_EMBEDDED");
+ }
if ((flags & FLAG_FIRST_CUSTOM) != 0) {
sb.append((sb.length() == 0 ? "" : "|") + "FIRST_CUSTOM");
}
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 48211a8234ee..14046945ede0 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -16,6 +16,8 @@
package android.window;
+import static android.view.WindowManager.transitTypeToString;
+
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
@@ -366,7 +368,7 @@ public final class TransitionRequestInfo implements Parcelable {
// String fieldNameToString() { ... }
return "TransitionRequestInfo { " +
- "type = " + mType + ", " +
+ "type = " + transitTypeToString(mType) + ", " +
"triggerTask = " + mTriggerTask + ", " +
"remoteTransition = " + mRemoteTransition + ", " +
"displayChange = " + mDisplayChange +
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 7dc039d44f95..3bffa890122a 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -348,8 +348,10 @@ public final class WindowContainerTransaction implements Parcelable {
* @param currentParent of the tasks to perform the operation no.
* {@code null} will perform the operation on the display.
* @param newParent for the tasks. {@code null} will perform the operation on the display.
- * @param windowingModes of the tasks to reparent.
- * @param activityTypes of the tasks to reparent.
+ * @param windowingModes of the tasks to reparent. {@code null} ignore this attribute when
+ * perform the operation.
+ * @param activityTypes of the tasks to reparent. {@code null} ignore this attribute when
+ * perform the operation.
* @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
* the bottom.
*/
@@ -384,16 +386,27 @@ public final class WindowContainerTransaction implements Parcelable {
*/
@NonNull
public WindowContainerTransaction setAdjacentRoots(
- @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2,
- boolean moveTogether) {
+ @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2) {
mHierarchyOps.add(HierarchyOp.createForAdjacentRoots(
root1.asBinder(),
- root2.asBinder(),
- moveTogether));
+ root2.asBinder()));
return this;
}
/**
+ * This is temp function for compatible with old cts tests suite and it equal to
+ * {@link #setAdjacentRoots(WindowContainerToken, WindowContainerToken).
+ * @deprecated should use {@link #setAdjacentRoots(WindowContainerToken, WindowContainerToken)}
+ */
+ @Deprecated
+ @NonNull
+ public WindowContainerTransaction setAdjacentRoots(
+ @NonNull WindowContainerToken root1, @NonNull WindowContainerToken root2,
+ boolean moveTogether) {
+ return setAdjacentRoots(root1, root2);
+ }
+
+ /**
* Sets the container as launch adjacent flag root. Task starting with
* {@link FLAG_ACTIVITY_LAUNCH_ADJACENT} will be launching to.
*/
@@ -1106,9 +1119,6 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mReparentTopOnly;
- // TODO(b/207185041): Remove this once having a single-top root for split screen.
- private boolean mMoveAdjacentTogether;
-
@Nullable
private int[] mWindowingModes;
@@ -1171,12 +1181,10 @@ public final class WindowContainerTransaction implements Parcelable {
}
/** Create a hierarchy op for setting adjacent root tasks. */
- public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2,
- boolean moveTogether) {
+ public static HierarchyOp createForAdjacentRoots(IBinder root1, IBinder root2) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS)
.setContainer(root1)
.setReparentContainer(root2)
- .setMoveAdjacentTogether(moveTogether)
.build();
}
@@ -1223,7 +1231,6 @@ public final class WindowContainerTransaction implements Parcelable {
mInsetsProviderFrame = copy.mInsetsProviderFrame;
mToTop = copy.mToTop;
mReparentTopOnly = copy.mReparentTopOnly;
- mMoveAdjacentTogether = copy.mMoveAdjacentTogether;
mWindowingModes = copy.mWindowingModes;
mActivityTypes = copy.mActivityTypes;
mLaunchOptions = copy.mLaunchOptions;
@@ -1245,7 +1252,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
mToTop = in.readBoolean();
mReparentTopOnly = in.readBoolean();
- mMoveAdjacentTogether = in.readBoolean();
mWindowingModes = in.createIntArray();
mActivityTypes = in.createIntArray();
mLaunchOptions = in.readBundle();
@@ -1300,10 +1306,6 @@ public final class WindowContainerTransaction implements Parcelable {
return mReparentTopOnly;
}
- public boolean getMoveAdjacentTogether() {
- return mMoveAdjacentTogether;
- }
-
public int[] getWindowingModes() {
return mWindowingModes;
}
@@ -1356,8 +1358,7 @@ public final class WindowContainerTransaction implements Parcelable {
return "{reorder: " + mContainer + " to " + (mToTop ? "top" : "bottom") + "}";
case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS:
return "{SetAdjacentRoot: container=" + mContainer
- + " adjacentRoot=" + mReparent + " mMoveAdjacentTogether="
- + mMoveAdjacentTogether + "}";
+ + " adjacentRoot=" + mReparent + "}";
case HIERARCHY_OP_TYPE_LAUNCH_TASK:
return "{LaunchTask: " + mLaunchOptions + "}";
case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT:
@@ -1413,7 +1414,6 @@ public final class WindowContainerTransaction implements Parcelable {
}
dest.writeBoolean(mToTop);
dest.writeBoolean(mReparentTopOnly);
- dest.writeBoolean(mMoveAdjacentTogether);
dest.writeIntArray(mWindowingModes);
dest.writeIntArray(mActivityTypes);
dest.writeBundle(mLaunchOptions);
@@ -1458,8 +1458,6 @@ public final class WindowContainerTransaction implements Parcelable {
private boolean mReparentTopOnly;
- private boolean mMoveAdjacentTogether;
-
@Nullable
private int[] mWindowingModes;
@@ -1515,11 +1513,6 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
- Builder setMoveAdjacentTogether(boolean moveAdjacentTogether) {
- mMoveAdjacentTogether = moveAdjacentTogether;
- return this;
- }
-
Builder setWindowingModes(@Nullable int[] windowingModes) {
mWindowingModes = windowingModes;
return this;
@@ -1570,7 +1563,6 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mInsetsProviderFrame = mInsetsProviderFrame;
hierarchyOp.mToTop = mToTop;
hierarchyOp.mReparentTopOnly = mReparentTopOnly;
- hierarchyOp.mMoveAdjacentTogether = mMoveAdjacentTogether;
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java
index d147524d3b3d..02c5ebcc184e 100644
--- a/core/java/android/window/WindowOnBackInvokedDispatcher.java
+++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java
@@ -32,6 +32,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.Objects;
import java.util.TreeMap;
+import java.util.function.Supplier;
/**
* Provides window based implementation of {@link OnBackInvokedDispatcher}.
@@ -64,6 +65,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
private final TreeMap<Integer, ArrayList<OnBackInvokedCallback>>
mOnBackInvokedCallbacks = new TreeMap<>();
private final Checker mChecker;
+ private boolean mHasFocus;
public WindowOnBackInvokedDispatcher(boolean applicationCallBackEnabled) {
mChecker = new Checker(applicationCallBackEnabled);
@@ -189,7 +191,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
.ImeOnBackInvokedCallback
? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback)
callback).getIOnBackInvokedCallback()
- : new OnBackInvokedCallbackWrapper(callback);
+ : new OnBackInvokedCallbackWrapper(callback, this::hasFocus);
callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority);
}
mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo);
@@ -198,6 +200,17 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
}
}
+ /**
+ * Called when window focus changed.
+ */
+ public void onWindowFocusChanged(boolean hasFocus) {
+ mHasFocus = hasFocus;
+ }
+
+ private boolean hasFocus() {
+ return mHasFocus;
+ }
+
public OnBackInvokedCallback getTopCallback() {
if (mAllCallbacks.isEmpty()) {
return null;
@@ -221,9 +234,11 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub {
private final WeakReference<OnBackInvokedCallback> mCallback;
-
- OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) {
+ private final Supplier<Boolean> mHasFocus;
+ OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback,
+ @NonNull Supplier<Boolean> hasFocus) {
mCallback = new WeakReference<>(callback);
+ mHasFocus = hasFocus;
}
@Override
@@ -263,7 +278,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher {
if (callback == null) {
return;
}
-
+ if (!mHasFocus.get()) {
+ Log.w(TAG, "Skip back invoke due to current focus has lost.");
+ return;
+ }
callback.onBackInvoked();
});
}
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 10bf2767b0c2..1d9e170794a7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -30,6 +30,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.SharedElementCallback;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
@@ -101,7 +102,10 @@ import android.view.ViewGroup.LayoutParams;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.animation.AccelerateInterpolator;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Space;
@@ -197,6 +201,8 @@ public class ChooserActivity extends ResolverActivity implements
private static final String PLURALS_COUNT = "count";
private static final String PLURALS_FILE_NAME = "file_name";
+ private static final String IMAGE_EDITOR_SHARED_ELEMENT = "screenshot_preview_image";
+
private boolean mIsAppPredictorComponentAvailable;
private Map<ChooserTarget, AppTarget> mDirectShareAppTargetCache;
private Map<ChooserTarget, ShortcutInfo> mDirectShareShortcutInfoCache;
@@ -250,6 +256,11 @@ public class ChooserActivity extends ResolverActivity implements
private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125;
+ private static final int URI_PERMISSION_INTENT_FLAGS = Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
+
@VisibleForTesting
int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.SHARESHEET_LIST_VIEW_UPDATE_DELAY,
@@ -305,6 +316,8 @@ public class ChooserActivity extends ResolverActivity implements
private boolean mRemoveSharedElements = false;
+ private View mContentView = null;
+
private class ContentPreviewCoordinator {
private static final int IMAGE_FADE_IN_MILLIS = 150;
private static final int IMAGE_LOAD_TIMEOUT = 1;
@@ -990,6 +1003,7 @@ public class ChooserActivity extends ResolverActivity implements
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume: " + getComponentName().flattenToShortString());
+ maybeCancelFinishAnimation();
}
@Override
@@ -1002,6 +1016,7 @@ public class ChooserActivity extends ResolverActivity implements
mShouldDisplayLandscape = shouldDisplayLandscape(newConfig.orientation);
mMaxTargetsPerRow = getResources().getInteger(R.integer.config_chooser_max_targets_per_row);
+ mChooserMultiProfilePagerAdapter.setMaxTargetsPerRow(mMaxTargetsPerRow);
adjustPreviewWidth(newConfig.orientation, null);
updateStickyContentPreview();
updateTabPadding();
@@ -1084,6 +1099,10 @@ public class ChooserActivity extends ResolverActivity implements
final ComponentName cn = getEditSharingComponent();
final Intent resolveIntent = new Intent(originalIntent);
+ // Retain only URI permission grant flags if present. Other flags may prevent the scene
+ // transition animation from running (i.e FLAG_ACTIVITY_NO_ANIMATION,
+ // FLAG_ACTIVITY_NEW_TASK, FLAG_ACTIVITY_NEW_DOCUMENT) but also not needed.
+ resolveIntent.setFlags(originalIntent.getFlags() & URI_PERMISSION_INTENT_FLAGS);
resolveIntent.setComponent(cn);
resolveIntent.setAction(Intent.ACTION_EDIT);
String originalAction = originalIntent.getAction();
@@ -1113,7 +1132,6 @@ public class ChooserActivity extends ResolverActivity implements
return dri;
}
-
@VisibleForTesting
protected TargetInfo getNearbySharingTarget(Intent originalIntent) {
final ComponentName cn = getNearbySharingComponent();
@@ -1216,15 +1234,30 @@ public class ChooserActivity extends ResolverActivity implements
"",
-1,
false);
+ View firstImgView = getFirstVisibleImgPreviewView();
// Action bar is user-independent, always start as primary
- safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
- finish();
+ if (firstImgView == null) {
+ safelyStartActivityAsUser(ti, getPersonalProfileUserHandle());
+ finish();
+ } else {
+ ActivityOptions options = ActivityOptions.makeSceneTransitionAnimation(
+ this, firstImgView, IMAGE_EDITOR_SHARED_ELEMENT);
+ safelyStartActivityAsUser(
+ ti, getPersonalProfileUserHandle(), options.toBundle());
+ startFinishAnimation();
+ }
}
);
b.setId(R.id.chooser_edit_button);
return b;
}
+ @Nullable
+ private View getFirstVisibleImgPreviewView() {
+ View firstImage = findViewById(R.id.content_preview_image_1_large);
+ return firstImage != null && firstImage.isVisibleToUser() ? firstImage : null;
+ }
+
private void addActionButton(ViewGroup parent, Button b) {
if (b == null) return;
final ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(
@@ -1572,6 +1605,14 @@ public class ChooserActivity extends ResolverActivity implements
}
@Override
+ protected void onStop() {
+ super.onStop();
+ if (maybeCancelFinishAnimation()) {
+ finish();
+ }
+ }
+
+ @Override
protected void onDestroy() {
super.onDestroy();
@@ -2899,6 +2940,30 @@ public class ChooserActivity extends ResolverActivity implements
.setSubtype(previewType));
}
+ private void startFinishAnimation() {
+ View rootView = findRootView();
+ rootView.startAnimation(new FinishAnimation(this, rootView));
+ }
+
+ private boolean maybeCancelFinishAnimation() {
+ View rootView = findRootView();
+ Animation animation = rootView.getAnimation();
+ if (animation instanceof FinishAnimation) {
+ boolean hasEnded = animation.hasEnded();
+ animation.cancel();
+ rootView.clearAnimation();
+ return !hasEnded;
+ }
+ return false;
+ }
+
+ private View findRootView() {
+ if (mContentView == null) {
+ mContentView = findViewById(android.R.id.content);
+ }
+ return mContentView;
+ }
+
abstract static class ViewHolderBase extends RecyclerView.ViewHolder {
private int mViewType;
@@ -3999,6 +4064,66 @@ public class ChooserActivity extends ResolverActivity implements
}
}
+ /**
+ * Used in combination with the scene transition when launching the image editor
+ */
+ private static class FinishAnimation extends AlphaAnimation implements
+ Animation.AnimationListener {
+ private Activity mActivity;
+ private View mRootView;
+ private final float mFromAlpha;
+
+ FinishAnimation(Activity activity, View rootView) {
+ super(rootView.getAlpha(), 0.0f);
+ mActivity = activity;
+ mRootView = rootView;
+ mFromAlpha = rootView.getAlpha();
+ setInterpolator(new LinearInterpolator());
+ long duration = activity.getWindow().getTransitionBackgroundFadeDuration();
+ setDuration(duration);
+ // The scene transition animation looks better when it's not overlapped with this
+ // fade-out animation thus the delay.
+ // It is most likely that the image editor will cause this activity to stop and this
+ // animation will be cancelled in the background without running (i.e. we'll animate
+ // only when this activity remains partially visible after the image editor launch).
+ setStartOffset(duration);
+ super.setAnimationListener(this);
+ }
+
+ @Override
+ public void setAnimationListener(AnimationListener listener) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void cancel() {
+ mRootView.setAlpha(mFromAlpha);
+ cleanup();
+ super.cancel();
+ }
+
+ @Override
+ public void onAnimationStart(Animation animation) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ if (mActivity != null) {
+ mActivity.finish();
+ cleanup();
+ }
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) {
+ }
+
+ private void cleanup() {
+ mActivity = null;
+ mRootView = null;
+ }
+ }
+
@Override
protected void maybeLogProfileChange() {
getChooserActivityLogger().logShareheetProfileChanged();
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index e6cc62472f5c..df1130be5ec6 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -93,6 +93,10 @@ public class ChooserMultiProfilePagerAdapter extends AbstractMultiProfilePagerAd
return profileDescriptor;
}
+ public void setMaxTargetsPerRow(int maxTargetsPerRow) {
+ mMaxTargetsPerRow = maxTargetsPerRow;
+ }
+
RecyclerView getListViewForIndex(int index) {
return getItem(index).recyclerView;
}
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 40429c609150..e9e437fda8f2 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -1314,7 +1314,7 @@ public class ResolverActivity extends Activity implements
StrictMode.disableDeathOnFileUriExposure();
try {
UserHandle currentUserHandle = mMultiProfilePagerAdapter.getCurrentUserHandle();
- safelyStartActivityInternal(cti, currentUserHandle);
+ safelyStartActivityInternal(cti, currentUserHandle, null);
} finally {
StrictMode.enableDeathOnFileUriExposure();
}
@@ -1327,18 +1327,23 @@ public class ResolverActivity extends Activity implements
*/
@VisibleForTesting
public void safelyStartActivityAsUser(TargetInfo cti, UserHandle user) {
+ safelyStartActivityAsUser(cti, user, null);
+ }
+
+ protected void safelyStartActivityAsUser(
+ TargetInfo cti, UserHandle user, @Nullable Bundle options) {
// We're dispatching intents that might be coming from legacy apps, so
// don't kill ourselves.
StrictMode.disableDeathOnFileUriExposure();
try {
- safelyStartActivityInternal(cti, user);
+ safelyStartActivityInternal(cti, user, options);
} finally {
StrictMode.enableDeathOnFileUriExposure();
}
}
-
- private void safelyStartActivityInternal(TargetInfo cti, UserHandle user) {
+ private void safelyStartActivityInternal(
+ TargetInfo cti, UserHandle user, @Nullable Bundle options) {
// If the target is suspended, the activity will not be successfully launched.
// Do not unregister from package manager updates in this case
if (!cti.isSuspended() && mRegistered) {
@@ -1356,14 +1361,14 @@ public class ResolverActivity extends Activity implements
Toast.makeText(this, mProfileSwitchMessage, Toast.LENGTH_LONG).show();
}
if (!mSafeForwardingMode) {
- if (cti.startAsUser(this, null, user)) {
+ if (cti.startAsUser(this, options, user)) {
onActivityStarted(cti);
maybeLogCrossProfileTargetLaunch(cti, user);
}
return;
}
try {
- if (cti.startAsCaller(this, null, user.getIdentifier())) {
+ if (cti.startAsCaller(this, options, user.getIdentifier())) {
onActivityStarted(cti);
maybeLogCrossProfileTargetLaunch(cti, user);
}
diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java
index 2ed0f981692e..b32afb44852a 100644
--- a/core/java/com/android/internal/app/ResolverListAdapter.java
+++ b/core/java/com/android/internal/app/ResolverListAdapter.java
@@ -265,6 +265,7 @@ public class ResolverListAdapter extends BaseAdapter {
return mResolverListController.getResolversForIntent(
/* shouldGetResolvedFilter= */ true,
mResolverListCommunicator.shouldGetActivityMetadata(),
+ mResolverListCommunicator.shouldGetOnlyDefaultActivities(),
mIntents);
}
}
@@ -416,8 +417,9 @@ public class ResolverListAdapter extends BaseAdapter {
if (ii == null) {
continue;
}
- ActivityInfo ai = ii.resolveActivityInfo(
- mPm, 0);
+ // Because of AIDL bug, resolveActivityInfo can't accept subclasses of Intent.
+ final Intent rii = (ii.getClass() == Intent.class) ? ii : new Intent(ii);
+ ActivityInfo ai = rii.resolveActivityInfo(mPm, 0);
if (ai == null) {
Log.w(TAG, "No activity found for " + ii);
continue;
@@ -727,6 +729,7 @@ public class ResolverListAdapter extends BaseAdapter {
protected List<ResolvedComponentInfo> getResolversForUser(UserHandle userHandle) {
return mResolverListController.getResolversForIntentAsUser(true,
mResolverListCommunicator.shouldGetActivityMetadata(),
+ mResolverListCommunicator.shouldGetOnlyDefaultActivities(),
mIntents, userHandle);
}
@@ -820,6 +823,12 @@ public class ResolverListAdapter extends BaseAdapter {
boolean shouldGetActivityMetadata();
+ /**
+ * @return true to filter only apps that can handle
+ * {@link android.content.Intent#CATEGORY_DEFAULT} intents
+ */
+ default boolean shouldGetOnlyDefaultActivities() { return true; };
+
Intent getTargetIntent();
void onHandlePackagesChanged(ResolverListAdapter listAdapter);
diff --git a/core/java/com/android/internal/app/ResolverListController.java b/core/java/com/android/internal/app/ResolverListController.java
index 27573631b2ce..01dcf9624ed5 100644
--- a/core/java/com/android/internal/app/ResolverListController.java
+++ b/core/java/com/android/internal/app/ResolverListController.java
@@ -110,17 +110,19 @@ public class ResolverListController {
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntent(
boolean shouldGetResolvedFilter,
boolean shouldGetActivityMetadata,
+ boolean shouldGetOnlyDefaultActivities,
List<Intent> intents) {
return getResolversForIntentAsUser(shouldGetResolvedFilter, shouldGetActivityMetadata,
- intents, mUserHandle);
+ shouldGetOnlyDefaultActivities, intents, mUserHandle);
}
public List<ResolverActivity.ResolvedComponentInfo> getResolversForIntentAsUser(
boolean shouldGetResolvedFilter,
boolean shouldGetActivityMetadata,
+ boolean shouldGetOnlyDefaultActivities,
List<Intent> intents,
UserHandle userHandle) {
- int baseFlags = PackageManager.MATCH_DEFAULT_ONLY
+ int baseFlags = (shouldGetOnlyDefaultActivities ? PackageManager.MATCH_DEFAULT_ONLY : 0)
| PackageManager.MATCH_DIRECT_BOOT_AWARE
| PackageManager.MATCH_DIRECT_BOOT_UNAWARE
| (shouldGetResolvedFilter ? PackageManager.GET_RESOLVED_FILTER : 0)
@@ -134,12 +136,15 @@ public class ResolverListController {
int baseFlags) {
List<ResolverActivity.ResolvedComponentInfo> resolvedComponents = null;
for (int i = 0, N = intents.size(); i < N; i++) {
- final Intent intent = intents.get(i);
+ Intent intent = intents.get(i);
int flags = baseFlags;
if (intent.isWebIntent()
|| (intent.getFlags() & Intent.FLAG_ACTIVITY_MATCH_EXTERNAL) != 0) {
flags |= PackageManager.MATCH_INSTANT;
}
+ // Because of AIDL bug, queryIntentActivitiesAsUser can't accept subclasses of Intent.
+ intent = (intent.getClass() == Intent.class) ? intent : new Intent(
+ intent);
final List<ResolveInfo> infos = mpm.queryIntentActivitiesAsUser(intent, flags,
userHandle);
if (infos != null) {
diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java
index 825b486d0635..dbfa4d35974b 100644
--- a/core/java/com/android/internal/jank/FrameTracker.java
+++ b/core/java/com/android/internal/jank/FrameTracker.java
@@ -26,10 +26,12 @@ import static android.view.SurfaceControl.JankData.SURFACE_FLINGER_SCHEDULING;
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_CANCEL;
import static com.android.internal.jank.InteractionJankMonitor.ACTION_SESSION_END;
+import static com.android.internal.jank.InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UiThread;
import android.graphics.HardwareRendererObserver;
import android.os.Handler;
import android.os.Trace;
@@ -85,8 +87,9 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
public @interface Reasons {
}
+ @VisibleForTesting
+ public final InteractionJankMonitor mMonitor;
private final HardwareRendererObserver mObserver;
- private SurfaceControl mSurfaceControl;
private final int mTraceThresholdMissedFrames;
private final int mTraceThresholdFrameTimeMillis;
private final ThreadedRendererWrapper mRendererWrapper;
@@ -99,17 +102,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
private final Handler mHandler;
private final ChoreographerWrapper mChoreographer;
private final StatsLogWrapper mStatsLog;
- private final Object mLock = InteractionJankMonitor.getInstance().getLock();
private final boolean mDeferMonitoring;
+ private final FrameTrackerListener mListener;
@VisibleForTesting
public final boolean mSurfaceOnly;
+ private SurfaceControl mSurfaceControl;
private long mBeginVsyncId = INVALID_ID;
private long mEndVsyncId = INVALID_ID;
private boolean mMetricsFinalized;
private boolean mCancelled = false;
- private FrameTrackerListener mListener;
private boolean mTracingStarted = false;
private Runnable mWaitForFinishTimedOut;
@@ -142,16 +145,52 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
this.jankType = jankType;
this.isFirstFrame = isFirstFrame;
}
+
+ @Override
+ public String toString() {
+ StringBuilder str = new StringBuilder();
+ switch (jankType) {
+ case JANK_NONE:
+ str.append("JANK_NONE");
+ break;
+ case JANK_APP_DEADLINE_MISSED:
+ str.append("JANK_APP_DEADLINE_MISSED");
+ break;
+ case JANK_SURFACEFLINGER_DEADLINE_MISSED:
+ str.append("JANK_SURFACEFLINGER_DEADLINE_MISSED");
+ break;
+ case JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED:
+ str.append("JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED");
+ break;
+ case DISPLAY_HAL:
+ str.append("DISPLAY_HAL");
+ break;
+ case PREDICTION_ERROR:
+ str.append("PREDICTION_ERROR");
+ break;
+ case SURFACE_FLINGER_SCHEDULING:
+ str.append("SURFACE_FLINGER_SCHEDULING");
+ break;
+ default:
+ str.append("UNKNOWN: ").append(jankType);
+ break;
+ }
+ str.append(", ").append(frameVsyncId);
+ str.append(", ").append(totalDurationNanos);
+ return str.toString();
+ }
}
- public FrameTracker(@NonNull Session session, @NonNull Handler handler,
- @Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper,
+ public FrameTracker(@NonNull InteractionJankMonitor monitor, @NonNull Session session,
+ @NonNull Handler handler, @Nullable ThreadedRendererWrapper renderer,
+ @Nullable ViewRootWrapper viewRootWrapper,
@NonNull SurfaceControlWrapper surfaceControlWrapper,
@NonNull ChoreographerWrapper choreographer,
@Nullable FrameMetricsWrapper metrics,
@NonNull StatsLogWrapper statsLog,
int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis,
@Nullable FrameTrackerListener listener, @NonNull Configuration config) {
+ mMonitor = monitor;
mSurfaceOnly = config.isSurfaceOnly();
mSession = session;
mHandler = handler;
@@ -186,17 +225,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() {
@Override
public void surfaceCreated(SurfaceControl.Transaction t) {
- synchronized (mLock) {
+ getHandler().runWithScissors(() -> {
if (mSurfaceControl == null) {
mSurfaceControl = mViewRoot.getSurfaceControl();
if (mBeginVsyncId != INVALID_ID) {
- mSurfaceControlWrapper.addJankStatsListener(
- FrameTracker.this, mSurfaceControl);
- markEvent("FT#deferMonitoring");
- postTraceStartMarker();
+ // Previous begin invocation is not successfully, begin it again.
+ begin();
}
}
- }
+ }, EXECUTOR_TASK_TIMEOUT);
}
@Override
@@ -208,18 +245,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
// Wait a while to give the system a chance for the remaining
// frames to arrive, then force finish the session.
- mHandler.postDelayed(() -> {
- synchronized (mLock) {
- if (DEBUG) {
- Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
- + ", finalized=" + mMetricsFinalized
- + ", info=" + mJankInfos.size()
- + ", vsync=" + mBeginVsyncId);
- }
- if (!mMetricsFinalized) {
- end(REASON_END_SURFACE_DESTROYED);
- finish();
- }
+ getHandler().postDelayed(() -> {
+ if (DEBUG) {
+ Log.d(TAG, "surfaceDestroyed: " + mSession.getName()
+ + ", finalized=" + mMetricsFinalized
+ + ", info=" + mJankInfos.size()
+ + ", vsync=" + mBeginVsyncId);
+ }
+ if (!mMetricsFinalized) {
+ end(REASON_END_SURFACE_DESTROYED);
+ finish();
}
}, 50);
}
@@ -230,35 +265,42 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
}
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
+
/**
* Begin a trace session of the CUJ.
*/
+ @UiThread
public void begin() {
- synchronized (mLock) {
- final long currentVsync = mChoreographer.getVsyncId();
- // In normal case, we should begin at the next frame,
- // the id of the next frame is not simply increased by 1,
- // but we can exclude the current frame at least.
+ final long currentVsync = mChoreographer.getVsyncId();
+ // In normal case, we should begin at the next frame,
+ // the id of the next frame is not simply increased by 1,
+ // but we can exclude the current frame at least.
+ if (mBeginVsyncId == INVALID_ID) {
mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync;
+ }
+ if (mSurfaceControl != null) {
if (DEBUG) {
Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId
- + ", defer=" + mDeferMonitoring);
+ + ", defer=" + mDeferMonitoring + ", current=" + currentVsync);
}
- if (mSurfaceControl != null) {
- if (mDeferMonitoring) {
- markEvent("FT#deferMonitoring");
- // Normal case, we begin the instrument from the very beginning,
- // will exclude the first frame.
- postTraceStartMarker();
- } else {
- // If we don't begin the instrument from the very beginning,
- // there is no need to skip the frame where the begin invocation happens.
- beginInternal();
- }
- mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
+ if (mDeferMonitoring && currentVsync < mBeginVsyncId) {
+ markEvent("FT#deferMonitoring");
+ // Normal case, we begin the instrument from the very beginning,
+ // will exclude the first frame.
+ postTraceStartMarker(this::beginInternal);
+ } else {
+ // If we don't begin the instrument from the very beginning,
+ // there is no need to skip the frame where the begin invocation happens.
+ beginInternal();
}
- if (!mSurfaceOnly) {
- mRendererWrapper.addObserver(mObserver);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "begin: defer beginning since the surface is not ready for CUJ="
+ + mSession.getName());
}
}
}
@@ -267,89 +309,89 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
* Start trace section at appropriate time.
*/
@VisibleForTesting
- public void postTraceStartMarker() {
- mChoreographer.mChoreographer.postCallback(
- Choreographer.CALLBACK_INPUT, this::beginInternal, null);
+ public void postTraceStartMarker(Runnable action) {
+ mChoreographer.mChoreographer.postCallback(Choreographer.CALLBACK_INPUT, action, null);
}
+ @UiThread
private void beginInternal() {
- synchronized (mLock) {
- if (mCancelled || mEndVsyncId != INVALID_ID) {
- return;
- }
- mTracingStarted = true;
- markEvent("FT#begin");
- Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ if (mCancelled || mEndVsyncId != INVALID_ID) {
+ return;
+ }
+ mTracingStarted = true;
+ markEvent("FT#begin");
+ Trace.beginAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl);
+ if (!mSurfaceOnly) {
+ mRendererWrapper.addObserver(mObserver);
}
}
/**
* End the trace session of the CUJ.
*/
+ @UiThread
public boolean end(@Reasons int reason) {
- synchronized (mLock) {
- if (mCancelled || mEndVsyncId != INVALID_ID) return false;
- mEndVsyncId = mChoreographer.getVsyncId();
- // Cancel the session if:
- // 1. The session begins and ends at the same vsync id.
- // 2. The session never begun.
- if (mBeginVsyncId == INVALID_ID) {
- return cancel(REASON_CANCEL_NOT_BEGUN);
- } else if (mEndVsyncId <= mBeginVsyncId) {
- return cancel(REASON_CANCEL_SAME_VSYNC);
- } else {
- if (DEBUG) {
- Log.d(TAG, "end: " + mSession.getName()
- + ", end=" + mEndVsyncId + ", reason=" + reason);
- }
- markEvent("FT#end#" + reason);
- Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
- mSession.setReason(reason);
-
- // We don't remove observer here,
- // will remove it when all the frame metrics in this duration are called back.
- // See onFrameMetricsAvailable for the logic of removing the observer.
- // Waiting at most 10 seconds for all callbacks to finish.
- mWaitForFinishTimedOut = () -> {
- Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
- finish();
- };
- mHandler.postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
- notifyCujEvent(ACTION_SESSION_END);
- return true;
+ if (mCancelled || mEndVsyncId != INVALID_ID) return false;
+ mEndVsyncId = mChoreographer.getVsyncId();
+ // Cancel the session if:
+ // 1. The session begins and ends at the same vsync id.
+ // 2. The session never begun.
+ if (mBeginVsyncId == INVALID_ID) {
+ return cancel(REASON_CANCEL_NOT_BEGUN);
+ } else if (mEndVsyncId <= mBeginVsyncId) {
+ return cancel(REASON_CANCEL_SAME_VSYNC);
+ } else {
+ if (DEBUG) {
+ Log.d(TAG, "end: " + mSession.getName()
+ + ", end=" + mEndVsyncId + ", reason=" + reason);
}
+ markEvent("FT#end#" + reason);
+ Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ mSession.setReason(reason);
+
+ // We don't remove observer here,
+ // will remove it when all the frame metrics in this duration are called back.
+ // See onFrameMetricsAvailable for the logic of removing the observer.
+ // Waiting at most 10 seconds for all callbacks to finish.
+ mWaitForFinishTimedOut = () -> {
+ Log.e(TAG, "force finish cuj because of time out:" + mSession.getName());
+ finish();
+ };
+ getHandler().postDelayed(mWaitForFinishTimedOut, TimeUnit.SECONDS.toMillis(10));
+ notifyCujEvent(ACTION_SESSION_END);
+ return true;
}
}
/**
* Cancel the trace session of the CUJ.
*/
+ @UiThread
public boolean cancel(@Reasons int reason) {
- synchronized (mLock) {
- final boolean cancelFromEnd =
- reason == REASON_CANCEL_NOT_BEGUN || reason == REASON_CANCEL_SAME_VSYNC;
- if (mCancelled || (mEndVsyncId != INVALID_ID && !cancelFromEnd)) return false;
- mCancelled = true;
- markEvent("FT#cancel#" + reason);
- // We don't need to end the trace section if it never begun.
- if (mTracingStarted) {
- Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
- }
-
- // Always remove the observers in cancel call to avoid leakage.
- removeObservers();
+ final boolean cancelFromEnd =
+ reason == REASON_CANCEL_NOT_BEGUN || reason == REASON_CANCEL_SAME_VSYNC;
+ if (mCancelled || (mEndVsyncId != INVALID_ID && !cancelFromEnd)) return false;
+ mCancelled = true;
+ markEvent("FT#cancel#" + reason);
+ // We don't need to end the trace section if it has never begun.
+ if (mTracingStarted) {
+ Trace.endAsyncSection(mSession.getName(), (int) mBeginVsyncId);
+ }
- if (DEBUG) {
- Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId
- + ", end=" + mEndVsyncId + ", reason=" + reason);
- }
+ // Always remove the observers in cancel call to avoid leakage.
+ removeObservers();
- mSession.setReason(reason);
- // Notify the listener the session has been cancelled.
- // We don't notify the listeners if the session never begun.
- notifyCujEvent(ACTION_SESSION_CANCEL);
- return true;
+ if (DEBUG) {
+ Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId
+ + ", end=" + mEndVsyncId + ", reason=" + reason);
}
+
+ mSession.setReason(reason);
+ // Notify the listener the session has been cancelled.
+ // We don't notify the listeners if the session never begun.
+ notifyCujEvent(ACTION_SESSION_CANCEL);
+ return true;
}
private void markEvent(String desc) {
@@ -364,8 +406,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
@Override
public void onJankDataAvailable(SurfaceControl.JankData[] jankData) {
- synchronized (mLock) {
- if (mCancelled) {
+ postCallback(() -> {
+ if (mCancelled || mMetricsFinalized) {
return;
}
@@ -384,10 +426,19 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
}
}
processJankInfos();
- }
+ });
}
- private @Nullable JankInfo findJankInfo(long frameVsyncId) {
+ /**
+ * For easier argument capture.
+ */
+ @VisibleForTesting
+ public void postCallback(Runnable callback) {
+ getHandler().post(callback);
+ }
+
+ @Nullable
+ private JankInfo findJankInfo(long frameVsyncId) {
return mJankInfos.get((int) frameVsyncId);
}
@@ -400,8 +451,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
@Override
public void onFrameMetricsAvailable(int dropCountSinceLastInvocation) {
- synchronized (mLock) {
- if (mCancelled) {
+ postCallback(() -> {
+ if (mCancelled || mMetricsFinalized) {
return;
}
@@ -426,9 +477,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
frameVsyncId, totalDurationNanos, isFirstFrame));
}
processJankInfos();
- }
+ });
}
+ @UiThread
private boolean hasReceivedCallbacksAfterEnd() {
if (mEndVsyncId == INVALID_ID) {
return false;
@@ -451,6 +503,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
return false;
}
+ @UiThread
private void processJankInfos() {
if (mMetricsFinalized) {
return;
@@ -467,9 +520,12 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
: info.hwuiCallbackFired && info.surfaceControlCallbackFired;
}
+ @UiThread
private void finish() {
- mHandler.removeCallbacks(mWaitForFinishTimedOut);
+ getHandler().removeCallbacks(mWaitForFinishTimedOut);
mWaitForFinishTimedOut = null;
+ if (mMetricsFinalized || mCancelled) return;
+ markEvent("FT#finish#" + mJankInfos.size());
mMetricsFinalized = true;
// The tracing has been ended, remove the observer, see if need to trigger perfetto.
@@ -496,7 +552,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
totalFramesCount++;
boolean missedFrame = false;
if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) {
- Log.w(TAG, "Missed App frame:" + info.jankType);
+ Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + mSession.getName());
missedAppFramesCount++;
missedFrame = true;
}
@@ -505,7 +561,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
|| (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0
|| (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0
|| (info.jankType & PREDICTION_ERROR) != 0) {
- Log.w(TAG, "Missed SF frame:" + info.jankType);
+ Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + mSession.getName());
missedSfFramesCount++;
missedFrame = true;
}
@@ -520,13 +576,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
// TODO (b/174755489): Early latch currently gets fired way too often, so we have
// to ignore it for now.
if (!mSurfaceOnly && !info.hwuiCallbackFired) {
- Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId);
+ Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId
+ + ", CUJ=" + mSession.getName());
}
}
if (!mSurfaceOnly && info.hwuiCallbackFired) {
maxFrameTimeNanos = Math.max(info.totalDurationNanos, maxFrameTimeNanos);
if (!info.surfaceControlCallbackFired) {
- Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId);
+ Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId
+ + ", CUJ=" + mSession.getName());
}
}
}
@@ -586,6 +644,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
* Remove all the registered listeners, observers and callbacks.
*/
@VisibleForTesting
+ @UiThread
public void removeObservers() {
mSurfaceControlWrapper.removeJankStatsListener(this);
if (!mSurfaceOnly) {
@@ -601,7 +660,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
* Trigger the prefetto daemon.
*/
public void triggerPerfetto() {
- InteractionJankMonitor.getInstance().trigger(mSession);
+ mMonitor.trigger(mSession);
}
/**
@@ -666,10 +725,18 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener
mViewRoot = viewRoot;
}
+ /**
+ * {@link ViewRootImpl#addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback)}
+ * @param callback {@link ViewRootImpl.SurfaceChangedCallback}
+ */
public void addSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
mViewRoot.addSurfaceChangedCallback(callback);
}
+ /**
+ * {@link ViewRootImpl#removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback)}
+ * @param callback {@link ViewRootImpl.SurfaceChangedCallback}
+ */
public void removeSurfaceChangedCallback(ViewRootImpl.SurfaceChangedCallback callback) {
mViewRoot.removeSurfaceChangedCallback(callback);
}
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 65e7abf674c3..8f10a5e3f049 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -53,6 +53,8 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_COLLAPSE_LOCK;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
@@ -74,15 +76,21 @@ import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_IN
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM;
+import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL;
import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.annotation.WorkerThread;
import android.content.Context;
import android.os.Build;
+import android.os.Handler;
import android.os.HandlerExecutor;
import android.os.HandlerThread;
import android.provider.DeviceConfig;
@@ -93,6 +101,7 @@ import android.view.Choreographer;
import android.view.SurfaceControl;
import android.view.View;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.jank.FrameTracker.ChoreographerWrapper;
import com.android.internal.jank.FrameTracker.FrameMetricsWrapper;
@@ -126,6 +135,7 @@ public class InteractionJankMonitor {
private static final String DEFAULT_WORKER_NAME = TAG + "-Worker";
private static final long DEFAULT_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(2L);
+ static final long EXECUTOR_TASK_TIMEOUT = 500;
private static final String SETTINGS_ENABLED_KEY = "enabled";
private static final String SETTINGS_SAMPLING_INTERVAL_KEY = "sampling_interval";
private static final String SETTINGS_THRESHOLD_MISSED_FRAMES_KEY =
@@ -202,6 +212,11 @@ public class InteractionJankMonitor {
public static final int CUJ_VOLUME_CONTROL = 55;
public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56;
public static final int CUJ_SETTINGS_TOGGLE = 57;
+ public static final int CUJ_SHADE_DIALOG_OPEN = 58;
+ public static final int CUJ_USER_DIALOG_OPEN = 59;
+ public static final int CUJ_TASKBAR_EXPAND = 60;
+ public static final int CUJ_TASKBAR_COLLAPSE = 61;
+ public static final int CUJ_SHADE_CLEAR_ALL = 62;
private static final int NO_STATSD_LOGGING = -1;
@@ -268,6 +283,11 @@ public class InteractionJankMonitor {
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION,
UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE,
+ UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL,
};
private static volatile InteractionJankMonitor sInstance;
@@ -275,13 +295,14 @@ public class InteractionJankMonitor {
private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener =
this::updateProperties;
- private final FrameMetricsWrapper mMetrics;
+ @GuardedBy("mLock")
private final SparseArray<FrameTracker> mRunningTrackers;
+ @GuardedBy("mLock")
private final SparseArray<Runnable> mTimeoutActions;
private final HandlerThread mWorker;
private final Object mLock = new Object();
- private boolean mEnabled = DEFAULT_ENABLED;
+ private volatile boolean mEnabled = DEFAULT_ENABLED;
private int mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES;
private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS;
@@ -346,6 +367,11 @@ public class InteractionJankMonitor {
CUJ_VOLUME_CONTROL,
CUJ_BIOMETRIC_PROMPT_TRANSITION,
CUJ_SETTINGS_TOGGLE,
+ CUJ_SHADE_DIALOG_OPEN,
+ CUJ_USER_DIALOG_OPEN,
+ CUJ_TASKBAR_EXPAND,
+ CUJ_TASKBAR_COLLAPSE,
+ CUJ_SHADE_CLEAR_ALL
})
@Retention(RetentionPolicy.SOURCE)
public @interface CujType {
@@ -378,9 +404,7 @@ public class InteractionJankMonitor {
mRunningTrackers = new SparseArray<>();
mTimeoutActions = new SparseArray<>();
mWorker = worker;
- mMetrics = new FrameMetricsWrapper();
mWorker.start();
- mEnabled = DEFAULT_ENABLED;
mSamplingInterval = DEFAULT_SAMPLING_INTERVAL;
// Post initialization to the background in case we're running on the main
@@ -393,10 +417,7 @@ public class InteractionJankMonitor {
DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR,
new HandlerExecutor(mWorker.getThreadHandler()),
mPropertiesChangedListener);
- }
-
- Object getLock() {
- return mLock;
+ mEnabled = DEFAULT_ENABLED;
}
/**
@@ -413,27 +434,27 @@ public class InteractionJankMonitor {
view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer());
final ViewRootWrapper viewRoot =
view == null ? null : new ViewRootWrapper(view.getViewRootImpl());
-
final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper();
final ChoreographerWrapper choreographer =
new ChoreographerWrapper(Choreographer.getInstance());
+ final FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
+ final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper();
- synchronized (mLock) {
- FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s);
- return new FrameTracker(session, mWorker.getThreadHandler(),
- threadedRenderer, viewRoot, surfaceControl, choreographer,
- mMetrics, new FrameTracker.StatsLogWrapper(),
- mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
- eventsListener, config);
- }
+ return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot,
+ surfaceControl, choreographer, frameMetrics, new FrameTracker.StatsLogWrapper(),
+ mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis,
+ eventsListener, config);
}
+ @UiThread
private void handleCujEvents(String action, Session session) {
// Clear the running and timeout tasks if the end / cancel was fired within the tracker.
// Or we might have memory leaks.
if (needRemoveTasks(action, session)) {
- removeTimeout(session.getCuj());
- removeTracker(session.getCuj());
+ getTracker(session.getCuj()).getHandler().runWithScissors(() -> {
+ removeTimeout(session.getCuj());
+ removeTracker(session.getCuj());
+ }, EXECUTOR_TASK_TIMEOUT);
}
}
@@ -450,7 +471,7 @@ public class InteractionJankMonitor {
synchronized (mLock) {
Runnable timeout = mTimeoutActions.get(cujType);
if (timeout != null) {
- mWorker.getThreadHandler().removeCallbacks(timeout);
+ getTracker(cujType).getHandler().removeCallbacks(timeout);
mTimeoutActions.remove(cujType);
}
}
@@ -475,9 +496,7 @@ public class InteractionJankMonitor {
*/
public boolean begin(View v, @CujType int cujType) {
try {
- return beginInternal(
- Configuration.Builder.withView(cujType, v)
- .build());
+ return begin(Configuration.Builder.withView(cujType, v));
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Build configuration failed!", ex);
return false;
@@ -488,35 +507,42 @@ public class InteractionJankMonitor {
* Begins a trace session.
*
* @param builder the builder of the configurations for instrumenting the CUJ.
- * @return boolean true if the tracker is started successfully, false otherwise.
+ * @return boolean true if the tracker is begun successfully, false otherwise.
*/
public boolean begin(@NonNull Configuration.Builder builder) {
try {
- return beginInternal(builder.build());
+ final Configuration config = builder.build();
+ final TrackerResult result = new TrackerResult();
+ final boolean success = config.getHandler().runWithScissors(
+ () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT);
+ if (!success) {
+ Log.d(TAG, "begin failed due to timeout, CUJ=" + getNameOfCuj(config.mCujType));
+ return false;
+ }
+ return result.mResult;
} catch (IllegalArgumentException ex) {
Log.d(TAG, "Build configuration failed!", ex);
return false;
}
}
+ @UiThread
private boolean beginInternal(@NonNull Configuration conf) {
- synchronized (mLock) {
- int cujType = conf.mCujType;
- if (!shouldMonitor(cujType)) return false;
- FrameTracker tracker = getTracker(cujType);
- // Skip subsequent calls if we already have an ongoing tracing.
- if (tracker != null) return false;
-
- // begin a new trace session.
- tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
- mRunningTrackers.put(cujType, tracker);
- tracker.begin();
-
- // Cancel the trace if we don't get an end() call in specified duration.
- scheduleTimeoutAction(
- cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
- return true;
- }
+ int cujType = conf.mCujType;
+ if (!shouldMonitor(cujType)) return false;
+ FrameTracker tracker = getTracker(cujType);
+ // Skip subsequent calls if we already have an ongoing tracing.
+ if (tracker != null) return false;
+
+ // begin a new trace session.
+ tracker = createFrameTracker(conf, new Session(cujType, conf.mTag));
+ putTracker(cujType, tracker);
+ tracker.begin();
+
+ // Cancel the trace if we don't get an end() call in specified duration.
+ scheduleTimeoutAction(
+ cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT));
+ return true;
}
/**
@@ -545,8 +571,10 @@ public class InteractionJankMonitor {
*/
@VisibleForTesting
public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) {
- mTimeoutActions.put(cuj, action);
- mWorker.getThreadHandler().postDelayed(action, timeout);
+ synchronized (mLock) {
+ mTimeoutActions.put(cuj, action);
+ getTracker(cuj).getHandler().postDelayed(action, timeout);
+ }
}
/**
@@ -556,18 +584,35 @@ public class InteractionJankMonitor {
* @return boolean true if the tracker is ended successfully, false otherwise.
*/
public boolean end(@CujType int cujType) {
- synchronized (mLock) {
- // remove the timeout action first.
- removeTimeout(cujType);
- FrameTracker tracker = getTracker(cujType);
- // Skip this call since we haven't started a trace yet.
- if (tracker == null) return false;
- // if the end call doesn't return true, another thread is handling end of the cuj.
- if (tracker.end(REASON_END_NORMAL)) {
- removeTracker(cujType);
+ FrameTracker tracker = getTracker(cujType);
+ // Skip this call since we haven't started a trace yet.
+ if (tracker == null) return false;
+ try {
+ final TrackerResult result = new TrackerResult();
+ final boolean success = tracker.getHandler().runWithScissors(
+ () -> result.mResult = endInternal(cujType), EXECUTOR_TASK_TIMEOUT);
+ if (!success) {
+ Log.d(TAG, "end failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+ return false;
}
- return true;
+ return result.mResult;
+ } catch (IllegalArgumentException ex) {
+ Log.d(TAG, "Execute end task failed!", ex);
+ return false;
+ }
+ }
+
+ @UiThread
+ private boolean endInternal(@CujType int cujType) {
+ // remove the timeout action first.
+ removeTimeout(cujType);
+ FrameTracker tracker = getTracker(cujType);
+ if (tracker == null) return false;
+ // if the end call doesn't return true, another thread is handling end of the cuj.
+ if (tracker.end(REASON_END_NORMAL)) {
+ removeTracker(cujType);
}
+ return true;
}
/**
@@ -586,39 +631,66 @@ public class InteractionJankMonitor {
*/
@VisibleForTesting
public boolean cancel(@CujType int cujType, @Reasons int reason) {
- synchronized (mLock) {
- // remove the timeout action first.
- removeTimeout(cujType);
- FrameTracker tracker = getTracker(cujType);
- // Skip this call since we haven't started a trace yet.
- if (tracker == null) return false;
- // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
- if (tracker.cancel(reason)) {
- removeTracker(cujType);
+ FrameTracker tracker = getTracker(cujType);
+ // Skip this call since we haven't started a trace yet.
+ if (tracker == null) return false;
+ try {
+ final TrackerResult result = new TrackerResult();
+ final boolean success = tracker.getHandler().runWithScissors(
+ () -> result.mResult = cancelInternal(cujType, reason), EXECUTOR_TASK_TIMEOUT);
+ if (!success) {
+ Log.d(TAG, "cancel failed due to timeout, CUJ=" + getNameOfCuj(cujType));
+ return false;
}
- return true;
+ return result.mResult;
+ } catch (IllegalArgumentException ex) {
+ Log.d(TAG, "Execute cancel task failed!", ex);
+ return false;
+ }
+ }
+
+ @UiThread
+ private boolean cancelInternal(@CujType int cujType, @Reasons int reason) {
+ // remove the timeout action first.
+ removeTimeout(cujType);
+ FrameTracker tracker = getTracker(cujType);
+ if (tracker == null) return false;
+ // if the cancel call doesn't return true, another thread is handling cancel of the cuj.
+ if (tracker.cancel(reason)) {
+ removeTracker(cujType);
+ }
+ return true;
+ }
+
+ private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) {
+ synchronized (mLock) {
+ mRunningTrackers.put(cuj, tracker);
}
}
private FrameTracker getTracker(@CujType int cuj) {
- return mRunningTrackers.get(cuj);
+ synchronized (mLock) {
+ return mRunningTrackers.get(cuj);
+ }
}
private void removeTracker(@CujType int cuj) {
- mRunningTrackers.remove(cuj);
+ synchronized (mLock) {
+ mRunningTrackers.remove(cuj);
+ }
}
+ @WorkerThread
private void updateProperties(DeviceConfig.Properties properties) {
- synchronized (mLock) {
- mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
- DEFAULT_SAMPLING_INTERVAL);
- mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
- mTraceThresholdMissedFrames = properties.getInt(SETTINGS_THRESHOLD_MISSED_FRAMES_KEY,
- DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
- mTraceThresholdFrameTimeMillis = properties.getInt(
- SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
- DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
- }
+ mSamplingInterval = properties.getInt(SETTINGS_SAMPLING_INTERVAL_KEY,
+ DEFAULT_SAMPLING_INTERVAL);
+ mTraceThresholdMissedFrames = properties.getInt(SETTINGS_THRESHOLD_MISSED_FRAMES_KEY,
+ DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES);
+ mTraceThresholdFrameTimeMillis = properties.getInt(
+ SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY,
+ DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS);
+ // The memory visibility is powered by the volatile field, mEnabled.
+ mEnabled = properties.getBoolean(SETTINGS_ENABLED_KEY, DEFAULT_ENABLED);
}
@VisibleForTesting
@@ -780,10 +852,24 @@ public class InteractionJankMonitor {
return "BIOMETRIC_PROMPT_TRANSITION";
case CUJ_SETTINGS_TOGGLE:
return "SETTINGS_TOGGLE";
+ case CUJ_SHADE_DIALOG_OPEN:
+ return "SHADE_DIALOG_OPEN";
+ case CUJ_USER_DIALOG_OPEN:
+ return "USER_DIALOG_OPEN";
+ case CUJ_TASKBAR_EXPAND:
+ return "TASKBAR_EXPAND";
+ case CUJ_TASKBAR_COLLAPSE:
+ return "TASKBAR_COLLAPSE";
+ case CUJ_SHADE_CLEAR_ALL:
+ return "SHADE_CLEAR_ALL";
}
return "UNKNOWN";
}
+ private static class TrackerResult {
+ private boolean mResult;
+ }
+
/**
* Configurations used while instrumenting the CUJ. <br/>
* <b>It may refer to an attached view, don't use static reference for any purpose.</b>
@@ -797,6 +883,7 @@ public class InteractionJankMonitor {
private final SurfaceControl mSurfaceControl;
private final @CujType int mCujType;
private final boolean mDeferMonitor;
+ private final Handler mHandler;
/**
* A builder for building Configuration. {@link #setView(View)} is essential
@@ -940,6 +1027,7 @@ public class InteractionJankMonitor {
mSurfaceControl = surfaceControl;
mDeferMonitor = deferMonitor;
validate();
+ mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler();
}
private void validate() {
@@ -988,20 +1076,25 @@ public class InteractionJankMonitor {
return mSurfaceControl;
}
- View getView() {
+ @VisibleForTesting
+ /**
+ * @return a view which is attached to the view tree.
+ */
+ public View getView() {
return mView;
}
- Context getContext() {
- return mContext;
- }
-
/**
* @return true if the monitoring should be deferred to the next frame, false otherwise.
*/
public boolean shouldDeferMonitor() {
return mDeferMonitor;
}
+
+ @VisibleForTesting
+ public Handler getHandler() {
+ return mHandler;
+ }
}
/**
@@ -1054,7 +1147,8 @@ public class InteractionJankMonitor {
mReason = reason;
}
- public @Reasons int getReason() {
+ @Reasons
+ public int getReason() {
return mReason;
}
}
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index ea5797d752d7..a352063aa6ee 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -42,6 +42,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static com.android.internal.policy.PhoneWindow.FEATURE_OPTIONS_PANEL;
@@ -309,6 +310,7 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
R.integer.dock_enter_exit_duration);
mForceWindowDrawsBarBackgrounds = context.getResources().getBoolean(
R.bool.config_forceWindowDrawsStatusBarBackground)
+ && params.type != TYPE_INPUT_METHOD
&& context.getApplicationInfo().targetSdkVersion >= N;
mSemiTransparentBarColor = context.getResources().getColor(
R.color.system_bar_background_semi_transparent, null /* theme */);
@@ -1164,8 +1166,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
0 /* sideInset */, animate && !disallowAnimate,
mForceWindowDrawsBarBackgrounds, controller);
boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
- mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
- && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
+ mDrawLegacyNavigationBarBackground =
+ (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
mDrawLegacyNavigationBarBackgroundHandled =
mWindow.onDrawLegacyNavigationBarBackgroundChanged(
@@ -1206,7 +1208,8 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
boolean hideNavigation = (sysUiVisibility & SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0
|| !(controller == null || controller.isRequestedVisible(ITYPE_NAVIGATION_BAR));
boolean decorFitsSystemWindows = mWindow.mDecorFitsSystemWindows;
- boolean forceConsumingNavBar = (mForceWindowDrawsBarBackgrounds
+ boolean forceConsumingNavBar =
+ ((mForceWindowDrawsBarBackgrounds || mDrawLegacyNavigationBarBackgroundHandled)
&& (attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) == 0
&& decorFitsSystemWindows
@@ -1437,8 +1440,9 @@ public class DecorView extends FrameLayout implements RootViewSurfaceTaker, Wind
int size, boolean verticalBar, boolean seascape, int sideMargin, boolean animate,
boolean force, WindowInsetsController controller) {
state.present = state.attributes.isPresent(
- controller.isRequestedVisible(state.attributes.insetsType),
- mWindow.getAttributes().flags, force);
+ (controller.isRequestedVisible(state.attributes.insetsType)
+ || mLastShouldAlwaysConsumeSystemBars),
+ mWindow.getAttributes().flags, force);
boolean show = state.attributes.isVisible(state.present, color,
mWindow.getAttributes().flags, force);
boolean showView = show && !isResizing() && !mHasCaption && size > 0;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 9c0fad902a52..fb38bba8ee16 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -915,6 +915,12 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback {
}
}
+ if (!st.hasPanelItems()) {
+ // Ensure that |st.decorView| has its actual content. Otherwise, an empty window can be
+ // created and cause ANR.
+ return;
+ }
+
st.isHandled = false;
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
diff --git a/core/java/com/android/internal/policy/TransitionAnimation.java b/core/java/com/android/internal/policy/TransitionAnimation.java
index 377dfd0fb137..295dc545ef4b 100644
--- a/core/java/com/android/internal/policy/TransitionAnimation.java
+++ b/core/java/com/android/internal/policy/TransitionAnimation.java
@@ -25,6 +25,7 @@ import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_NONE;
import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_WALLPAPER_INTRA_OPEN;
import static android.view.WindowManager.TRANSIT_OPEN;
@@ -258,11 +259,17 @@ public class TransitionAnimation {
}
return null;
}
-
- /** Load animation by attribute Id from a specific AnimationStyle resource. */
+ /**
+ * Load animation by attribute Id from a specific AnimationStyle resource.
+ *
+ * @param translucent {@code true} if we're sure that the animation is applied on a translucent
+ * window container, {@code false} otherwise.
+ * @param transit {@link TransitionOldType} for the app transition of this animation, or
+ * {@link TransitionOldType#TRANSIT_OLD_UNSET} if app transition type is unknown.
+ */
@Nullable
- public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
- boolean translucent) {
+ private Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
+ boolean translucent, @TransitionOldType int transit) {
if (animStyleResId == 0) {
return null;
}
@@ -278,6 +285,8 @@ public class TransitionAnimation {
}
if (translucent) {
resId = updateToTranslucentAnimIfNeeded(resId);
+ } else if (transit != TRANSIT_OLD_UNSET) {
+ resId = updateToTranslucentAnimIfNeeded(resId, transit);
}
if (ResourceId.isValid(resId)) {
return loadAnimationSafely(context, resId, mTag);
@@ -285,6 +294,15 @@ public class TransitionAnimation {
return null;
}
+
+ /** Load animation by attribute Id from a specific AnimationStyle resource. */
+ @Nullable
+ public Animation loadAnimationAttr(String packageName, int animStyleResId, int animAttr,
+ boolean translucent) {
+ return loadAnimationAttr(packageName, animStyleResId, animAttr, translucent,
+ TRANSIT_OLD_UNSET);
+ }
+
/** Load animation by attribute Id from android package. */
@Nullable
public Animation loadDefaultAnimationAttr(int animAttr, boolean translucent) {
@@ -292,6 +310,13 @@ public class TransitionAnimation {
translucent);
}
+ /** Load animation by attribute Id from android package. */
+ @Nullable
+ public Animation loadDefaultAnimationAttr(int animAttr, @TransitionOldType int transit) {
+ return loadAnimationAttr(DEFAULT_PACKAGE, mDefaultWindowAnimationStyleResId, animAttr,
+ false /* translucent */, transit);
+ }
+
@Nullable
private AttributeCache.Entry getCachedAnimations(LayoutParams lp) {
if (mDebug) {
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index e93a7854f1cd..65bec0e405ea 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -33,6 +33,7 @@ import android.view.InsetsVisibilities;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
@@ -202,10 +203,12 @@ oneway interface IStatusBar
* @param behavior the behavior of the focused window.
* @param requestedVisibilities the collection of the requested visibilities of system insets.
* @param packageName the package name of the focused app.
+ * @param letterboxDetails a set of letterbox details of apps visible on the screen.
*/
void onSystemBarAttributesChanged(int displayId, int appearance,
in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- int behavior, in InsetsVisibilities requestedVisibilities, String packageName);
+ int behavior, in InsetsVisibilities requestedVisibilities, String packageName,
+ in LetterboxDetails[] letterboxDetails);
/**
* Notifies System UI to show transient bars. The transient bars are system bars, e.g., status
diff --git a/core/java/com/android/internal/statusbar/LetterboxDetails.aidl b/core/java/com/android/internal/statusbar/LetterboxDetails.aidl
new file mode 100644
index 000000000000..7875796cd3ba
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/LetterboxDetails.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+parcelable LetterboxDetails;
diff --git a/core/java/com/android/internal/statusbar/LetterboxDetails.java b/core/java/com/android/internal/statusbar/LetterboxDetails.java
new file mode 100644
index 000000000000..5d14ee347482
--- /dev/null
+++ b/core/java/com/android/internal/statusbar/LetterboxDetails.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.statusbar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.InsetsFlags;
+import android.view.ViewDebug;
+import android.view.WindowInsetsController.Appearance;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * Details about the letterbox state of an app.
+ */
+@DataClass(
+ genParcelable = true,
+ genAidl = true,
+ genToString = true,
+ genGetters = false,
+ genEqualsHashCode = true
+)
+public class LetterboxDetails implements Parcelable {
+
+ @NonNull
+ private final Rect mLetterboxInnerBounds;
+ @NonNull
+ private final Rect mLetterboxFullBounds;
+ private final int mAppAppearance;
+
+ /**
+ * Returns the bounds of the inner letterbox (app content).
+ *
+ * <p>When an app is letterboxed, it is not using the full bounds of its window. Here we return
+ * the bounds that are being used for the app content.
+ *
+ * <pre>
+ * +-------+---------+-------+
+ * | | | |
+ * | | | |
+ * | Outer | Inner | Outer |
+ * | | | |
+ * | | | |
+ * +-------+-------- +-------+
+ * <pre>
+ */
+ @NonNull
+ public Rect getLetterboxInnerBounds() {
+ return mLetterboxInnerBounds;
+ }
+
+ /**
+ * Returns the full bounds of the letterbox.
+ *
+ * <p>These are the entire bounds of the window where the app is placed. We cannot assume that
+ * the full bounds are the bounds of the screen, as the app can be in split-screen, or can have
+ * some margin due to display cutouts.
+ *
+ * <pre>
+ * ---- Full bounds width ----
+ * +-------+---------+-------+ |
+ * | | | | |
+ * | | | | |
+ * | Outer | Inner | Outer | + Full bounds height
+ * | | | | |
+ * | | | | |
+ * +-------+-------- +-------+ |
+ * </pre>
+ */
+ @NonNull
+ public Rect getLetterboxFullBounds() {
+ return mLetterboxFullBounds;
+ }
+
+ /**
+ * Returns the {@link Appearance} of the inner letterbox (app content).
+ */
+ @Appearance
+ public int getAppAppearance() {
+ return mAppAppearance;
+ }
+
+ /** Returns a string representation of the {@link #getAppAppearance()} property. */
+ public String appAppearanceToString() {
+ return ViewDebug.flagsToString(InsetsFlags.class, "appearance", mAppAppearance);
+ }
+
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/statusbar/LetterboxDetails.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ @DataClass.Generated.Member
+ public LetterboxDetails(
+ @NonNull Rect letterboxInnerBounds,
+ @NonNull Rect letterboxFullBounds,
+ int appAppearance) {
+ this.mLetterboxInnerBounds = letterboxInnerBounds;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLetterboxInnerBounds);
+ this.mLetterboxFullBounds = letterboxFullBounds;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLetterboxFullBounds);
+ this.mAppAppearance = appAppearance;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "LetterboxDetails { " +
+ "letterboxInnerBounds = " + mLetterboxInnerBounds + ", " +
+ "letterboxFullBounds = " + mLetterboxFullBounds + ", " +
+ "appAppearance = " + appAppearanceToString() +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public boolean equals(@Nullable Object o) {
+ // You can override field equality logic by defining either of the methods like:
+ // boolean fieldNameEquals(LetterboxDetails other) { ... }
+ // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ @SuppressWarnings("unchecked")
+ LetterboxDetails that = (LetterboxDetails) o;
+ //noinspection PointlessBooleanExpression
+ return true
+ && java.util.Objects.equals(mLetterboxInnerBounds, that.mLetterboxInnerBounds)
+ && java.util.Objects.equals(mLetterboxFullBounds, that.mLetterboxFullBounds)
+ && mAppAppearance == that.mAppAppearance;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int hashCode() {
+ // You can override field hashCode logic by defining methods like:
+ // int fieldNameHashCode() { ... }
+
+ int _hash = 1;
+ _hash = 31 * _hash + java.util.Objects.hashCode(mLetterboxInnerBounds);
+ _hash = 31 * _hash + java.util.Objects.hashCode(mLetterboxFullBounds);
+ _hash = 31 * _hash + mAppAppearance;
+ return _hash;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@NonNull Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeTypedObject(mLetterboxInnerBounds, flags);
+ dest.writeTypedObject(mLetterboxFullBounds, flags);
+ dest.writeInt(mAppAppearance);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ protected LetterboxDetails(@NonNull Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ Rect letterboxInnerBounds = (Rect) in.readTypedObject(Rect.CREATOR);
+ Rect letterboxFullBounds = (Rect) in.readTypedObject(Rect.CREATOR);
+ int appAppearance = in.readInt();
+
+ this.mLetterboxInnerBounds = letterboxInnerBounds;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLetterboxInnerBounds);
+ this.mLetterboxFullBounds = letterboxFullBounds;
+ com.android.internal.util.AnnotationValidations.validate(
+ NonNull.class, null, mLetterboxFullBounds);
+ this.mAppAppearance = appAppearance;
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @NonNull Parcelable.Creator<LetterboxDetails> CREATOR
+ = new Parcelable.Creator<LetterboxDetails>() {
+ @Override
+ public LetterboxDetails[] newArray(int size) {
+ return new LetterboxDetails[size];
+ }
+
+ @Override
+ public LetterboxDetails createFromParcel(@NonNull Parcel in) {
+ return new LetterboxDetails(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1656941109526L,
+ codegenVersion = "1.0.23",
+ sourceFile = "frameworks/base/core/java/com/android/internal/statusbar/LetterboxDetails.java",
+ inputSignatures = "private final @android.annotation.NonNull android.graphics.Rect mLetterboxInnerBounds\nprivate final @android.annotation.NonNull android.graphics.Rect mLetterboxFullBounds\nprivate final int mAppAppearance\npublic @android.annotation.NonNull android.graphics.Rect getLetterboxInnerBounds()\npublic @android.annotation.NonNull android.graphics.Rect getLetterboxFullBounds()\npublic @android.view.WindowInsetsController.Appearance int getAppAppearance()\npublic java.lang.String appAppearanceToString()\nclass LetterboxDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genToString=true, genGetters=false, genEqualsHashCode=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 4dcc82e2e572..8b898f01554f 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -43,12 +43,14 @@ public final class RegisterStatusBarResult implements Parcelable {
public final InsetsVisibilities mRequestedVisibilities;
public final String mPackageName;
public final int[] mTransientBarTypes;
+ public final LetterboxDetails[] mLetterboxDetails;
public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
- String packageName, @NonNull int[] transientBarTypes) {
+ String packageName, @NonNull int[] transientBarTypes,
+ LetterboxDetails[] letterboxDetails) {
mIcons = new ArrayMap<>(icons);
mDisabledFlags1 = disabledFlags1;
mAppearance = appearance;
@@ -63,6 +65,7 @@ public final class RegisterStatusBarResult implements Parcelable {
mRequestedVisibilities = requestedVisibilities;
mPackageName = packageName;
mTransientBarTypes = transientBarTypes;
+ mLetterboxDetails = letterboxDetails;
}
@Override
@@ -86,6 +89,7 @@ public final class RegisterStatusBarResult implements Parcelable {
dest.writeTypedObject(mRequestedVisibilities, 0);
dest.writeString(mPackageName);
dest.writeIntArray(mTransientBarTypes);
+ dest.writeParcelableArray(mLetterboxDetails, flags);
}
/**
@@ -112,10 +116,13 @@ public final class RegisterStatusBarResult implements Parcelable {
source.readTypedObject(InsetsVisibilities.CREATOR);
final String packageName = source.readString();
final int[] transientBarTypes = source.createIntArray();
+ final LetterboxDetails[] letterboxDetails =
+ source.readParcelableArray(null, LetterboxDetails.class);
return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
- requestedVisibilities, packageName, transientBarTypes);
+ requestedVisibilities, packageName, transientBarTypes,
+ letterboxDetails);
}
@Override
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 1cdc10866f2c..af9c5a5cc0d5 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -53,7 +53,7 @@ public class ResolverDrawerLayout extends ViewGroup {
/**
* Max width of the whole drawer layout
*/
- private int mMaxWidth;
+ private final int mMaxWidth;
/**
* Max total visible height of views not marked always-show when in the closed/initial state
@@ -187,8 +187,10 @@ public class ResolverDrawerLayout extends ViewGroup {
}
public void setSmallCollapsed(boolean smallCollapsed) {
- mSmallCollapsed = smallCollapsed;
- requestLayout();
+ if (mSmallCollapsed != smallCollapsed) {
+ mSmallCollapsed = smallCollapsed;
+ requestLayout();
+ }
}
public boolean isSmallCollapsed() {
@@ -200,9 +202,10 @@ public class ResolverDrawerLayout extends ViewGroup {
}
public void setShowAtTop(boolean showOnTop) {
- mShowAtTop = showOnTop;
- invalidate();
- requestLayout();
+ if (mShowAtTop != showOnTop) {
+ mShowAtTop = showOnTop;
+ requestLayout();
+ }
}
public boolean getShowAtTop() {
@@ -220,6 +223,9 @@ public class ResolverDrawerLayout extends ViewGroup {
public void setCollapsibleHeightReserved(int heightPixels) {
final int oldReserved = mCollapsibleHeightReserved;
mCollapsibleHeightReserved = heightPixels;
+ if (oldReserved != mCollapsibleHeightReserved) {
+ requestLayout();
+ }
final int dReserved = mCollapsibleHeightReserved - oldReserved;
if (dReserved != 0 && mIsDragging) {
@@ -255,7 +261,7 @@ public class ResolverDrawerLayout extends ViewGroup {
if (getShowAtTop()) {
// Keep the drawer fully open.
- mCollapseOffset = 0;
+ setCollapseOffset(0);
return false;
}
@@ -264,9 +270,9 @@ public class ResolverDrawerLayout extends ViewGroup {
if (remainClosed && (oldCollapsibleHeight < mCollapsibleHeight
&& mCollapseOffset == oldCollapsibleHeight)) {
// Stay closed even at the new height.
- mCollapseOffset = mCollapsibleHeight;
+ setCollapseOffset(mCollapsibleHeight);
} else {
- mCollapseOffset = Math.min(mCollapseOffset, mCollapsibleHeight);
+ setCollapseOffset(Math.min(mCollapseOffset, mCollapsibleHeight));
}
final boolean isCollapsedNew = mCollapseOffset != 0;
if (isCollapsedOld != isCollapsedNew) {
@@ -274,11 +280,18 @@ public class ResolverDrawerLayout extends ViewGroup {
}
} else {
// Start out collapsed at first unless we restored state for otherwise
- mCollapseOffset = mOpenOnLayout ? 0 : mCollapsibleHeight;
+ setCollapseOffset(mOpenOnLayout ? 0 : mCollapsibleHeight);
}
return true;
}
+ private void setCollapseOffset(float collapseOffset) {
+ if (mCollapseOffset != collapseOffset) {
+ mCollapseOffset = collapseOffset;
+ requestLayout();
+ }
+ }
+
private int getMaxCollapsedHeight() {
return (isSmallCollapsed() ? mMaxCollapsedHeightSmall : mMaxCollapsedHeight)
+ mCollapsibleHeightReserved;
@@ -420,8 +433,7 @@ public class ResolverDrawerLayout extends ViewGroup {
case MotionEvent.ACTION_POINTER_DOWN: {
final int pointerIndex = ev.getActionIndex();
- final int pointerId = ev.getPointerId(pointerIndex);
- mActivePointerId = pointerId;
+ mActivePointerId = ev.getPointerId(pointerIndex);
mInitialTouchX = ev.getX(pointerIndex);
mInitialTouchY = mLastTouchY = ev.getY(pointerIndex);
}
@@ -924,7 +936,7 @@ public class ResolverDrawerLayout extends ViewGroup {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int sourceWidth = MeasureSpec.getSize(widthMeasureSpec);
int widthSize = sourceWidth;
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
+ final int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Single-use layout; just ignore the mode and use available space.
// Clamp to maxWidth.
diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp
index 4af28ea24361..1520ea5c6831 100644
--- a/core/jni/android_graphics_BLASTBufferQueue.cpp
+++ b/core/jni/android_graphics_BLASTBufferQueue.cpp
@@ -41,7 +41,12 @@ struct {
static JNIEnv* getenv(JavaVM* vm) {
JNIEnv* env;
- if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+ auto result = vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
+ if (result == JNI_EDETACHED) {
+ if (vm->AttachCurrentThreadAsDaemon(&env, nullptr) != JNI_OK) {
+ LOG_ALWAYS_FATAL("Failed to AttachCurrentThread!");
+ }
+ } else if (result != JNI_OK) {
LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", vm);
}
return env;
@@ -60,28 +65,22 @@ public:
}
~TransactionHangCallbackWrapper() {
- if (mTransactionHangObject) {
- getenv()->DeleteGlobalRef(mTransactionHangObject);
+ if (mTransactionHangObject != nullptr) {
+ getenv(mVm)->DeleteGlobalRef(mTransactionHangObject);
mTransactionHangObject = nullptr;
}
}
void onTransactionHang(bool isGpuHang) {
if (mTransactionHangObject) {
- getenv()->CallVoidMethod(mTransactionHangObject,
- gTransactionHangCallback.onTransactionHang, isGpuHang);
+ getenv(mVm)->CallVoidMethod(mTransactionHangObject,
+ gTransactionHangCallback.onTransactionHang, isGpuHang);
}
}
private:
JavaVM* mVm;
jobject mTransactionHangObject;
-
- JNIEnv* getenv() {
- JNIEnv* env;
- mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6);
- return env;
- }
};
static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName,
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index f388fec2cd78..b1610d790222 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -580,6 +580,11 @@ static void nativeSetEarlyWakeupEnd(JNIEnv* env, jclass clazz, jlong transaction
transaction->setEarlyWakeupEnd();
}
+static jlong nativeGetTransactionId(JNIEnv* env, jclass clazz, jlong transactionObj) {
+ auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
+ return transaction->getId();
+}
+
static void nativeSetLayer(JNIEnv* env, jclass clazz, jlong transactionObj,
jlong nativeObject, jint zorder) {
auto transaction = reinterpret_cast<SurfaceComposerClient::Transaction*>(transactionObj);
@@ -2103,6 +2108,8 @@ static const JNINativeMethod sSurfaceControlMethods[] = {
(void*)nativeSetEarlyWakeupStart },
{"nativeSetEarlyWakeupEnd", "(J)V",
(void*)nativeSetEarlyWakeupEnd },
+ {"nativeGetTransactionId", "(J)J",
+ (void*)nativeGetTransactionId },
{"nativeSetLayer", "(JJI)V",
(void*)nativeSetLayer },
{"nativeSetRelativeLayer", "(JJJI)V",
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index 6a421f007a76..285258a979e4 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -473,6 +473,13 @@ message SecureSettingsProto {
}
optional PowerMenuPrivacy power_menu_privacy = 81;
+ message ExtraLowPowerMode {
+ option (android.msg_privacy).dest = DEST_EXPLICIT;
+
+ optional SettingProto extra_automatic_power_save_mode = 1 [ (android.privacy).dest = DEST_AUTOMATIC ];
+ }
+ optional ExtraLowPowerMode extra_low_power_mode = 93;
+
message PrintService {
option (android.msg_privacy).dest = DEST_EXPLICIT;
@@ -679,5 +686,5 @@ message SecureSettingsProto {
// Please insert fields in alphabetical order and group them into messages
// if possible (to avoid reaching the method limit).
- // Next tag = 93;
+ // Next tag = 94;
}
diff --git a/core/proto/android/server/powermanagerservice.proto b/core/proto/android/server/powermanagerservice.proto
index 04f4d7b09d82..bd4f99044b17 100644
--- a/core/proto/android/server/powermanagerservice.proto
+++ b/core/proto/android/server/powermanagerservice.proto
@@ -238,6 +238,7 @@ message PowerServiceSettingsAndConfigurationDumpProto {
optional bool is_stay_on_while_plugged_in_ac = 1;
optional bool is_stay_on_while_plugged_in_usb = 2;
optional bool is_stay_on_while_plugged_in_wireless = 3;
+ optional bool is_stay_on_while_plugged_in_dock = 4;
}
message ScreenBrightnessSettingLimitsProto {
option (.android.msg_privacy).dest = DEST_AUTOMATIC;
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 152d729da3b6..505ef30949ce 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -116,6 +116,7 @@ message KeyguardControllerProto {
repeated KeyguardOccludedProto keyguard_occluded_states = 2 [deprecated=true];
optional bool aod_showing = 3;
repeated KeyguardPerDisplayProto keyguard_per_display = 4;
+ optional bool keyguard_going_away = 5;
}
message KeyguardOccludedProto {
@@ -132,6 +133,7 @@ message KeyguardPerDisplayProto {
optional bool keyguard_showing = 2;
optional bool aod_showing = 3;
optional bool keyguard_occluded = 4;
+ optional bool keyguard_going_away = 5;
}
/* represents PhoneWindowManager */
@@ -576,7 +578,7 @@ message InsetsSourceProviderProto {
optional WindowStateProto pending_control_target = 6;
optional WindowStateProto fake_control_target = 7;
optional .android.view.SurfaceControlProto captured_leash = 8;
- optional .android.graphics.RectProto ime_overridden_frame = 9;
+ optional .android.graphics.RectProto ime_overridden_frame = 9 [deprecated=true];
optional bool is_leash_ready_for_dispatching = 10;
optional bool client_visible = 11;
optional bool server_visible = 12;
diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto
new file mode 100644
index 000000000000..4161f6554fb6
--- /dev/null
+++ b/core/proto/android/server/windowmanagertransitiontrace.proto
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto3";
+
+package com.android.server.wm.shell;
+
+import "frameworks/base/core/proto/android/server/windowmanagerservice.proto";
+
+option java_multiple_files = true;
+
+/* Represents a file full of transition entries.
+ Encoded, it should start with 0x9 0x57 0x49 0x4e 0x54 0x52 0x41 0x43 0x45 (.TRNTRACE), such
+ that it can be easily identified. */
+message TransitionTraceProto {
+
+ /* constant; MAGIC_NUMBER = (long) MAGIC_NUMBER_H << 32 | MagicNumber.MAGIC_NUMBER_L
+ (this is needed because enums have to be 32 bits and there's no nice way to put 64bit
+ constants into .proto files. */
+ enum MagicNumber {
+ INVALID = 0;
+ MAGIC_NUMBER_L = 0x544e5254; /* TRNT (little-endian ASCII) */
+ MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */
+ }
+
+ fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */
+ int64 timestamp = 2; /* The timestamp of when the trace was started. */
+ repeated Transition transition = 3;
+}
+
+message Transition {
+
+ enum State {
+ COLLECTING = 0;
+ PENDING = -1;
+ STARTED = 1;
+ PLAYING = 2;
+ ABORT = 3;
+ FINISHED = 4;
+ }
+
+ int32 id = 1;
+ int32 transition_type = 2;
+ int64 timestamp = 3;
+ State state = 5;
+ int32 flags = 6;
+ repeated ChangeInfo change = 7;
+ uint64 start_transaction_id = 8;
+ uint64 finish_transaction_id = 9;
+}
+
+message ChangeInfo {
+ com.android.server.wm.IdentifierProto window_identifier = 1;
+ int32 transit_mode = 2;
+ bool has_changed = 3;
+ int32 change_flags = 4;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 3b4b830fc0df..25e7c3e3a7f7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6498,6 +6498,13 @@
<permission android:name="android.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS"
android:protectionLevel="signature|privileged" />
+ <!-- Allows an app to set gesture exclusion without restrictions on the vertical extent of the
+ exclusions (see {@link android.view.View#setSystemGestureExclusionRects}).
+ @hide
+ -->
+ <permission android:name="android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION"
+ android:protectionLevel="signature|privileged|recents" />
+
<!-- @SystemApi Allows TV input apps and TV apps to use TIS extension interfaces for
domain-specific features.
<p>Protection level: signature|privileged|vendorPrivileged
@@ -7057,6 +7064,10 @@
android:permission="android.permission.BIND_JOB_SERVICE">
</service>
+ <service android:name="com.android.server.notification.NotificationHistoryJobService"
+ android:permission="android.permission.BIND_JOB_SERVICE" >
+ </service>
+
<service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader"
android:exported="false">
<intent-filter>
diff --git a/core/res/res/anim-ldrtl/task_fragment_clear_top_close_enter.xml b/core/res/res/anim-ldrtl/task_fragment_clear_top_close_enter.xml
new file mode 100644
index 000000000000..6c44bdc04e9e
--- /dev/null
+++ b/core/res/res/anim-ldrtl/task_fragment_clear_top_close_enter.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackdrop="true">
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="100"
+ android:duration="350" />
+
+ <translate
+ android:fromXDelta="5%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+</set> \ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_fragment_clear_top_close_exit.xml b/core/res/res/anim-ldrtl/task_fragment_clear_top_close_exit.xml
new file mode 100644
index 000000000000..65cf2c2c74fc
--- /dev/null
+++ b/core/res/res/anim-ldrtl/task_fragment_clear_top_close_exit.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="0"
+ android:duration="100" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="-25%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+</set> \ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_fragment_clear_top_open_enter.xml b/core/res/res/anim-ldrtl/task_fragment_clear_top_open_enter.xml
new file mode 100644
index 000000000000..022bc22b998b
--- /dev/null
+++ b/core/res/res/anim-ldrtl/task_fragment_clear_top_open_enter.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackdrop="true">
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/linear"
+ android:startOffset="100"
+ android:duration="350" />
+
+ <translate
+ android:fromXDelta="-5%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="450" />
+</set> \ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_fragment_clear_top_open_exit.xml b/core/res/res/anim-ldrtl/task_fragment_clear_top_open_exit.xml
new file mode 100644
index 000000000000..3644dea7da90
--- /dev/null
+++ b/core/res/res/anim-ldrtl/task_fragment_clear_top_open_exit.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="100" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="25%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="450" />
+</set> \ No newline at end of file
diff --git a/core/res/res/anim-ldrtl/task_fragment_open_enter.xml b/core/res/res/anim-ldrtl/task_fragment_open_enter.xml
index b6f1af3e7840..9e3780a764d3 100644
--- a/core/res/res/anim-ldrtl/task_fragment_open_enter.xml
+++ b/core/res/res/anim-ldrtl/task_fragment_open_enter.xml
@@ -17,7 +17,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
<alpha
android:fromAlpha="0"
diff --git a/core/res/res/anim/task_fragment_clear_top_close_enter.xml b/core/res/res/anim/task_fragment_clear_top_close_enter.xml
new file mode 100644
index 000000000000..e33f718d33cc
--- /dev/null
+++ b/core/res/res/anim/task_fragment_clear_top_close_enter.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackdrop="true">
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_decelerate"
+ android:startOffset="100"
+ android:duration="350" />
+
+ <translate
+ android:fromXDelta="-5%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/task_fragment_clear_top_close_exit.xml b/core/res/res/anim/task_fragment_clear_top_close_exit.xml
new file mode 100644
index 000000000000..3d274ba9cd82
--- /dev/null
+++ b/core/res/res/anim/task_fragment_clear_top_close_exit.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="100" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="25%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/task_fragment_clear_top_open_enter.xml b/core/res/res/anim/task_fragment_clear_top_open_enter.xml
new file mode 100644
index 000000000000..b9636616c35d
--- /dev/null
+++ b/core/res/res/anim/task_fragment_clear_top_open_enter.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false"
+ android:showBackdrop="true">
+
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_decelerate"
+ android:startOffset="100"
+ android:duration="350" />
+
+ <translate
+ android:fromXDelta="5%"
+ android:toXDelta="0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_slow_in"
+ android:startOffset="0"
+ android:duration="450" />
+
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/task_fragment_clear_top_open_exit.xml b/core/res/res/anim/task_fragment_clear_top_open_exit.xml
new file mode 100644
index 000000000000..22be7d1004eb
--- /dev/null
+++ b/core/res/res/anim/task_fragment_clear_top_open_exit.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shareInterpolator="false">
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/standard_accelerate"
+ android:startOffset="0"
+ android:duration="100" />
+
+ <translate
+ android:fromXDelta="0"
+ android:toXDelta="-25%"
+ android:fillEnabled="true"
+ android:fillBefore="true"
+ android:fillAfter="true"
+ android:interpolator="@interpolator/fast_out_extra_slow_in"
+ android:duration="450" />
+
+</set> \ No newline at end of file
diff --git a/core/res/res/anim/task_fragment_open_enter.xml b/core/res/res/anim/task_fragment_open_enter.xml
index aa61e6f17070..87ee17925ca7 100644
--- a/core/res/res/anim/task_fragment_open_enter.xml
+++ b/core/res/res/anim/task_fragment_open_enter.xml
@@ -16,7 +16,7 @@
-->
<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:shareInterpolator="false">
+ android:shareInterpolator="false">
<alpha
android:fromAlpha="0"
android:toAlpha="1.0"
diff --git a/core/res/res/anim/task_fragment_open_exit.xml b/core/res/res/anim/task_fragment_open_exit.xml
index b4914d21d097..55a472db750d 100644
--- a/core/res/res/anim/task_fragment_open_exit.xml
+++ b/core/res/res/anim/task_fragment_open_exit.xml
@@ -32,5 +32,5 @@
android:fillBefore="true"
android:fillAfter="true"
android:interpolator="@interpolator/fast_out_extra_slow_in"
- android:duration="400" />
-</set> \ No newline at end of file
+ android:duration="400"/>
+</set>
diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png
index 5152972d2a80..a23f5539cca7 100644
--- a/core/res/res/drawable-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
index 26376fb87cbe..1e272e06221c 100644
--- a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
index 490ebeeb75c1..d10c77d2f7f3 100644
--- a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/layout-car/car_alert_dialog.xml b/core/res/res/layout-car/car_alert_dialog.xml
index 2e7b62ceb723..3c7a5c578fc5 100644
--- a/core/res/res/layout-car/car_alert_dialog.xml
+++ b/core/res/res/layout-car/car_alert_dialog.xml
@@ -54,7 +54,7 @@
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_view_start_margin"
android:layout_marginEnd="@dimen/text_view_end_margin"
- style="@style/CarBody4"/>
+ style="@style/CarDialogMessageText"/>
<!-- we don't need this spacer, but the id needs to be here for compatibility -->
<Space
diff --git a/core/res/res/layout-car/car_alert_dialog_button_bar.xml b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
index 4f815b887088..43fd1eb4c25f 100644
--- a/core/res/res/layout-car/car_alert_dialog_button_bar.xml
+++ b/core/res/res/layout-car/car_alert_dialog_button_bar.xml
@@ -16,13 +16,13 @@
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/buttonPanel"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:scrollbarAlwaysDrawVerticalTrack="true"
- android:scrollIndicators="top|bottom"
- android:fillViewport="true"
- style="?attr/buttonBarStyle">
+ android:id="@+id/buttonPanel"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:scrollbarAlwaysDrawVerticalTrack="true"
+ android:scrollIndicators="top|bottom"
+ android:fillViewport="true"
+ style="?attr/buttonBarStyle">
<com.android.internal.widget.ButtonBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -35,7 +35,7 @@
<Button
android:id="@+id/button3"
- style="@style/CarAction1"
+ style="@style/CarDialogButtonText"
android:minWidth="@dimen/car_touch_target_size"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
@@ -46,7 +46,7 @@
<Button
android:id="@+id/button2"
- style="@style/CarAction1"
+ style="@style/CarDialogButtonText"
android:minWidth="@dimen/car_touch_target_size"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
@@ -57,7 +57,7 @@
<Button
android:id="@+id/button1"
- style="@style/CarAction1"
+ style="@style/CarDialogButtonText"
android:minWidth="@dimen/car_touch_target_size"
android:paddingStart="@dimen/car_padding_2"
android:paddingEnd="@dimen/car_padding_2"
diff --git a/core/res/res/layout/transient_notification_with_icon.xml b/core/res/res/layout/transient_notification_with_icon.xml
new file mode 100644
index 000000000000..e9b17df75333
--- /dev/null
+++ b/core/res/res/layout/transient_notification_with_icon.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:maxWidth="@dimen/toast_width"
+ android:background="?android:attr/toastFrameBackground"
+ android:elevation="@dimen/toast_elevation"
+ android:layout_marginEnd="16dp"
+ android:layout_marginStart="16dp"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp">
+
+ <ImageView
+ android:id="@android:id/icon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@android:id/message"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="end"
+ android:maxLines="2"
+ android:paddingTop="12dp"
+ android:paddingBottom="12dp"
+ android:textAppearance="@style/TextAppearance.Toast"/>
+</LinearLayout>
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index febd14c4c058..cae6165b650d 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Moenie Steur Nie het verander"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tik om te kyk wat geblokkeer word."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Gaan kennisgewinginstellings na"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13 het programme wat jy installeer jou toestemming nodig om kennisgewings te stuur. Tik om hierdie toestemming vir bestaande programme te verander."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Herinner my later"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Maak toe"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Stelsel"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index 1fd1b519f960..8bac983694ec 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"አትረብሽ ተቀይሯል"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"ምን እንደታገደ ለመፈተሽ መታ ያድርጉ።"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"የማሳወቂያ ቅንብሮችን ይገምግሙ"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"በAndroid 13 ላይ የሚጭኗቸው መተግበሪያዎች ማሳወቂያዎችን ለመላክ የእርስዎ ፈቃድ ያስፈልጋቸዋል። ይህን ፈቃድ ለነባር መተግበሪያዎች ለመቀየር መታ ያድርጉ።"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"በኋላ አስታውሰኝ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"አሰናብት"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ሥርዓት"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 59d67e9f181e..61349186ff54 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2057,7 +2057,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"تم تغيير ميزة \"عدم الإزعاج\""</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"انقر للاطّلاع على ما تم حظره."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"مراجعة إعدادات الإشعارات"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"‏في نظام التشغيل Android 13، يجب أن تحصل التطبيقات التي تُثبِّتها على إذن لإرسال الإشعارات. انقر لتغيير هذا الإذن للتطبيقات الحالية."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"تذكيري لاحقًا"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"إغلاق"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"النظام"</string>
@@ -2278,11 +2279,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> (مُترجَم)."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"الرسالة مُترجَمة من <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> إلى <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"النشاط في الخلفية"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"تطبيق يستنزف طاقة البطارية"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"ثمة تطبيق لا يزال نشطًا"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"التطبيق <xliff:g id="APP">%1$s</xliff:g> قيد التشغيل في الخلفية. انقر لإدارة استخدام البطارية."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"قد يؤثر استخدام التطبيق <xliff:g id="APP">%1$s</xliff:g> على عمر البطارية. انقر للاطّلاع على التطبيقات النشطة."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"التحقّق من التطبيقات النشطة"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"يتعذّر الوصول إلى كاميرا الهاتف من على جهاز <xliff:g id="DEVICE">%1$s</xliff:g>."</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 80b2c32eb978..6a32b9c6dcee 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -1037,7 +1037,7 @@
<string name="permdesc_readHistoryBookmarks" msgid="2323799501008967852">"ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰে ব্যৱহাৰ কৰা আটাইবোৰ URLৰ ইতিহাস পঢ়িবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
<string name="permlab_writeHistoryBookmarks" msgid="6090259925187986937">"আপোনাৰ ৱেব বুকমার্কবোৰ আৰু ইতিহাস লিখক"</string>
<string name="permdesc_writeHistoryBookmarks" product="tablet" msgid="573341025292489065">"আপোনাৰ টেবলেটত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
- <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডাটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্‌টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</string>
+ <string name="permdesc_writeHistoryBookmarks" product="tv" msgid="88642768580408561">"এপ্‌টোক আপোনাৰ Android TV ডিভাইচত ষ্ট’ৰ কৰি ৰখা ব্ৰাউজাৰৰ ইতিহাস আৰু বুকমার্কবোৰ সংশোধন কৰিবলৈ অনুমতি দিয়ে। ব্ৰাউজাৰ ডেটা মোহাৰিবলৈ অথবা সংশোধন কৰিবলৈ ই এপ্‌টোক অনুমতি দিব পাৰে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ অথবা ৱেব ব্ৰাউজিঙৰ ক্ষমতা থকা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ কৰা নহ’বও পাৰে।"</string>
<string name="permdesc_writeHistoryBookmarks" product="default" msgid="2245203087160913652">"আপোনাৰ ফ\'নত সঞ্চয় কৰি ৰখা ব্ৰাউজাৰৰ বুকমার্ক আৰু ব্ৰাউজাৰৰ ইতিহাস সংশোধন কৰিবলৈ এপক অনুমতি দিয়ে। টোকা: এই অনুমতি তৃতীয় পক্ষৰ ব্ৰাউজাৰবোৰ বা ৱেব ব্ৰাউজিং কৰিব পৰা অন্য এপ্লিকেশ্বনবোৰৰ দ্বাৰা বলৱৎ নহ\'বও পাৰে।"</string>
<string name="permlab_setAlarm" msgid="1158001610254173567">"এলাৰ্ম ছেট কৰক"</string>
<string name="permdesc_setAlarm" msgid="2185033720060109640">"এপটোক ইনষ্টল হৈ থকা এলাৰ্ম ক্লক এপত এলাৰ্ম ছেট কৰিবলৈ অনুমতি দিয়ে। কিছুমান এলাৰ্ম ক্লক এপত এই সুবিধাটো প্ৰযোজ্য নহ’ব পাৰে।"</string>
@@ -1926,8 +1926,8 @@
<string name="app_suspended_default_message" msgid="6451215678552004172">"এই মুহূৰ্তত <xliff:g id="APP_NAME_0">%1$s</xliff:g> উপলব্ধ নহয়। ইয়াক <xliff:g id="APP_NAME_1">%2$s</xliff:g>এ পৰিচালনা কৰে।"</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"অধিক জানক"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"এপ্‌ আনপজ কৰক"</string>
- <string name="work_mode_off_title" msgid="961171256005852058">"কাম সম্পৰ্কীয় এপ্‌ অন কৰিবনে?"</string>
- <string name="work_mode_off_message" msgid="7319580997683623309">"আপোনাৰ কাম সম্পৰ্কীয় এপ্‌ আৰু জাননীৰ এক্সেছ পাওক"</string>
+ <string name="work_mode_off_title" msgid="961171256005852058">"কৰ্মস্থানৰ এপ্‌ অন কৰিবনে?"</string>
+ <string name="work_mode_off_message" msgid="7319580997683623309">"আপোনাৰ কৰ্মস্থানৰ এপ্‌ আৰু জাননীৰ এক্সেছ পাওক"</string>
<string name="work_mode_turn_on" msgid="3662561662475962285">"অন কৰক"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"এপ্‌টো উপলব্ধ নহয়"</string>
<string name="app_blocked_message" msgid="542972921087873023">"এই মুহূৰ্তত <xliff:g id="APP_NAME">%1$s</xliff:g> উপলব্ধ নহয়।"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"অসুবিধা নিদিব সলনি হৈছে"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"কি কি অৱৰোধ কৰা হৈছে জানিবলৈ টিপক।"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"জাননীৰ ছেটিং পৰ্যালোচনা কৰক"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"আপুনি Android 13ত ইনষ্টল কৰা এপক জাননী পঠিয়াবলৈ আপোনাৰ অনুমতিৰ প্ৰয়োজন। আগৰে পৰা থকা এপৰ বাবে এই অনুমতিটো সলনি কৰিবলৈ টিপক।"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"পাছত মনত পেলাই দিব"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"অগ্ৰাহ্য কৰক"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ছিষ্টেম"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 64007d943987..fa3a9984fcf5 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\"Narahat Etməyin\" rejimi dəyişdirildi"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Nəyin blok edildiyini yoxlamaq üçün klikləyin."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Bildiriş ayarlarını nəzərdən keçirin"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-də quraşdırdığınız tətbiqlər bildiriş göndərmək üçün icazənizi tələb edir. Mövcud tətbiqlər üçün bu icazəni dəyişmək üçün toxunun."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Sonra xatırladın"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Qapadın"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
@@ -2103,7 +2104,7 @@
<string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} + # fayl}other{{file_name} + # fayl}}"</string>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Paylaşmaq üçün tövsiyə edilən bir kimsə yoxdur"</string>
<string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Tətbiq siyahısı"</string>
- <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Bu tətbiqə yazmaq icazəsi verilməyib, lakin, bu USB vasitəsilə səs yaza bilər."</string>
+ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Tətbiqə qeydə almaq icazəsi verilməsə də, bu USB vasitəsilə səsi qeydə ala bilər."</string>
<string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Əsas səhifə"</string>
<string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Geri"</string>
<string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Son Tətbiqlər"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 117120c44b9a..e854f43087e6 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -2054,7 +2054,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Režim Ne uznemiravaj je promenjen"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Dodirnite da biste proverili šta je blokirano."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Pregledajte podešavanja obaveštenja"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"U Android-u 13 aplikacije koje instalirate moraju da imaju dozvolu za slanje obaveštenja. Dodirnite da biste promenili ovu dozvolu za postojeće aplikacije."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Podseti me kasnije"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Odbaci"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 4dbd03ca038c..3197b732ad76 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Зменены налады рэжыму \"Не турбаваць\""</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Націсніце, каб паглядзець заблакіраванае."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Праверце налады апавяшчэнняў"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"У версіі Android 13 усталяваным вамі праграмам неабходна даць дазвол на адпраўку апавяшчэнняў. Націсніце, каб змяніць дазвол для існуючых праграм."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Нагадаць пазней"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Закрыць"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Сістэма"</string>
@@ -2276,11 +2277,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Паведамленне \"<xliff:g id="MESSAGE">%1$s</xliff:g>\" перакладзена."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Паведамленне перакладзена з мовы \"<xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>\" на мову \"<xliff:g id="TO_LANGUAGE">%2$s</xliff:g>\"."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Фонавая дзейнасць"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Праграма разраджае акумулятар"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Праграма па-ранейшаму актыўная"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Праграма \"<xliff:g id="APP">%1$s</xliff:g>\" працуе ў фонавым рэжыме. Націсніце, каб кіраваць выкарыстаннем зараду."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> можа скараціць час працы прылады ад акумулятара. Націсніце, каб праглядзець актыўныя праграмы."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Праверце актыўныя праграмы"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не ўдалося атрымаць доступ да камеры тэлефона з прылады \"<xliff:g id="DEVICE">%1$s</xliff:g>\""</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index ea5aed4d3cc9..ee7af661cb11 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Настройките за „Не безпокойте“ са променени"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Докоснете, за да проверите какво е блокирано."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Преглед на настройките за известия"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Под Android 13 инсталираните от вас приложения трябва да получат разрешението ви, за да изпращат известия. Докоснете, за да промените това разрешение за съществуващите приложения."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Напомняне по-късно"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Отхвърляне"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Система"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Съобщението <xliff:g id="MESSAGE">%1$s</xliff:g> бе преведено."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Съобщението бе преведено от <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> на <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Активност на заден план"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Приложение изтощава батерията"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Приложение е все още активно"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> работи на заден план. Докоснете, за да управлявате използването на батерията."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> може да засегне живота на батерията. Докоснете за преглед на активните приложения."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Проверете активните приложения"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Няма достъп до камерата на телефона от вашия <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index 386677c509ed..b0a832e4f700 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\'বিরক্ত করবে না\' মোডের সেটিং বদলে গেছে"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"কী কী ব্লক করা আছে তা দেখতে ট্যাপ করুন।"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"বিজ্ঞপ্তির সেটিংস পর্যালোচনা করুন"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-এ, আপনার ইনস্টল করা অ্যাপের বিজ্ঞপ্তি পাঠানোর জন্য অনুমতি প্রয়োজন। আগে থাকা অ্যাপের জন্য এই অনুমতি পরিবর্তন করতে ট্যাপ করুন।"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"পরে মনে করিয়ে দিও"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"বাতিল করুন"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"সিস্টেম"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> অনুবাদ করা হয়েছে।"</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"মেসেজ <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> থেকে <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> ভাষাতে অনুবাদ করা হয়েছে।"</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"ব্যাকগ্রাউন্ড অ্যাক্টিভিটি"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"কোনও অ্যাপ ব্যাটারির চার্জ দ্রুত শেষ করে ফেলছে"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"কোনও একটি অ্যাপ এখনও চালু আছে"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"ব্যাকগ্রাউন্ডে <xliff:g id="APP">%1$s</xliff:g> চলছে। ব্যাটারির ব্যবহার ম্যানেজ করতে ট্যাপ করুন।"</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> ব্যাটারির আয়ুকে প্রভাবিত করতে পারে। চালু থাকা অ্যাপ পর্যালোচনা করতে ট্যাপ করুন।"</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"অ্যাক্টিভ অ্যাপ চেক করুন"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"আপনার <xliff:g id="DEVICE">%1$s</xliff:g> থেকে ফোনের ক্যামেরা অ্যাক্সেস করা যাচ্ছে না"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index b1b0e05a5594..b73b6a99a328 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -2054,7 +2054,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Način rada Ne ometaj je promijenjen"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Dodirnite da provjerite šta je blokirano."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Pregledajte postavke obavještenja"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"U Androidu 13 aplikacije koje instalirate trebaju odobrenje da šalju obavještenja. Dodirnite da promijenite odobrenje za postojeće aplikacije."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Podsjeti me kasnije"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Odbaci"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
@@ -2275,9 +2276,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> – prevedeno."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Poruka je prevedena s jezika <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> na <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktivnost u pozadini"</string>
- <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Aplikacija prazni bateriju"</string>
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Aplikacija troši bateriju"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Aplikacija je i dalje aktivna"</string>
- <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> se pokreće u pozadini. Dodirnite da biste upravljali potrošnjom baterije."</string>
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> radi u pozadini. Dodirnite da upravljate potrošnjom baterije."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Aplikacija <xliff:g id="APP">%1$s</xliff:g> može uticati na vijek trajanja baterije. Dodirnite da pregledate aktivne aplikacije."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Provjerite aktivne aplikacije"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nije moguće pristupiti kameri telefona s uređaja <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index c40da7126b50..ded50c717c04 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"S\'ha canviat el mode No molestis"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Toca per consultar què s\'ha bloquejat."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Consulta la configuració de notificacions"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"A Android 13, les aplicacions que instal·les necessiten el teu permís per enviar notificacions. Toca per canviar aquest permís per a les aplicacions existents."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Recorda-m\'ho més tard"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Ignora"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index bb6a14d2f516..aad6bc4d5ed1 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Nastavení režimu Nerušit se změnilo"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Klepnutím zkontrolujete, co je blokováno."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Zkontrolujte nastavení oznámení"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"V systému Android 13 od vás nainstalované aplikace potřebují oprávnění k odesílání oznámení. Klepnutím toto oprávnění změníte pro stávající aplikace."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Připomenout později"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Zavřít"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Systém"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 879e00160d6b..6c12cc2bdb64 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Tilstanden Forstyr ikke blev ændret"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tryk for at se, hvad der er blokeret."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Gennemgå indstillinger for notifikationer"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"I Android 13 skal apps, som du installerer, have din tilladelse til at sende notifikationer. Tryk for at ændre denne indstilling for eksisterende apps."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Påmind mig senere"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Luk"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> er oversat."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Meddelelsen er oversat fra <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> til <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktivitet i baggrunden"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"En app aflader batteriet"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"En app er stadig aktiv"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> kører i baggrunden. Tryk for at administrere batteriforbruget."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> kan påvirke batteritiden. Tryk for at gennemgå nye apps."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Tjek aktive apps"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kameraet på din telefon kan ikke tilgås via din <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 78e986e5d972..f8c75965bf25 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"„Bitte nicht stören“ wurde geändert"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tippe, um zu überprüfen, welche Inhalte blockiert werden."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Benachrichtigungseinstellungen überprüfen"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Bei Android 13 benötigen Apps, die du installierst, die Berechtigung zum Senden von Benachrichtigungen. Wenn du diese Berechtigung für bereits installierte Apps ändern möchtest, tippe hier."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Später erinnern"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Schließen"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index fca6363a848f..e949ce1b15e2 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -1912,8 +1912,8 @@
<string name="importance_from_user" msgid="2782756722448800447">"Μπορείτε να ρυθμίσετε τη βαρύτητα αυτών των ειδοποιήσεων."</string>
<string name="importance_from_person" msgid="4235804979664465383">"Αυτό είναι σημαντικό λόγω των ατόμων που συμμετέχουν."</string>
<string name="notification_history_title_placeholder" msgid="7748630986182249599">"Προσαρμοσμένη ειδοποίηση εφαρμογής"</string>
- <string name="user_creation_account_exists" msgid="2239146360099708035">"Επιτρέπετε στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με τον λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> (υπάρχει ήδη χρήστης με αυτόν τον λογαριασμό);"</string>
- <string name="user_creation_adding" msgid="7305185499667958364">"Επιτρέπετε στην εφαρμογή <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με τον λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g>;"</string>
+ <string name="user_creation_account_exists" msgid="2239146360099708035">"Επιτρέπετε στο <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με τον λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g> (υπάρχει ήδη χρήστης με αυτόν τον λογαριασμό);"</string>
+ <string name="user_creation_adding" msgid="7305185499667958364">"Επιτρέπετε στο <xliff:g id="APP">%1$s</xliff:g> να δημιουργήσει έναν νέο χρήστη με τον λογαριασμό <xliff:g id="ACCOUNT">%2$s</xliff:g>;"</string>
<string name="supervised_user_creation_label" msgid="6884904353827427515">"Προσθήκη εποπτευόμενου χρήστη"</string>
<string name="language_selection_title" msgid="52674936078683285">"Προσθήκη γλώσσας"</string>
<string name="country_selection_title" msgid="5221495687299014379">"Προτίμηση περιοχής"</string>
@@ -2037,7 +2037,7 @@
<string name="harmful_app_warning_uninstall" msgid="6472912975664191772">"ΑΠΕΓΚΑΤΑΣΤΑΣΗ"</string>
<string name="harmful_app_warning_open_anyway" msgid="5963657791740211807">"ΑΝΟΙΓΜΑ"</string>
<string name="harmful_app_warning_title" msgid="8794823880881113856">"Εντοπίστηκε επιβλαβής εφαρμογή"</string>
- <string name="log_access_confirmation_title" msgid="2343578467290592708">"Να επιτρέπεται στην εφαρμογή <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> η πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής;"</string>
+ <string name="log_access_confirmation_title" msgid="2343578467290592708">"Να επιτρέπεται στο <xliff:g id="LOG_ACCESS_APP_NAME">%s</xliff:g> η πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής;"</string>
<string name="log_access_confirmation_allow" msgid="5302517782599389507">"Να επιτρέπεται η πρόσβαση για μία φορά"</string>
<string name="log_access_confirmation_deny" msgid="7685790957455099845">"Να μην επιτραπεί"</string>
<string name="log_access_confirmation_body" msgid="6581985716241928135">"Τα αρχεία καταγραφής συσκευής καταγράφουν ό,τι συμβαίνει στη συσκευή σας. Οι εφαρμογές μπορούν να χρησιμοποιούν αυτά τα αρχεία καταγραφής για να εντοπίζουν και να διορθώνουν ζητήματα.\n\nΟρισμένα αρχεία καταγραφής ενδέχεται να περιέχουν ευαίσθητες πληροφορίες. Ως εκ τούτου, επιτρέψτε την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής μόνο στις εφαρμογές που εμπιστεύεστε. \n\nΕάν δεν επιτρέψετε σε αυτήν την εφαρμογή την πρόσβαση σε όλα τα αρχεία καταγραφής συσκευής, η εφαρμογή εξακολουθεί να έχει πρόσβαση στα δικά της αρχεία καταγραφής. Ο κατασκευαστής της συσκευής σας ενδέχεται να εξακολουθεί να έχει πρόσβαση σε ορισμένα αρχεία καταγραφής ή ορισμένες πληροφορίες στη συσκευή σας. Μάθετε περισσότερα"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Η λειτουργία \"Μην ενοχλείτε\" άλλαξε"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Πατήστε για να ελέγξετε το περιεχόμενο που έχει αποκλειστεί."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Έλεγχος ρυθμίσεων ειδοποιήσεων"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Στο Android 13, οι εφαρμογές που εγκαθιστάτε χρειάζονται την άδειά σας για την αποστολή ειδοποιήσεων. Πατήστε για να αλλάξετε αυτήν την άδεια για υπάρχουσες εφαρμογές."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Υπενθύμιση αργότερα"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Παράβλεψη"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Σύστημα"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 4b55b70faade..aedac3e874ae 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13, apps that you install need your permission to send notifications. Tap to change this permission for existing apps."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Remind me later"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Dismiss"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 7ac8f01f3ad1..88fedc9c2454 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13, apps that you install need your permission to send notifications. Tap to change this permission for existing apps."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Remind me later"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Dismiss"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 5f3faeb01883..dd9de67d24d2 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13, apps that you install need your permission to send notifications. Tap to change this permission for existing apps."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Remind me later"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Dismiss"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 9855d01bfcde..a953730d90c2 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Do Not Disturb has changed"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tap to check what\'s blocked."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Review notification settings"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13, apps that you install need your permission to send notifications. Tap to change this permission for existing apps."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Remind me later"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Dismiss"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 05565ae8c8d2..aff6c07779d0 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -2053,7 +2053,7 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‎‏‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎Do Not Disturb has changed‎‏‎‎‏‎"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‏‏‏‎‎‏‏‎‏‏‏‎‏‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‎‏‎Tap to check what\'s blocked.‎‏‎‎‏‎"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‏‎‎‎Review notification settings‎‏‎‎‏‎"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‏‏‎‎‎‎‎‎‏‎‏‏‏‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‎‎‏‎‎In Android 13, apps that you install need your permission to send notifications. Tap to change this permission for existing apps.‎‏‎‎‏‎"</string>
+ <string name="review_notification_settings_text" msgid="5916244866751849279">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‎‎‎‏‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‏‏‏‏‏‏‎Starting in Android 13, apps that you install need your permission to send notifications. Tap to change this permission for existing apps.‎‏‎‎‏‎"</string>
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‎‎‏‎‏‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‏‏‎Remind me later‎‏‎‎‏‎"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‎‎‎‎‎‏‏‎‎Dismiss‎‏‎‎‏‎"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‎‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‏‏‏‏‏‎‎‎‏‎System‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 3ed995079e8c..62b2d7651107 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Se modificó la opción No interrumpir"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Presiona para consultar lo que está bloqueado."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Revisa la configuración de notificaciones"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"En Android 13, las apps que instales necesitarán tu permiso a fin de enviar notificaciones. Presiona para cambiar este permiso para las apps existentes."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Recordarme más tarde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Descartar"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Se tradujo: <xliff:g id="MESSAGE">%1$s</xliff:g>."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Se tradujo el mensaje del <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> al <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Actividad en segundo plano"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Una app está agotando la batería."</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Hay una app que sigue activa"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> se está ejecutando en segundo plano. Presiona para administrar el uso de batería."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> podría afectar la duración de la batería. Presiona para revisar las apps activas."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Consulta las apps activas"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del dispositivo desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 7be37cf9fed4..593fa2ef9cd4 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -213,7 +213,7 @@
<string name="power_dialog" product="tablet" msgid="8333207765671417261">"Opciones del tablet"</string>
<string name="power_dialog" product="tv" msgid="7792839006640933763">"Opciones de Android TV"</string>
<string name="power_dialog" product="default" msgid="1107775420270203046">"Opciones del teléfono"</string>
- <string name="silent_mode" msgid="8796112363642579333">"Modo silencio"</string>
+ <string name="silent_mode" msgid="8796112363642579333">"Modo Silencio"</string>
<string name="turn_on_radio" msgid="2961717788170634233">"Activar conexión inalámbrica"</string>
<string name="turn_off_radio" msgid="7222573978109933360">"Desactivar función inalámbrica"</string>
<string name="screen_lock" msgid="2072642720826409809">"Bloqueo de pantalla"</string>
@@ -233,8 +233,8 @@
<string name="shutdown_confirm" product="watch" msgid="2977299851200240146">"El reloj se apagará."</string>
<string name="shutdown_confirm" product="default" msgid="136816458966692315">"El teléfono se apagará."</string>
<string name="shutdown_confirm_question" msgid="796151167261608447">"¿Seguro que quieres apagar el teléfono?"</string>
- <string name="reboot_safemode_title" msgid="5853949122655346734">"Reiniciar en modo seguro"</string>
- <string name="reboot_safemode_confirm" msgid="1658357874737219624">"¿Quieres reiniciar el sistema en modo seguro? Se inhabilitarán todas las aplicaciones externas que hayas instalado. Esas aplicaciones se restaurarán la próxima vez que reinicies del sistema."</string>
+ <string name="reboot_safemode_title" msgid="5853949122655346734">"Reiniciar en modo Seguro"</string>
+ <string name="reboot_safemode_confirm" msgid="1658357874737219624">"¿Quieres reiniciar el sistema en modo Seguro? Se inhabilitarán todas las aplicaciones externas que hayas instalado. Esas aplicaciones se restaurarán la próxima vez que reinicies del sistema."</string>
<string name="recent_tasks_title" msgid="8183172372995396653">"Reciente"</string>
<string name="no_recent_tasks" msgid="9063946524312275906">"No hay aplicaciones recientes."</string>
<string name="global_actions" product="tablet" msgid="4412132498517933867">"Opciones del tablet"</string>
@@ -248,7 +248,7 @@
<string name="global_action_bug_report" msgid="5127867163044170003">"Informe de errores"</string>
<string name="global_action_logout" msgid="6093581310002476511">"Finalizar sesión"</string>
<string name="global_action_screenshot" msgid="2610053466156478564">"Captura de pantalla"</string>
- <string name="bugreport_title" msgid="8549990811777373050">"Informar de un error"</string>
+ <string name="bugreport_title" msgid="8549990811777373050">"Informar error"</string>
<string name="bugreport_message" msgid="5212529146119624326">"Se recopilará información sobre el estado actual de tu dispositivo y se enviará por correo electrónico. Pasarán unos minutos desde que empiece a generarse el informe de errores hasta que se envíe."</string>
<string name="bugreport_option_interactive_title" msgid="7968287837902871289">"Informe interactivo"</string>
<string name="bugreport_option_interactive_summary" msgid="8493795476325339542">"Usa esta opción en la mayoría de los casos. Te permite realizar un seguimiento del progreso del informe, introducir más información sobre el problema y hacer capturas de pantalla. Es posible que se omitan algunas secciones menos utilizadas y que requieran más tiempo."</string>
@@ -257,12 +257,12 @@
<string name="bugreport_countdown" msgid="6418620521782120755">"{count,plural, =1{La captura de pantalla para el informe de errores se hará en # segundo.}other{La captura de pantalla para el informe de errores se hará en # segundos.}}"</string>
<string name="bugreport_screenshot_success_toast" msgid="7986095104151473745">"Se ha hecho la captura de pantalla con el informe de errores"</string>
<string name="bugreport_screenshot_failure_toast" msgid="6736320861311294294">"No se ha podido hacer la captura de pantalla con el informe de errores"</string>
- <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo silencio"</string>
+ <string name="global_action_toggle_silent_mode" msgid="8464352592860372188">"Modo Silencio"</string>
<string name="global_action_silent_mode_on_status" msgid="2371892537738632013">"El sonido está desactivado. Activar"</string>
<string name="global_action_silent_mode_off_status" msgid="6608006545950920042">"El sonido está activado. Desactivar"</string>
- <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Modo avión"</string>
- <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Modo avión activado. Desactivar"</string>
- <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Modo avión desactivado. Activar"</string>
+ <string name="global_actions_toggle_airplane_mode" msgid="6911684460146916206">"Modo Avión"</string>
+ <string name="global_actions_airplane_mode_on_status" msgid="5508025516695361936">"Modo Avión activado. Desactivar"</string>
+ <string name="global_actions_airplane_mode_off_status" msgid="8522219771500505475">"Modo Avión desactivado. Activar"</string>
<string name="global_action_settings" msgid="4671878836947494217">"Ajustes"</string>
<string name="global_action_assist" msgid="2517047220311505805">"Asistencia"</string>
<string name="global_action_voice_assist" msgid="6655788068555086695">"Asistente voz"</string>
@@ -293,7 +293,7 @@
<string name="foreground_service_apps_in_background" msgid="7340037176412387863">"<xliff:g id="NUMBER">%1$d</xliff:g> aplicaciones están usando la batería"</string>
<string name="foreground_service_tap_for_details" msgid="9078123626015586751">"Toca para ver información detallada sobre el uso de datos y de la batería"</string>
<string name="foreground_service_multiple_separator" msgid="5002287361849863168">"<xliff:g id="LEFT_SIDE">%1$s</xliff:g>, <xliff:g id="RIGHT_SIDE">%2$s</xliff:g>"</string>
- <string name="safeMode" msgid="8974401416068943888">"Modo seguro"</string>
+ <string name="safeMode" msgid="8974401416068943888">"Modo Seguro"</string>
<string name="android_system_label" msgid="5974767339591067210">"Sistema Android"</string>
<string name="user_owner_label" msgid="8628726904184471211">"Cambiar al perfil personal"</string>
<string name="managed_profile_label" msgid="7316778766973512382">"Cambiar al perfil de trabajo"</string>
@@ -1696,7 +1696,7 @@
<string name="leave_accessibility_shortcut_on" msgid="6543362062336990814">"Utilizar acceso directo"</string>
<string name="color_inversion_feature_name" msgid="326050048927789012">"Inversión de color"</string>
<string name="color_correction_feature_name" msgid="3655077237805422597">"Corrección de color"</string>
- <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo una mano"</string>
+ <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Modo Una mano"</string>
<string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Atenuación extra"</string>
<string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Al mantener pulsadas las teclas de volumen, se ha activado <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string>
<string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Se han mantenido pulsadas las teclas de volumen. Se ha desactivado <xliff:g id="SERVICE_NAME">%1$s</xliff:g>."</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Ha cambiado el modo No molestar"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Toca para consultar lo que se está bloqueando."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Consulta los ajustes de notificaciones"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"En Android 13, las aplicaciones que instales necesitan tu permiso para enviar notificaciones. Toca para cambiar este permiso en las aplicaciones que ya tengas."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Recordar más tarde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Cerrar"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
@@ -2098,12 +2099,12 @@
<string name="mime_type_spreadsheet_ext" msgid="8720173181137254414">"Hoja de cálculo <xliff:g id="EXTENSION">%1$s</xliff:g>"</string>
<string name="mime_type_presentation" msgid="1145384236788242075">"Presentación"</string>
<string name="mime_type_presentation_ext" msgid="8761049335564371468">"Presentación <xliff:g id="EXTENSION">%1$s</xliff:g>"</string>
- <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"El Bluetooth seguirá activado en el modo avión"</string>
+ <string name="bluetooth_airplane_mode_toast" msgid="2066399056595768554">"El Bluetooth seguirá activado en el modo Avión"</string>
<string name="car_loading_profile" msgid="8219978381196748070">"Cargando"</string>
<string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} y # archivo más}other{{file_name} y # archivos más}}"</string>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"No hay sugerencias de personas con las que compartir"</string>
<string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Lista de aplicaciones"</string>
- <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Esta aplicación no tiene permiso para grabar, pero podría registrar audio con este dispositivo USB."</string>
+ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Esta aplicación no tiene permiso para grabar, pero podría capturar audio con este dispositivo USB."</string>
<string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Inicio"</string>
<string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Atrás"</string>
<string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Aplicaciones recientes"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> traducido."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Mensaje traducido del <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> al <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Actividad en segundo plano"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Una aplicación está agotando la batería"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Una aplicación sigue activa"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> está funcionando en segundo plano. Toca para gestionar el uso de batería."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> podría afectar a la duración de la batería. Toca para ver las aplicaciones activas."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Consultar aplicaciones activas"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"No se puede acceder a la cámara del teléfono desde tu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 6e6349f26bdd..737c802498fb 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -350,7 +350,7 @@
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"Kuva märguanded lukustatud seadmes täisekraantegevustena"</string>
<string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Lubab rakendusel märguandeid lukustatud seadmes täisekraantegevustena kuvada"</string>
<string name="permlab_install_shortcut" msgid="7451554307502256221">"Otseteede installimine"</string>
- <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Lubab rakendusel lisada avakuva otseteid ilma kasutaja sekkumiseta."</string>
+ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"lubab rakendusel lisada avakuva otseteid ilma kasutaja sekkumiseta."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"otseteede desinstallimine"</string>
<string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Lubab rakendusel eemaldada avakuva otseteid ilma kasutaja sekkumiseta."</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"marsruutige väljuvad kõned uuesti"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Režiimi Mitte segada muudeti"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Puudutage, et kontrollida, mis on blokeeritud."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Vaadake üle märguandeseaded"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Operatsioonisüsteemis Android 13 vajavad installitavad rakendused märguannete saatmiseks teie luba. Puudutage, et muuta seda luba olemasolevate rakenduste jaoks."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Tuleta hiljem meelde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Loobu"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Süsteem"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Sõnum „<xliff:g id="MESSAGE">%1$s</xliff:g>” on tõlgitud."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Sõnum on tõlgitud <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> keelest <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> keelde."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Tegevus taustal"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Rakendus kulutab akutoidet"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Rakendus on ikka aktiivne"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Rakendus <xliff:g id="APP">%1$s</xliff:g> töötab taustal. Puudutage akukasutuse haldamiseks."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> võib aku tööiga mõjutada. Puudutage aktiivsete rakenduste ülevaatamiseks."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Vaadake aktiivseid rakendusi"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Teie seadmest <xliff:g id="DEVICE">%1$s</xliff:g> ei pääse telefoni kaamerale juurde"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index 497df3a8c4f1..1f3d169ef39c 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Ez molestatzeko modua aldatu da"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Sakatu zer dagoen blokeatuta ikusteko."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Berrikusi jakinarazpen-ezarpenak"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-n, jakinarazpenak bidaltzeko baimena eman behar diezu instalatzen dituzun aplikazioei. Sakatu hau lehendik dauden aplikazioen baimenak aldatzeko."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Gogorarazi geroago"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Baztertu"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Itzuli da <xliff:g id="MESSAGE">%1$s</xliff:g>."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"<xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> hizkuntzatik <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> hizkuntzara itzuli da mezua."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Atzeko planoko jarduerak"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Aplikazio bat bateria agortzen ari da"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Aplikazio bat aktibo dago oraindik"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Atzeko planoan funtzionatzen ari da <xliff:g id="APP">%1$s</xliff:g>. Bateria-erabilera kudeatzeko, sakatu hau."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Baliteke <xliff:g id="APP">%1$s</xliff:g> aplikazioak bateriaren iraupenean eragina izatea. Sakatu hau aplikazio aktiboak ikusteko."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Ikusi zer aplikazio dauden aktibo"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ezin da atzitu telefonoaren kamera <xliff:g id="DEVICE">%1$s</xliff:g> gailutik"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 745733951f99..2b79b17bee57 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -734,7 +734,7 @@
<string name="permlab_removeDrmCertificates" msgid="710576248717404416">"‏حذف گواهی‌های DRM"</string>
<string name="permdesc_removeDrmCertificates" msgid="4068445390318355716">"‏به برنامه امکان می‌دهد گواهی‌های DRM را حذف کند. نباید برای برنامه‌های عادی هیچ‌وقت لازم باشد."</string>
<string name="permlab_bindCarrierMessagingService" msgid="3363450860593096967">"مقید به سرویس پیام‌رسانی شرکت مخابراتی"</string>
- <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"به کنترل‌کننده اجازه می‌دهد که به سطح بالای میانای کاربر سرویس پیام‌رسانی شرکت مخابراتی مقید شود. هرگز نباید برای برنامه‌های عادی مورد نیاز شود."</string>
+ <string name="permdesc_bindCarrierMessagingService" msgid="6316457028173478345">"به کنترل‌کننده اجازه می‌دهد که به سطح بالای واسط کاربر سرویس پیام‌رسانی شرکت مخابراتی مقید شود. هرگز نباید برای برنامه‌های عادی مورد نیاز شود."</string>
<string name="permlab_bindCarrierServices" msgid="2395596978626237474">"اتصال به سرویس‌های شرکت مخابراتی"</string>
<string name="permdesc_bindCarrierServices" msgid="9185614481967262900">"به دارنده امکان می‌دهد به سرویس‌های شرکت مخابراتی متصل شود. هرگز نباید برای برنامه‌های عادی مورد نیاز باشد."</string>
<string name="permlab_access_notification_policy" msgid="5524112842876975537">"دسترسی به حالت «مزاحم نشوید»"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"«مزاحم نشوید» تغییر کرده است"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"برای بررسی موارد مسدودشده ضربه بزنید."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"مرور تنظیمات اعلان"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"‏در Android نسخه ۱۳، برنامه‌هایی که نصب می‌کنید برای ارسال اعلان به اجازه شما نیاز دارند. برای تغییر دادن این اجازه برای برنامه‌های موجود، ضربه بزنید."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"بعداً یادآوری شود"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"رد شدن"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"سیستم"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index adea5bf0838c..52c1d26902b8 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Älä häiritse ‑tila muuttui"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Napauta niin näet, mitä on estetty."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Tarkista ilmoitusasetukset"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Asentamasi sovellukset tarvitsevat sinulta luvan ilmoitusten lähettämiseen Android 13 ‑käyttöjärjestelmässä. Napauta muuttaaksesi aiempien sovellusten lupia."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Muistuta myöhemmin"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Ohita"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Järjestelmä"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> käännettiin."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Viesti käännettiin kielestä <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> kielelle <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Taustatoiminta"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Sovellus kuluttaa akkua"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Sovellus on edelleen aktiivinen"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> on käynnissä taustalla. Hallitse akun käyttöä napauttamalla."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> saattaa vaikuttaa akunkestoon. Tarkista aktiiviset sovellukset napauttamalla."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Tarkista aktiiviset sovellukset"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> ei pääse puhelimen kameraan"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index 71bd100d7dbf..fc7fc071f7a1 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Les paramètres du mode Ne pas déranger ont changé"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Touchez l\'écran pour vérifier ce qui est bloqué."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Examiner les paramètres de notification"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Sous Android 13, les applications que vous installez ont besoin de votre autorisation pour envoyer des notifications. Touchez pour modifier cette autorisation pour les applications existantes."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Me rappeler plus tard"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Fermer"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Système"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Message <xliff:g id="MESSAGE">%1$s</xliff:g> traduit."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Message traduit : <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> vers <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Activité en arrière-plan"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Une application décharge votre pile"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Une application est toujours active"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> s\'exécute en arrière-plan. Touchez pour gérer l\'utilisation de la pile."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> peut avoir une incidence sur l\'autonomie de la pile. Touchez pour examiner les applications actives."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Vérifier les applications actives"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone à partir de votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 0675e8c00dc4..861d6b020fd7 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Le mode Ne pas déranger a été modifié"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Appuyez pour vérifier les contenus bloqués."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Consulter les paramètres de notification"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Sur Android 13, les applis que vous installez ont besoin de votre autorisation pour vous envoyer des notifications. Appuyez pour modifier cette autorisation pour les applis déjà installées."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Plus tard"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Fermer"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Système"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> traduit."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Message en <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> traduit en <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Activité en arrière-plan"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Une appli décharge la batterie"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Une appli est encore active"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> s\'exécute en arrière-plan. Appuyez pour gérer l\'utilisation de la batterie."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> peut affecter l\'autonomie de la batterie. Appuyez pour consulter les applis actives."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Vérifier les applis actives"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossible d\'accéder à l\'appareil photo du téléphone depuis votre <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 6cadfa1a4d66..0393c4b2ab29 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"O modo Non molestar cambiou"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Toca para comprobar o contido bloqueado."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Consulta a configuración de notificacións"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"En Android 13, as aplicacións que instales necesitan o teu permiso para enviar notificacións. Toca para cambiar este permiso nas aplicacións que xa teñas."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Lembrarmo máis tarde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Pechar"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Mensaxe <xliff:g id="MESSAGE">%1$s</xliff:g> traducida."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Mensaxe traducida do <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> ao <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Actividade en segundo plano"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Hai unha aplicación que está consumindo excesiva batería"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Hai unha aplicación que aínda está activa"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Estase executando en segundo plano a aplicación <xliff:g id="APP">%1$s</xliff:g>. Toca para xestionar o uso da batería."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> podería estar minguando a duración da batería. Toca para revisar as aplicacións activas."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Comprobar aplicacións activas"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Non se puido acceder á cámara do teléfono desde o teu dispositivo (<xliff:g id="DEVICE">%1$s</xliff:g>)"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 43f21d48c4d4..033f7ee5ac58 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -2052,14 +2052,11 @@
<string name="zen_upgrade_notification_visd_content" msgid="3683314609114134946">"વધુ જાણવા અને બદલવા માટે ટૅપ કરો."</string>
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"ખલેલ પાડશો નહીંમાં ફેરફાર થયો છે"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"શું બ્લૉક કરેલ છે તે તપાસવા માટે ટૅપ કરો."</string>
- <!-- no translation found for review_notification_settings_title (5102557424459810820) -->
- <skip />
- <!-- no translation found for review_notification_settings_text (5696497037817525074) -->
- <skip />
- <!-- no translation found for review_notification_settings_remind_me_action (1081081018678480907) -->
- <skip />
- <!-- no translation found for review_notification_settings_dismiss (4160916504616428294) -->
+ <string name="review_notification_settings_title" msgid="5102557424459810820">"નોટિફિકેશનના સેટિંગ રિવ્યૂ કરો"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
<skip />
+ <string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"મને પછી યાદ અપાવજો"</string>
+ <string name="review_notification_settings_dismiss" msgid="4160916504616428294">"છોડી દો"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"સિસ્ટમ"</string>
<string name="notification_app_name_settings" msgid="9088548800899952531">"સેટિંગ"</string>
<string name="notification_appops_camera_active" msgid="8177643089272352083">"કૅમેરા"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index bef53d6afd3d..f02982284476 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -1926,7 +1926,7 @@
<string name="app_suspended_default_message" msgid="6451215678552004172">"फ़िलहाल <xliff:g id="APP_NAME_0">%1$s</xliff:g> उपलब्ध नहीं है. इसे <xliff:g id="APP_NAME_1">%2$s</xliff:g> के ज़रिए प्रबंधित किया जाता है."</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"ज़्यादा जानें"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"ऐप्लिकेशन पर लगी रोक हटाएं"</string>
- <string name="work_mode_off_title" msgid="961171256005852058">"ऑफ़िस के काम से जुड़े ऐप्लिकेशन चालू करना चाहते हैं?"</string>
+ <string name="work_mode_off_title" msgid="961171256005852058">"ऑफ़िस के काम से जुड़े ऐप्लिकेशन चालू करने हैं?"</string>
<string name="work_mode_off_message" msgid="7319580997683623309">"अपने ऑफ़िस के काम से जुड़े ऐप्लिकेशन और सूचनाओं का ऐक्सेस पाएं"</string>
<string name="work_mode_turn_on" msgid="3662561662475962285">"चालू करें"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"ऐप्लिकेशन उपलब्ध नहीं है"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"परेशान न करें की सुविधा बदल गई है"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"टैप करके देखें कि किन चीज़ों पर रोक लगाई गई है."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"सूचना सेटिंग देखें"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 में जो ऐप्लिकेशन इंस्टॉल किए जाएंगे, उन्हें आपको सूचनाएं भेजने के लिए अनुमति लेनी होगी. पहले से इंस्टॉल किए गए ऐप्लिकेशन को दी गई अनुमति बदलने के लिए टैप करें."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"बाद में याद दिलाएं"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"बंद करें"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"सिस्टम"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> का अनुवाद किया गया."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"मैसेज का <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> से <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> में अनुवाद किया गया."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"बैकग्राउंड में हो रही गतिविधि"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"कोई ऐप्लिकेशन, तेज़ी से बैटरी खर्च कर रहा है"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"कोई ऐप्लिकेशन अब भी चालू है"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> बैकग्राउंड में चल रहा है. \'बैटरी खर्च को मैनेज करें\' पर टैप करें."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> से, आपके डिवाइस की बैटरी लाइफ़ पर असर पड़ सकता है. चालू ऐप्लिकेशन देखने के लिए टैप करें."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"चालू ऐप्लिकेशन देखें"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"आपके <xliff:g id="DEVICE">%1$s</xliff:g> से फ़ोन के कैमरे को ऐक्सेस नहीं किया जा सकता"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 21bcd20a27f9..42adb5dfdaa4 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -2054,7 +2054,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Promijenjena je postavka Ne uznemiravaj"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Dodirnite da biste provjerili što je blokirano."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Pregledajte postavke obavijesti"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"U Androidu 13 aplikacije koje instalirate trebaju vaše dopuštenje za slanje obavijesti. Dodirnite da biste promijenili to dopuštenje za postojeće aplikacije."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Podsjeti me kasnije"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Odbaci"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sustav"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 21c40ade7637..c02cff6ac7f1 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Módosultak a Ne zavarjanak mód beállításai"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Koppintson a letiltott elemek megtekintéséhez."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Értesítési beállítások áttekintése"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-as rendszeren a telepített alkalmazásoknak engedélyre van szükségük értesítések küldéséhez. Koppintással módosíthatja ezt az engedélyt a meglévő alkalmazások esetében."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Emlékeztessen később"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Bezárás"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Rendszer"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index 7c966b364d5b..b983004690e6 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -1927,7 +1927,7 @@
<string name="app_suspended_more_details" msgid="211260942831587014">"Մանրամասն"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"Չեղարկել դադարեցումը"</string>
<string name="work_mode_off_title" msgid="961171256005852058">"Միացնե՞լ հավելվածները"</string>
- <string name="work_mode_off_message" msgid="7319580997683623309">"Միացրեք աշխատանքային հավելվածներն ու ծանուցումները"</string>
+ <string name="work_mode_off_message" msgid="7319580997683623309">"Ձեզ հասանելի կդառնան ձեր աշխատանքային հավելվածներն ու ծանուցումները"</string>
<string name="work_mode_turn_on" msgid="3662561662475962285">"Միացնել"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"Հավելվածը հասանելի չէ"</string>
<string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> հավելվածն այս պահին հասանելի չէ։"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"«Չանհանգստացնել» ռեժիմի կարգավորումները փոխվել են"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Հպեք՝ տեսնելու, թե ինչ է արգելափակվել:"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Ստուգեք ծանուցումների կարգավորումները"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-ում ձեր տեղադրած հավելվածներին անհրաժեշտ է տրամադրել ծանուցումներ ուղարկելու թույլտվություն։ Հպեք և փոխեք այս թույլտվությունն արդեն տեղադրված հավելվածների համար։"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Հիշեցնել ավելի ուշ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Փակել"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Համակարգ"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index 61104d3a6ab6..f02a44052093 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -1015,8 +1015,8 @@
<string name="save_password_label" msgid="9161712335355510035">"Konfirmasi"</string>
<string name="double_tap_toast" msgid="7065519579174882778">"Kiat: Ketuk dua kali untuk memperbesar dan memperkecil."</string>
<string name="autofill_this_form" msgid="3187132440451621492">"Isi Otomatis"</string>
- <string name="setup_autofill" msgid="5431369130866618567">"Siapkan Pengisian Otomatis"</string>
- <string name="autofill_window_title" msgid="4379134104008111961">"IsiOtomatis dengan <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
+ <string name="setup_autofill" msgid="5431369130866618567">"Siapkan Isi otomatis"</string>
+ <string name="autofill_window_title" msgid="4379134104008111961">"Isi otomatis dengan <xliff:g id="SERVICENAME">%1$s</xliff:g>"</string>
<string name="autofill_address_name_separator" msgid="8190155636149596125">" "</string>
<string name="autofill_address_summary_name_format" msgid="3402882515222673691">"$1$2$3"</string>
<string name="autofill_address_summary_separator" msgid="760522655085707045">", "</string>
@@ -1986,10 +1986,10 @@
<string name="time_picker_prompt_label" msgid="303588544656363889">"Ketikkan waktu"</string>
<string name="time_picker_text_input_mode_description" msgid="4761160667516611576">"Beralih ke mode masukan teks untuk masukan waktu."</string>
<string name="time_picker_radial_mode_description" msgid="1222342577115016953">"Beralih ke mode jam untuk masukan waktu."</string>
- <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opsi IsiOtomatis"</string>
- <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Simpan untuk IsiOtomatis"</string>
+ <string name="autofill_picker_accessibility_title" msgid="4425806874792196599">"Opsi isi otomatis"</string>
+ <string name="autofill_save_accessibility_title" msgid="1523225776218450005">"Simpan untuk Isi otomatis"</string>
<string name="autofill_error_cannot_autofill" msgid="6528827648643138596">"Konten tidak dapat diisi otomatis"</string>
- <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Tidak ada saran IsiOtomatis"</string>
+ <string name="autofill_picker_no_suggestions" msgid="1076022650427481509">"Tidak ada saran isi otomatis"</string>
<string name="autofill_picker_some_suggestions" msgid="5560549696296202701">"{count,plural, =1{Satu saran isi otomatis}other{# saran isi otomatis}}"</string>
<string name="autofill_save_title" msgid="7719802414283739775">"Simpan ke "<b>"<xliff:g id="LABEL">%1$s</xliff:g>"</b>"?"</string>
<string name="autofill_save_title_with_type" msgid="3002460014579799605">"Simpan <xliff:g id="TYPE">%1$s</xliff:g> ke "<b>"<xliff:g id="LABEL">%2$s</xliff:g>"</b>"?"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Jangan Ganggu telah berubah"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Ketuk untuk memeriksa item yang diblokir."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Tinjau setelan notifikasi"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Di Android 13, aplikasi yang Anda instal memerlukan izin untuk mengirim notifikasi. Ketuk guna mengubah izin ini untuk aplikasi yang sudah ada."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Ingatkan saya nanti"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Tutup"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Diterjemahkan."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Pesan diterjemahkan dari bahasa <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> ke <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktivitas Latar Belakang"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Aplikasi menghabiskan daya baterai"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Aplikasi masih aktif"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> sedang berjalan di latar belakang. Ketuk untuk mengelola penggunaan baterai."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> mungkin memengaruhi masa pakai baterai. Ketuk untuk meninjau aplikasi aktif."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Periksa aplikasi aktif"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Tidak dapat mengakses kamera ponsel dari <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index 8b8a22883db6..ce84af711ad3 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"„Ónáðið ekki“ var breytt"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Ýttu til að skoða hvað lokað hefur verið á."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Yfirfara tilkynningastillingar"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Í Android 13 þurfa forrit sem þú setur upp heimild frá þér til að senda tilkynningar. Ýttu til að breyta þessari heimild fyrir forrit sem fyrir eru."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Minna mig á seinna"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Hunsa"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Kerfi"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> var þýtt."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Skilaboð þýdd úr <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> á <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Bakgrunnsvirkni"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Forrit notar mikið af rafhlöðunni"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Forrit er enn virkt"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> keyrir í bakgrunni. Ýttu til að stjórna rafhlöðunotkun."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> gæti haft áhrif á rafhlöðuendingu. Ýttu til að skoða virk forrit."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Skoða virk forrit"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Ekki er hægt að opna myndavél símans úr <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index bf591eca6d6c..1db6e6660557 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"L\'impostazione Non disturbare è cambiata"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tocca per controllare le notifiche bloccate."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Controlla le impostazioni di notifica"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13, le app che installi devono avere la tua autorizzazione per poter inviare notifiche. Tocca per cambiare questa autorizzazione per le app esistenti."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Ricordamelo dopo"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Ignora"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Messaggio <xliff:g id="MESSAGE">%1$s</xliff:g> tradotto."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Messaggio tradotto dalla lingua <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> alla lingua <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Attività in background"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Un\'app sta consumando la batteria"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"C\'è un\'app ancora attiva"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> è in esecuzione in background. Tocca per gestire l\'utilizzo della batteria."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> potrebbe influire sulla durata della batteria. Tocca per controllare le app attive."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Verifica le app attive"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Impossibile accedere alla fotocamera del telefono dal tuo <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index e1c696f4dca6..044934951ec7 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"ההגדרה \'נא לא להפריע\' השתנתה"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"יש להקיש כדי לבדוק מה חסום."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"בדיקת הגדרת ההתראות"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"‏ב-Android 13, עליך לתת לאפליקציות שהתקנת הרשאה לשלוח התראות. אפשר להקיש כדי לשנות את ההרשאה הזו באפליקציות קיימות."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"תזכירו לי מאוחר יותר"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"סגירה"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"מערכת"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index dff29f980e4b..1d2def79176d 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -834,7 +834,7 @@
<string name="phoneTypeFaxHome" msgid="6678559953115904345">"FAX(自宅)"</string>
<string name="phoneTypePager" msgid="576402072263522767">"ポケベル"</string>
<string name="phoneTypeOther" msgid="6918196243648754715">"その他"</string>
- <string name="phoneTypeCallback" msgid="3455781500844157767">"かけ直す"</string>
+ <string name="phoneTypeCallback" msgid="3455781500844157767">"コールバック"</string>
<string name="phoneTypeCar" msgid="4604775148963129195">"クルマ"</string>
<string name="phoneTypeCompanyMain" msgid="4482773154536455441">"会社代表番号"</string>
<string name="phoneTypeIsdn" msgid="2496238954533998512">"ISDN"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"サイレント モードが変わりました"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"タップしてブロック対象をご確認ください。"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"通知設定の確認"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 では、インストールするアプリに、通知を送信する権限を付与する必要があります。既存のアプリのこの権限を変更するには、タップしてください。"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"後で"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"閉じる"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"システム"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index e98240e45949..7fb4833c1a6d 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"„არ შემაწუხოთ“ რეჟიმი შეცვლილია"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"შეეხეთ იმის სანახავად, თუ რა არის დაბლოკილი."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"შეტყობინების პარამეტრების შემოწმება"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-ზე შეტყობინებების გასაგზავნად საჭიროა თქვენ მიერ დაინსტალირებული აპებისთვის ნებართვის მინიჭება. არსებული აპებისთვის ამ ნებართვის შესაცვლელად შეეხეთ."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"შემახსენე მოგვიან."</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"უარყოფა"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"სისტემა"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index a21479f30767..4bd7c144e635 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -1933,36 +1933,21 @@
<string name="app_blocked_message" msgid="542972921087873023">"<xliff:g id="APP_NAME">%1$s</xliff:g> қазір қолжетімді емес."</string>
<string name="app_streaming_blocked_title" msgid="6090945835898766139">"<xliff:g id="ACTIVITY">%1$s</xliff:g> қолжетімсіз"</string>
<string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Рұқсат қажет"</string>
- <!-- no translation found for app_streaming_blocked_title_for_camera_dialog (3935701653713853065) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_fingerprint_dialog (3516853717714141951) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_microphone_dialog (544822455127171206) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_settings_dialog (196994247017450357) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_settings_dialog (8222710146267948647) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_settings_dialog (6895719984375299791) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message (5024599278277957935) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message (7491114163056552686) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message (1245180131667647277) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_permission_dialog (6306583663205997979) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_permission_dialog (6545624942642129664) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_permission_dialog (8462740631707923000) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_fingerprint_dialog (3470977315395784567) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_fingerprint_dialog (698460091901465092) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_fingerprint_dialog (8552691971910603907) -->
- <skip />
+ <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера қолжетімді емес"</string>
+ <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Телефоннан жалғастыру"</string>
+ <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Микрофон қолжетімді емес"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Android TV параметрлері қолжетімді емес"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Планшет параметрлері қолжетімді емес"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Телефон параметрлері қолжетімді емес"</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="5024599278277957935">"Бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына Android TV құрылғысын пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="7491114163056552686">"Бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына планшетті пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="1245180131667647277">"Бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына телефонды пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tv" msgid="6306583663205997979">"Қазір бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына Android TV құрылғысын пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet" msgid="6545624942642129664">"Қазір бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына планшетті пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="default" msgid="8462740631707923000">"Қазір бұған <xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан кіру мүмкін емес. Оның орнына телефонды пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Бұл қолданба үшін қосымша қауіпсіздік шарасы қажет. Оның орнына Android TV құрылғысын пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Бұл қолданба үшін қосымша қауіпсіздік шарасы қажет. Оның орнына планшетті пайдаланып көріңіз."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Бұл қолданба үшін қосымша қауіпсіздік шарасы қажет. Оның орнына телефонды пайдаланып көріңіз."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Қолданба Android жүйесінің ескі нұсқасына арналған және дұрыс жұмыс істемеуі мүмкін. Жаңартылған нұсқаны тексеріңіз немесе әзірлеушіге хабарласыңыз."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Жаңарту бар-жоғын тексеру"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"Сізде жаңа хабарлар бар"</string>
@@ -2068,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Мазаламау режимі өзгерді"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Түймені түртіп, неге тыйым салынатынын көріңіз."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Хабарландыру параметрлерін қарау"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 нұсқасында орнатылатын қолданбалар үшін хабарландырулар жіберу рұқсаты керек. Бұрынғы қолданбаларда осы рұқсатты өзгерту үшін түртіңіз."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Кейінірек еске салу"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Жабу"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Жүйе"</string>
@@ -2289,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"\"<xliff:g id="MESSAGE">%1$s</xliff:g>\" хабары аударылды."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Хабар мына тілге аударылды: <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>. Түпнұсқаның тілі: <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Фондық режимдегі әрекет"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Қолданба батареяны тез отырғызып жатыр"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Қолданба әлі белсенді"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> фондық режимде жұмыс істеп тұр. Батарея шығынын басқару үшін түртіңіз."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> қолданбасы батарея жұмысының ұзақтығына әсер етуі мүмкін. Белсенді қолданбаларды қарап шығу үшін түртіңіз."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Белсенді қолданбаларды тексеру"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> құрылғысынан телефон камерасын пайдалану мүмкін емес."</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index bf0f8dbb1572..a3447096cb19 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"មុខងារ​កុំ​រំខាន​ត្រូវ​បាន​ប្ដូរ"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"សូមចុច​ដើម្បី​មើល​ថា​​បានទប់ស្កាត់អ្វីខ្លះ។"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"ពិនិត្យមើលការកំណត់ការជូនដំណឹង"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"ក្នុង Android 13 កម្មវិធីដែលអ្នកដំឡើងត្រូវការការអនុញ្ញាតរបស់អ្នក ដើម្បីផ្ញើការជូនដំណឹង។ សូមចុចដើម្បីផ្លាស់ប្ដូរការអនុញ្ញាតនេះសម្រាប់កម្មវិធីដែលមានស្រាប់។"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"រំលឹក​ខ្ញុំ​ពេលក្រោយ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ច្រានចោល"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ប្រព័ន្ធ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index d415d800827d..eb1baf009e37 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"ಅಡಚಣೆ ಮಾಡಬೇಡಿ ಬದಲಾಗಿದೆ"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"ಏನನ್ನು ನಿರ್ಬಂಧಿಸಲಾಗಿದೆ ಎಂಬುದನ್ನು ಪರೀಕ್ಷಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"ಅಧಿಸೂಚನೆ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಪರಿಶೀಲಿಸಿ"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 ನಲ್ಲಿ, ನೀವು ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡುವ ಆ್ಯಪ್‌ಗಳಿಗೆ, ಅಧಿಸೂಚನೆಗಳನ್ನು ಕಳುಹಿಸಲು ನಿಮ್ಮ ಅನುಮತಿಯ ಅಗತ್ಯವಿದೆ. ಅಸ್ತಿತ್ವದಲ್ಲಿರುವ ಆ್ಯಪ್‌ಗಳ ಅನುಮತಿಗಳನ್ನು ಬದಲಾಯಿಸಲು ಟ್ಯಾಪ್ ಮಾಡಿ."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"ನಂತರ ರಿಮೈಂಡ್ ಮಾಡಿ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ವಜಾಗೊಳಿಸಿ"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ಸಿಸ್ಟಂ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index b34d17e859d7..f8b9bdc8031a 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"방해 금지 모드 변경"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"차단된 항목을 확인하려면 탭하세요."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"알림 설정 검토"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13에 설치된 앱에는 알림을 전송하기 위한 권한이 필요합니다. 기존 앱의 알림 전송 권한을 변경하려면 탭하세요."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"나중에 알림"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"닫기"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"시스템"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"다음 메시지가 번역되었습니다. <xliff:g id="MESSAGE">%1$s</xliff:g>"</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"메시지가 <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>에서 <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>로 번역되었습니다."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"백그라운드 활동"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"배터리 소모가 큰 앱이 있습니다"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"앱이 여전히 활성 상태임"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> 앱이 백그라운드에서 실행 중입니다. 배터리 사용량을 관리하려면 탭하세요."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> 앱은 배터리 수명에 영향을 미칠 수 있습니다. 활성 상태인 앱을 확인하려면 탭하세요."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"활성 상태의 앱 확인"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"사용자의 <xliff:g id="DEVICE">%1$s</xliff:g>에서 휴대전화 카메라에 액세스할 수 없습니다."</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index 76956575f6c4..b3074cbfc918 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -1320,7 +1320,7 @@
<string name="sms_short_code_confirm_allow" msgid="920477594325526691">"Жөнөтүү"</string>
<string name="sms_short_code_confirm_deny" msgid="1356917469323768230">"Айнуу"</string>
<string name="sms_short_code_remember_choice" msgid="1374526438647744862">"Менин тандоомду эстеп кал"</string>
- <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Муну кийин Тууралоолор &gt; Колдонмолордон өзгөртө аласыз"</string>
+ <string name="sms_short_code_remember_undo_instruction" msgid="2620984439143080410">"Муну кийин Тууралоо &gt; Колдонмолордон өзгөртө аласыз"</string>
<string name="sms_short_code_confirm_always_allow" msgid="2223014893129755950">"Дайыма уруксат берүү"</string>
<string name="sms_short_code_confirm_never_allow" msgid="2688828813521652079">"Эч качан уруксат берилбесин"</string>
<string name="sim_removed_title" msgid="5387212933992546283">"SIM-карта өчүрүлдү"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\"Тынчымды алба\" режими өзгөрдү"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Бөгөттөлгөн нерселерди көрүү үчүн таптаңыз."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Билдирмелердин жөндөөлөрүн карап чыгуу"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 версиясында билдирмелерди жөнөтүү үчүн орноткон колдонмолоруңузга уруксат берүү керек. Учурдагы колдонмолор үчүн бул уруксатты өзгөртүү үчүн таптап коюңуз."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Кийинчерээк эскертүү"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Жабуу"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Тутум"</string>
@@ -2103,7 +2104,7 @@
<string name="file_count" msgid="3220018595056126969">"{count,plural, =1{{file_name} + # файл}other{{file_name} + # файл}}"</string>
<string name="chooser_no_direct_share_targets" msgid="1511722103987329028">"Бөлүшкөнгө эч ким сунушталган жок"</string>
<string name="chooser_all_apps_button_label" msgid="3230427756238666328">"Колдонмолордун тизмеси"</string>
- <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Бул колдонмонун жаздырууга уруксаты жок, бирок бул USB түзмөгү аркылуу аудиону жаздыра алат."</string>
+ <string name="usb_device_resolve_prompt_warn" msgid="325871329788064199">"Бул колдонмого жаздырууга уруксат берилген эмес, бирок ушул USB түзмөгү аркылуу үндөрдү жаза алат."</string>
<string name="accessibility_system_action_home_label" msgid="3234748160850301870">"Башкы бет"</string>
<string name="accessibility_system_action_back_label" msgid="4205361367345537608">"Артка"</string>
<string name="accessibility_system_action_recents_label" msgid="4782875610281649728">"Акыркы колдонмолор"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index cb8a77df06b3..9e8b1197fbae 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"ປ່ຽນໂໝດຫ້າມລົບກວນແລ້ວ"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"ແຕະເພື່ອກວດສອບວ່າມີຫຍັງຖືກບລັອກໄວ້ແດ່."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"ກວດສອບ​ການ​ຕັ້ງ​ຄ່າ​ການ​ແຈ້ງ​ເຕືອນ"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"ໃນ Android 13, ແອັບຕ່າງໆທີ່ທ່ານຕິດຕັ້ງຈະຕ້ອງໃຊ້ການອະນຸຍາດຂອງທ່ານເພື່ອສົ່ງການແຈ້ງເຕືອນ. ແຕະເພື່ອປ່ຽນການອະນຸຍາດນີ້ສຳລັບແອັບທີ່ມີຢູ່ກ່ອນແລ້ວ."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"ແຈ້ງເຕືອນຂ້ອຍພາຍຫຼັງ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ປິດໄວ້"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ລະບົບ"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"ແປ <xliff:g id="MESSAGE">%1$s</xliff:g> ແລ້ວ."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"ແປຂໍ້ຄວາມຈາກ <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> ເປັນ <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> ແລ້ວ."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"ການເຄື່ອນໄຫວໃນພື້ນຫຼັງ"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"ມີແອັບກຳລັງໃຊ້ແບັດເຕີຣີຫຼາຍ"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"ມີແອັບທີ່ຍັງຄົງນຳໃຊ້ຢູ່"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> ກຳລັງເອີ້ນໃຊ້ໃນພື້ນຫຼັງ. ແຕະເພື່ອຈັດການການໃຊ້ແບັດເຕີຣີ."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> ອາດສົ່ງຜົນກະທົບຕໍ່ອາຍຸແບັດເຕີຣີ. ແຕະເພື່ອກວດສອບແອັບທີ່ນຳໃຊ້ຢູ່."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"ກວດສອບແອັບທີ່ເຄື່ອນໄຫວ"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ບໍ່ສາມາດເຂົ້າເຖິງກ້ອງຖ່າຍຮູບຂອງໂທລະສັບຈາກ <xliff:g id="DEVICE">%1$s</xliff:g> ຂອງທ່ານໄດ້"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index f2ce117330d0..989d57c1cc40 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Netrukdymo režimas pakeistas"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Palieskite, kad patikrintumėte, kas blokuojama."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Peržiūrėkite pranešimų nustatymus"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"13 versijos „Android” jūsų įdiegtoms programoms reikia suteikti leidimą siųsti pranešimus. Palieskite, kad pakeistumėte šį leidimą esamoms programoms."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Priminti vėliau"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Atsisakyti"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
@@ -2276,11 +2277,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Pranešimas „<xliff:g id="MESSAGE">%1$s</xliff:g>“ išverstas."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Pranešimas išverstas iš <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> į <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Veikla fone"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Programa eikvoja akumuliatoriaus energiją"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Programa vis dar aktyvi"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"„<xliff:g id="APP">%1$s</xliff:g>“ veikia fone. Palieskite ir tvarkykite akumuliatoriaus energijos vartojimą."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Programa „<xliff:g id="APP">%1$s</xliff:g>“ gali turėti įtakos akumuliatoriaus veikimo laikui. Palieskite ir peržiūrėkite aktyvias programas."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Peržiūrėkite aktyvias programas"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nepavyko pasiekti telefono fotoaparato iš „<xliff:g id="DEVICE">%1$s</xliff:g>“"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 12b41462e969..076900c9020c 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -2054,7 +2054,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Režīms “Netraucēt” ir mainīts"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Pieskarieties, lai uzzinātu, kas tiek bloķēts."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Pārskatīt paziņojumu iestatījumus"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Operētājsistēmā Android 13 jūsu instalētajām lietotnēm ir nepieciešama jūsu atļauja, lai sūtītu paziņojumus. Pieskarieties, lai mainītu šo atļauju esošajām lietotnēm."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Atgādināt vēlāk"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Noraidīt"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistēma"</string>
@@ -2275,11 +2276,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Iztulkots: <xliff:g id="MESSAGE">%1$s</xliff:g>."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Ziņojums ir iztulkots no šādas valodas: <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> šādā valodā: <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Darbība fonā"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Lietotnes darbības dēļ notiek akumulatora izlāde"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Lietotne joprojām ir aktīva"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Lietotne <xliff:g id="APP">%1$s</xliff:g> darbojas fonā. Pieskarieties, lai pārvaldītu akumulatora lietojumu."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Lietotne <xliff:g id="APP">%1$s</xliff:g> var ietekmēt akumulatora darbības ilgumu. Pieskarieties, lai pārskatītu aktīvās lietotnes."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Pārbaudiet aktīvās lietotnes"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nevar piekļūt tālruņa kamerai no jūsu ierīces (<xliff:g id="DEVICE">%1$s</xliff:g>)."</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index e287a7af7482..1c4b514cae57 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Поставките за „Не вознемирувај“ се изменија"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Допрете за да проверите што е блокирано."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Прегледајте ги поставките за известувања"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Во Android 13, на апликациите што ги инсталирате им е потребна ваша дозвола за испраќање известувања. Допрете за да ја промените оваа дозвола за постојни апликации."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Потсети ме подоцна"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Отфрли"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Систем"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 23236e08b869..9169e97314e7 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\'ശല്യപ്പെടുത്തരുത്\' മാറ്റി"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"എന്തിനെയാണ് ബ്ലോക്ക് ചെയ്‌തതെന്ന് പരിശോധിക്കാൻ ടാപ്പ് ചെയ്യുക."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"അറിയിപ്പ് ക്രമീകരണം അവലോകനം ചെയ്യുക"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-ൽ നിങ്ങൾ ഇൻസ്‌റ്റാൾ ചെയ്യുന്ന ആപ്പുകൾക്ക് അറിയിപ്പുകൾ അയയ്‌ക്കാൻ നിങ്ങളുടെ അനുമതി വേണം. നിലവിലുള്ള ആപ്പുകൾക്ക് ഈ അനുമതി മാറ്റാൻ ടാപ്പ് ചെയ്യുക."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"പിന്നീട് ഓർമ്മിപ്പിക്കൂ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"സിസ്റ്റം"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 45ee7549f757..6fb8c894c740 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -350,7 +350,7 @@
<string name="permlab_fullScreenIntent" msgid="4310888199502509104">"мэдэгдлийг түгжигдсэн төхөөрөмж дээр бүтэн дэлгэцийн үйл ажиллагаа байдлаар үзүүлэх"</string>
<string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Аппад мэдэгдлийг түгжигдсэн төхөөрөмж дээр бүтэн дэлгэцийн үйл ажиллагаа байдлаар үзүүлэхийг зөвшөөрдөг"</string>
<string name="permlab_install_shortcut" msgid="7451554307502256221">"товчлол суулгах"</string>
- <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Аппликейшн нь хэрэглэгчийн оролцоогүйгээр Нүүр дэлгэцний товчлолыг нэмж чадна."</string>
+ <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Аппликейшн нь хэрэглэгчийн оролцоогүйгээр Үндсэн нүүрний товчлолыг нэмж чадна."</string>
<string name="permlab_uninstall_shortcut" msgid="295263654781900390">"товчлолыг устгах"</string>
<string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Аппликейшн нь хэрэглэгчийн оролцоогүйгээр Нүүр дэлгэцний товчлолыг устгаж чадна."</string>
<string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"гарсан дуудлагыг чиглэлийг өөрчлөх"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Бүү саад бол горимыг өөрчилсөн"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Блоклосон зүйлийг шалгахын тулд товшино уу."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Мэдэгдлийн тохиргоог шалгах"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13-т таны суулгасан аппууд мэдэгдэл илгээхийн тулд тэдгээрт таны зөвшөөрөл шаардлагатай. Одоо байгаа аппуудын уг зөвшөөрлийг өөрчлөхийн тулд товшино уу."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Надад дараа сануул"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Хаах"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Систем"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index 408a3ae55340..637342df5082 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"व्यत्यय आणू नका बदलले आहे"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"काय ब्लॉक केले आहे हे तपासण्यासाठी टॅप करा."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"सूचना सेटिंग्जचे पुनरावलोकन करा"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 मध्ये, तुम्ही इंस्टॉल केलेल्या अ‍ॅप्सना सूचना पाठवण्यासाठी तुमच्या परवानगीची आवश्यकता असते. सध्याच्या अ‍ॅप्ससाठी ही परवानगी बदलण्याकरिता टॅप करा."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"मला आठवण करून द्या"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"डिसमिस करा"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"सिस्टम"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index 3a95c940a57d..54dfc16a6857 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -297,7 +297,7 @@
<string name="android_system_label" msgid="5974767339591067210">"Sistem Android"</string>
<string name="user_owner_label" msgid="8628726904184471211">"Beralih ke profil peribadi"</string>
<string name="managed_profile_label" msgid="7316778766973512382">"Beralih ke profil kerja"</string>
- <string name="permgrouplab_contacts" msgid="4254143639307316920">"Kenalan"</string>
+ <string name="permgrouplab_contacts" msgid="4254143639307316920">"Contacts"</string>
<string name="permgroupdesc_contacts" msgid="9163927941244182567">"mengakses kenalan anda"</string>
<string name="permgrouplab_location" msgid="1858277002233964394">"Lokasi"</string>
<string name="permgroupdesc_location" msgid="1995955142118450685">"mengakses lokasi peranti ini"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Jangan Ganggu telah berubah"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Ketik untuk menyemak item yang disekat."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Semak tetapan pemberitahuan"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Dalam Android 13, apl yang anda pasang memerlukan kebenaran anda untuk menghantar pemberitahuan. Ketik untuk menukar kebenaran ini bagi apl sedia ada."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Ingatkan saya nanti"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Ketepikan"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index b655345d6714..78cdb62bff80 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\'မနှောင့်ယှက်ရ\' ပြောင်းလဲသွားပါပြီ"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"ပိတ်ထားသည့်အရာများကို ကြည့်ရန် တို့ပါ။"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"အကြောင်းကြားချက် ဆက်တင်များ စိစစ်ရန်"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 တွင် သင်ထည့်သွင်းသော အက်ပ်များသည် အကြောင်းကြားချက်များပို့ရန် သင်၏ခွင့်ပြုချက် လိုအပ်ပါမည်။ ရှိပြီးသားအက်ပ်များအတွက် ဤခွင့်ပြုချက်ကိုပြောင်းရန် တို့ပါ။"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"နောက်မှ သတိပေးပါ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ပယ်ရန်"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"စနစ်"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 2f495b0151b6..077b76896203 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -171,7 +171,7 @@
<string name="httpErrorBadUrl" msgid="754447723314832538">"Kunne ikke åpne siden fordi nettadressen er ugyldig."</string>
<string name="httpErrorFile" msgid="3400658466057744084">"Fikk ikke tilgang til filen."</string>
<string name="httpErrorFileNotFound" msgid="5191433324871147386">"Fant ikke den forespurte filen."</string>
- <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"For mange forespørsler blir behandlet. Prøv igjen senere."</string>
+ <string name="httpErrorTooManyRequests" msgid="2149677715552037198">"For mange forespørsler blir behandlet. Prøv på nytt senere."</string>
<string name="notification_title" msgid="5783748077084481121">"Påloggingsfeil for <xliff:g id="ACCOUNT">%1$s</xliff:g>"</string>
<string name="contentServiceSync" msgid="2341041749565687871">"Synkronisering"</string>
<string name="contentServiceSyncNotificationTitle" msgid="5766411446676388623">"Kan ikke synkronisere"</string>
@@ -609,7 +609,7 @@
<string name="fingerprint_error_user_canceled" msgid="7685676229281231614">"Fingeravtrykk-operasjonen ble avbrutt av brukeren."</string>
<string name="fingerprint_error_lockout" msgid="7853461265604738671">"For mange forsøk. Prøv på nytt senere."</string>
<string name="fingerprint_error_lockout_permanent" msgid="3895478283943513746">"For mange forsøk. Fingeravtrykkssensoren er slått av."</string>
- <string name="fingerprint_error_unable_to_process" msgid="1148553603490048742">"Prøv igjen."</string>
+ <string name="fingerprint_error_unable_to_process" msgid="1148553603490048742">"Prøv på nytt."</string>
<string name="fingerprint_error_no_fingerprints" msgid="8671811719699072411">"Ingen fingeravtrykk er registrert."</string>
<string name="fingerprint_error_hw_not_present" msgid="578914350967423382">"Denne enheten har ikke fingeravtrykkssensor."</string>
<string name="fingerprint_error_security_update_required" msgid="7750187320640856433">"Sensoren er midlertidig slått av."</string>
@@ -634,7 +634,7 @@
<string name="fingerprint_recalibrate_notification_name" msgid="1414578431898579354">"Opplåsing med fingeravtrykk"</string>
<string name="fingerprint_recalibrate_notification_title" msgid="2406561052064558497">"Kan ikke bruke fingeravtrykkssensoren"</string>
<string name="fingerprint_recalibrate_notification_content" msgid="8519935717822194943">"Gå til en reparasjonsleverandør."</string>
- <string name="face_acquired_insufficient" msgid="2150805835949162453">"Kunne ikke ta opp nøyaktige ansiktsdata Prøv igjen"</string>
+ <string name="face_acquired_insufficient" msgid="2150805835949162453">"Kunne ikke ta opp nøyaktige ansiktsdata Prøv på nytt"</string>
<string name="face_acquired_too_bright" msgid="8070756048978079164">"For lyst. Prøv svakere belysning."</string>
<string name="face_acquired_too_dark" msgid="252573548464426546">"For mørkt. Prøv sterkere belysning."</string>
<string name="face_acquired_too_close" msgid="1628767882971469833">"Flytt telefonen lengre unna"</string>
@@ -647,7 +647,7 @@
<string name="face_acquired_not_detected" msgid="2945945257956443257">"Hold ansiktet ditt rett foran telefonen."</string>
<string name="face_acquired_too_much_motion" msgid="8199691445085189528">"For mye bevegelse. Hold telefonen stødig."</string>
<string name="face_acquired_recalibrate" msgid="8724013080976469746">"Registrer ansiktet ditt på nytt."</string>
- <string name="face_acquired_too_different" msgid="4699657338753282542">"Kan ikke gjenkjenne ansiktet lenger. Prøv igjen."</string>
+ <string name="face_acquired_too_different" msgid="4699657338753282542">"Kan ikke gjenkjenne ansiktet lenger. Prøv på nytt."</string>
<string name="face_acquired_too_similar" msgid="7684650785108399370">"For likt – endre posituren din."</string>
<string name="face_acquired_pan_too_extreme" msgid="7822191262299152527">"Vri hodet ditt litt mindre."</string>
<string name="face_acquired_tilt_too_extreme" msgid="8618210742620248049">"Vri hodet litt mindre."</string>
@@ -661,10 +661,10 @@
<string name="face_error_no_space" msgid="5649264057026021723">"Kan ikke lagre nye ansiktsdata. Slett gamle data først."</string>
<string name="face_error_canceled" msgid="2164434737103802131">"Ansikt-operasjonen ble avbrutt."</string>
<string name="face_error_user_canceled" msgid="5766472033202928373">"Ansiktslås ble avbrutt av brukeren"</string>
- <string name="face_error_lockout" msgid="7864408714994529437">"For mange forsøk. Prøv igjen senere."</string>
+ <string name="face_error_lockout" msgid="7864408714994529437">"For mange forsøk. Prøv på nytt senere."</string>
<string name="face_error_lockout_permanent" msgid="3277134834042995260">"For mange forsøk. Ansiktslås er slått av."</string>
<string name="face_error_lockout_screen_lock" msgid="5062609811636860928">"For mange forsøk. Skriv inn skjermlås i stedet."</string>
- <string name="face_error_unable_to_process" msgid="5723292697366130070">"Kan ikke bekrefte ansiktet. Prøv igjen."</string>
+ <string name="face_error_unable_to_process" msgid="5723292697366130070">"Kan ikke bekrefte ansiktet. Prøv på nytt."</string>
<string name="face_error_not_enrolled" msgid="1134739108536328412">"Du har ikke konfigurert ansiktslås"</string>
<string name="face_error_hw_not_present" msgid="7940978724978763011">"Ansiktslås støttes ikke på denne enheten"</string>
<string name="face_error_security_update_required" msgid="5076017208528750161">"Sensoren er midlertidig slått av."</string>
@@ -952,7 +952,7 @@
<string name="lockscreen_failed_attempts_now_wiping" product="tablet" msgid="8682445539263683414">"Du har foretatt <xliff:g id="NUMBER">%d</xliff:g> mislykkede opplåsinger av nettbrettet. Nettbrettet blir nå tilbakestilt til fabrikkinnstillingene."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="tv" msgid="2205435033340091883">"Du har prøvd å låse opp Android TV-enheten din <xliff:g id="NUMBER">%d</xliff:g> ganger. Android TV-enheten din tilbakestilles nå til fabrikkstandard."</string>
<string name="lockscreen_failed_attempts_now_wiping" product="default" msgid="2203704707679895487">"Du har foretatt <xliff:g id="NUMBER">%d</xliff:g> mislykkede opplåsinger av telefonen. Telefonen blir nå tilbakestilt til fabrikkinnstillingene."</string>
- <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Prøv igjen om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
+ <string name="lockscreen_too_many_failed_attempts_countdown" msgid="6807200118164539589">"Prøv på nytt om <xliff:g id="NUMBER">%d</xliff:g> sekunder."</string>
<string name="lockscreen_forgot_pattern_button_text" msgid="8362442730606839031">"Har du glemt mønsteret?"</string>
<string name="lockscreen_glogin_forgot_pattern" msgid="9218940117797602518">"Opplåsing av konto"</string>
<string name="lockscreen_glogin_too_many_attempts" msgid="3775904917743034195">"For mange forsøk på tegning av mønster"</string>
@@ -1887,7 +1887,7 @@
<string name="stk_cc_ss_to_ussd" msgid="8417905193112944760">"SS-forespørsel endret til USSD-forespørsel"</string>
<string name="stk_cc_ss_to_ss" msgid="132040645206514450">"Endret til ny SS-forespørsel"</string>
<string name="notification_phishing_alert_content_description" msgid="494227305355958790">"Varsel om nettfisking"</string>
- <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Arbeidsprofil"</string>
+ <string name="notification_work_profile_content_description" msgid="5296477955677725799">"Jobbprofil"</string>
<string name="notification_alerted_content_description" msgid="6139691253611265992">"Varslet"</string>
<string name="notification_verified_content_description" msgid="6401483602782359391">"Bekreftet"</string>
<string name="expand_button_content_description_collapsed" msgid="3873368935659010279">"Vis"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Ikke forstyrr er endret"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Trykk for å sjekke hva som er blokkert."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Gjennomgå varslingsinnstillingene"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"I Android 13 må apper du installerer, få tillatelse til å sende varsler. Trykk for å endre denne tillatelsen for eksisterende apper."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Påminn meg senere"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Lukk"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> er oversatt."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Meldingen er oversatt fra <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> til <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktivitet i bakgrunnen"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"En app bruker batteri"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"En app er fremdeles aktiv"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> kjører i bakgrunnen. Trykk for å administrere batteribruken."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> kan påvirke batterilevetiden. Trykk for å gjennomgå aktive apper."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Sjekk aktive apper"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Det er ikke mulig å få tilgang til telefonkameraet fra <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index 1bcab4e85c78..b12c5c0cef06 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -1926,7 +1926,7 @@
<string name="app_suspended_default_message" msgid="6451215678552004172">"<xliff:g id="APP_NAME_0">%1$s</xliff:g> अहिले उपलब्ध छैन। यो <xliff:g id="APP_NAME_1">%2$s</xliff:g> द्वारा व्यवस्थित छ।"</string>
<string name="app_suspended_more_details" msgid="211260942831587014">"थप जान्नुहोस्"</string>
<string name="app_suspended_unsuspend_message" msgid="1665438589450555459">"एपको पज हटाउनुहोस्"</string>
- <string name="work_mode_off_title" msgid="961171256005852058">"कामसम्बन्धी एपहरू सक्षम पार्ने हो?"</string>
+ <string name="work_mode_off_title" msgid="961171256005852058">"कामसम्बन्धी एपहरू अन गर्ने हो?"</string>
<string name="work_mode_off_message" msgid="7319580997683623309">"कामसम्बन्धी एप चलाउने र सूचना प्राप्त गर्ने सुविधा अन गर्नुहोस्"</string>
<string name="work_mode_turn_on" msgid="3662561662475962285">"अन गर्नुहोस्"</string>
<string name="app_blocked_title" msgid="7353262160455028160">"एप उपलब्ध छैन"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"बाधा नपुर्‍याउनुहोस् मोड परिवर्तन भएको छ"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"रोक लगाइएका कुराहरू जाँच गर्न ट्याप गर्नुहोस्‌।"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"सूचनाका सेटिङको समीक्षा गर्नुहोस्"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android १३ मा तपाईंले अनुमति दिनुभएका खण्डमा मात्र तपाईंले इन्स्टल गर्नुभएका एपले सूचना पठाउन सक्छन्। यसअघि इन्स्टल गरिसकिएका एपका हकमा यो अनुमति परिवर्तन गर्न ट्याप गर्नुहोस्।"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"मलाई पछि स्मरण गराइयोस्"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"हटाउनुहोस्"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"प्रणाली"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> अनुवाद गरिएको छ।"</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"म्यासेज <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> भाषाबाट <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> भाषामा अनुवाद गरिएको छ।"</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"ब्याकग्राउन्डमा गरिएको क्रियाकलाप"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"कुनै एपले ब्याट्री खपत गरिरहेको छ"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"कुनै एप अझै पनि चलिरहेको छ"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> ब्याकग्राउन्डमा चलिरहेको छ। कुन एपले कति ब्याट्री खपत गर्छ भन्ने कुरा व्यवस्थापन गर्न ट्याप गर्नुहोस्।"</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> ले ब्याट्रीको आयु घटाउन सक्छ। सक्रिय एपहरू हेर्न ट्याप गर्नुहोस्।"</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"कुन कुन एप सक्रिय छ भन्ने कुरा जाँच्नुहोस्"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"तपाईंको <xliff:g id="DEVICE">%1$s</xliff:g> मार्फत फोनको क्यामेरा प्रयोग गर्न मिल्दैन"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 93f96104bb63..f5448049bbf6 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\'Niet storen\' is gewijzigd"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tik om te controleren wat er is geblokkeerd."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Instellingen voor meldingen bekijken"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"In Android 13 hebben apps die je installeert je toestemming nodig om meldingen te sturen. Tik om deze toestemming voor bestaande apps te wijzigen."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Later herinneren"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Sluiten"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Systeem"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> vertaald."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Bericht vertaald vanuit het <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> naar het <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Achtergrondactiviteit"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Een app gebruikt de batterij overmatig"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Een app is nog actief"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> is op de achtergrond actief. Tik om het batterijgebruik te beheren."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> kan van invloed zijn op de batterijduur. Tik om actieve apps te bekijken."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Actieve apps checken"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Kan geen toegang tot de camera van de telefoon krijgen vanaf je <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index 6b58faf99960..a51d2f36c9c4 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"’ବିରକ୍ତ କରନ୍ତୁ ନାହିଁ’ ବଦଳିଯାଇଛି"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"କ’ଣ ଅବରୋଧ ହୋଇଛି ଯାଞ୍ଚ କରିବା ପାଇଁ ଟାପ୍ କରନ୍ତୁ।"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"ବିଜ୍ଞପ୍ତି ସେଟିଂସକୁ ସମୀକ୍ଷା କରନ୍ତୁ"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13ରେ, ଆପଣ ଇନଷ୍ଟଲ କରୁଥିବା ଆପ୍ସ ବିଜ୍ଞପ୍ତିଗୁଡ଼ିକୁ ପଠାଇବା ପାଇଁ ଆପଣଙ୍କ ଅନୁମତି ଆବଶ୍ୟକ କରେ। ପୂର୍ବରୁ ଥିବା ଆପ୍ସ ପାଇଁ ଏହି ଅନୁମତିକୁ ପରିବର୍ତ୍ତନ କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"ମୋତେ ପରେ ରିମାଇଣ୍ଡ କର"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ଖାରଜ କରନ୍ତୁ"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ସିଷ୍ଟମ୍‌"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> ଅନୁବାଦ କରାଯାଇଛି।"</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"ମେସେଜ୍, <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>ରୁ <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>କୁ ଅନୁବାଦ କରାଯାଇଛି।"</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"ପୃଷ୍ଠପଟ କାର୍ଯ୍ୟକଳାପ"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"ଏକ ଆପ ବ୍ୟାଟେରୀର ଚାର୍ଜ ଶୀଘ୍ର ସମାପ୍ତ କରୁଛି"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"ଏକ ଆପ ଏବେ ବି ସକ୍ରିୟ ଅଛି"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> ପୃଷ୍ଠପଟରେ ଚାଲୁଛି। ବ୍ୟାଟେରୀ ବ୍ୟବହାର ପରିଚାଳନା କରିବାକୁ ଟାପ କରନ୍ତୁ।"</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> ବ୍ୟାଟେରୀ ଲାଇଫକୁ ପ୍ରଭାବିତ କରିପାରେ। ସକ୍ରିୟ ଆପ୍ସକୁ ସମୀକ୍ଷା କରିବା ପାଇଁ ଟାପ କରନ୍ତୁ।"</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"ସକ୍ରିୟ ଆପଗୁଡ଼ିକୁ ଯାଞ୍ଚ କରନ୍ତୁ"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"ଆପଣଙ୍କ <xliff:g id="DEVICE">%1$s</xliff:g>ରୁ ଫୋନର କ୍ୟାମେରାକୁ ଆକ୍ସେସ କରାଯାଇପାରିବ ନାହିଁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index c02e01d43105..2bc6da6d9d2f 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\'ਪਰੇਸ਼ਾਨ ਨਾ ਕਰੋ\' ਵਿਕਲਪ ਬਦਲ ਗਿਆ ਹੈ"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"ਟੈਪ ਕਰਕੇ ਦੋਖੋ ਕਿ ਕਿਹੜੀਆਂ ਚੀਜ਼ਾਂ ਬਲਾਕ ਕੀਤੀਆਂ ਗਈਆਂ ਹਨ।"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"ਸੂਚਨਾ ਸੈਟਿੰਗਾਂ ਦੀ ਸਮੀਖਿਆ ਕਰੋ"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 ਵਿੱਚ, ਤੁਹਾਡੇ ਵੱਲੋਂ ਸਥਾਪਤ ਕੀਤੀਆਂ ਜਾਣ ਵਾਲੀਆਂ ਐਪਾਂ ਨੂੰ ਸੂਚਨਾਵਾਂ ਭੇਜਣ ਲਈ ਤੁਹਾਡੀ ਇਜਾਜ਼ਤ ਦੀ ਲੋੜ ਹੁੰਦੀ ਹੈ। ਮੌਜੂਦਾ ਐਪਾਂ ਲਈ ਇਸ ਇਜਾਜ਼ਤ ਨੂੰ ਬਦਲਣ ਵਾਸਤੇ ਟੈਪ ਕਰੋ।"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"ਬਾਅਦ ਵਿੱਚ ਯਾਦ ਕਰਵਾਓ"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ਖਾਰਜ ਕਰੋ"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ਸਿਸਟਮ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index 8e44bde5a4bf..6c96e08c2cf2 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Zmiany w trybie Nie przeszkadzać"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Kliknij, by sprawdzić, co jest zablokowane."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Sprawdź ustawienia powiadomień"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Na Androidzie 13 aplikacje, które zainstalujesz, będą potrzebowały zezwolenia na wysyłanie powiadomień. Kliknij, aby zmienić uprawnienia dla istniejących aplikacji."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Przypomnij później"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Odrzuć"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
@@ -2276,11 +2277,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Przetłumaczono wiadomość: <xliff:g id="MESSAGE">%1$s</xliff:g>."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Wiadomość przetłumaczono z języka: <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> na język: <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktywność w tle"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Aplikacja zużywa baterię"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Aplikacja jest wciąż aktywna"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Aplikacja <xliff:g id="APP">%1$s</xliff:g> działa w tle. Kliknij, by zarządzać wykorzystaniem baterii."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Aplikacja <xliff:g id="APP">%1$s</xliff:g> może mieć wpływ na czas pracy na baterii. Kliknij, aby sprawdzić aktywne aplikacje."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Sprawdź aktywne aplikacje"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nie można korzystać z aparatu telefonu na urządzeniu <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index 375e16df61e2..dd51ad91072e 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"O modo \"Não perturbe\" foi alterado"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Toque para verificar o que está bloqueado."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Revise as configurações de notificação"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"No Android 13, os apps que você instala precisam da sua permissão para enviar notificações. Toque para mudar essa permissão para os apps já instalados."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Lembrar mais tarde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Dispensar"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index 47a6fe521568..c51cf863fb70 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"O modo Não incomodar foi alterado"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Toque para verificar o que está bloqueado."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Analise as definições de notificação"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"No Android 13, as apps que instalar precisam da sua autorização para enviar notificações. Toque para alterar esta autorização para as apps existentes."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Lembrar mais tarde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Ignorar"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index 375e16df61e2..dd51ad91072e 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"O modo \"Não perturbe\" foi alterado"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Toque para verificar o que está bloqueado."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Revise as configurações de notificação"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"No Android 13, os apps que você instala precisam da sua permissão para enviar notificações. Toque para mudar essa permissão para os apps já instalados."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Lembrar mais tarde"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Dispensar"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistema"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index 5b84189231e8..c1e6d87b568d 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -2054,7 +2054,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Funcția Nu deranja s-a schimbat"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Atingeți pentru a verifica ce este blocat."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Examinați setările pentru notificări"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Pe Android 13, aplicațiile pe care le instalați necesită permisiunea de a trimite notificări. Atingeți ca să modificați permisiunea pentru aplicațiile existente."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Mai târziu"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Închideți"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index b90c3c3cc87a..ce3cfe748058 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Настройки режима \"Не беспокоить\" изменены"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Нажмите, чтобы проверить настройки."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Проверьте настройки уведомлений"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Если вы хотите получать уведомления от приложения в Android 13, после установки ему нужно предоставить надлежащее разрешение. Нажмите, чтобы настроить разрешения для установленных приложений."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Напомнить позже"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Закрыть"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Система"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 1cb81a98f39d..8fdfeb7346d1 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"බාධා නොකරන්න වෙනස් කර ඇත"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"අවහිර කර ඇති දේ පරීක්ෂා කිරීමට තට්ටු කරන්න."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"දැනුම්දීම් සැකසීම් සමාලෝචනය කරන්න"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 හි, ඔබ ස්ථාපනය කරන යෙදුම්වලට දැනුම්දීම් යැවීමට ඔබගේ අවසරය අවශ්‍ය වේ. තිබෙන යෙදුම් සඳහා මෙම අවසරය වෙනස් කිරීමට තට්ටු කරන්න."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"මට පසුව මතක් කරන්න"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ඉවත ලන්න"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"පද්ධතිය"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index 70f6a51a7043..01583c8603dd 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Režim bez vyrušení sa zmenil"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Klepnutím skontrolujete, čo je blokované."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Kontrola nastavení upozornení"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"V Androide 13 vyžadujú nainštalované aplikácie povolenie, aby mohli odosielať upozornenia. Klepnutím môžete zmeniť toto povolenie pre existujúce aplikácie."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Pripomenúť neskôr"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Zavrieť"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Systém"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 7515456765cc..f69442822825 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -2055,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Način »ne moti« je spremenjen"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Dotaknite se, da preverite, kaj je blokirano."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Preglejte nastavitve obvestil"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"V Androidu 13 bodo aplikacije, ki jih namestite, za pošiljanje obvestil potrebovale vaše dovoljenje. Dotaknite se, če želite spremeniti to dovoljenje za obstoječe aplikacije."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Opomni me pozneje"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Opusti"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index f247fa9fb7b1..52fcea3a2826 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\"Mos shqetëso\" ka ndryshuar"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Trokit për të shënuar atë që është bllokuar"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Rishiko cilësimet e njoftimeve"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Në Android 13, aplikacionet që instalon kanë nevojë për lejen tënde për të dërguar njoftime. Trokit për ta ndryshuar këtë leje për aplikacionet ekzistuese."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Më kujto më vonë"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Hiq"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistemi"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> i përkthyer."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Mesazhi u përkthye nga <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> në <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Aktiviteti në sfond"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Një aplikacion po shkarkon baterinë"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Një aplikacion është ende aktiv"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> po ekzekutohet në sfond. Trokit për të menaxhuar përdorimin e baterisë."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> mund të kenë ndikim në kohëzgjatjen e baterisë. Trokit për të rishikuar aplikacionet aktive."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Kontrollo aplikacionet aktive"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Nuk mund të qasesh në kamerën e telefonit tënd nga <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index 3a5db52eab52..0dbef5a0a2a9 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -2054,7 +2054,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Режим Не узнемиравај је промењен"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Додирните да бисте проверили шта је блокирано."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Прегледајте подешавања обавештења"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"У Android-у 13 апликације које инсталирате морају да имају дозволу за слање обавештења. Додирните да бисте променили ову дозволу за постојеће апликације."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Подсети ме касније"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Одбаци"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Систем"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index 5cf929f9d399..6223b0dfd762 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Stör ej har ändrats"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Tryck om du vill se vad som blockeras."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Granska aviseringsinställningarna"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"I Android 13 behöver appar som du installerar behörighet att skicka aviseringar. Tryck om du vill ändra denna behörighet för befintliga appar."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Påminn mig senare"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Stäng"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> har översatts."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Meddelandet har översatts från <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> till<xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Bakgrundsaktivitet"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"En app laddar ur batteriet"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"En app är fortfarande aktiv"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> körs i bakgrunden. Tryck för att hantera batteriförbrukning."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> kan påverka batteritiden. Tryck för att granska de aktiva apparna."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Kontrollera aktiva appar"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Telefonens kamera kan inte användas från <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index cf7044532e45..aded067bebb4 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Kipengele cha Usinisumbue kimebadilishwa"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Gusa ili uangalie kipengee ambacho kimezuiwa."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Kagua mipangilio ya arifa"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Kwenye Android toleo la 13, programu ambazo unasakinisha zitahitaji ruhusa yako ili zikutumie arifa. Gusa ili ubadilishe ruhusa hii kwa programu zilizopo."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Nikumbushe baadaye"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Ondoa"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Mfumo"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Imetafsiriwa."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Ujumbe umetafsiriwa kwa <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> kutoka <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Shughuli za Chinichini"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Programu inamaliza chaji ya betri haraka"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Programu bado inatumika"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> inatumika chinichini. Gusa ili udhibiti matumizi ya betri."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> inaweza kuathiri muda wa matumizi ya betri. Gusa ili ukague programu zinazotumika."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Angalia programu zinazotumika"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Haiwezi kufikia kamera ya simu kutoka kwenye <xliff:g id="DEVICE">%1$s</xliff:g> yako"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 31d702942b64..b7dd38729bd3 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"தொந்தரவு செய்ய வேண்டாம் அமைப்புகள் மாற்றப்பட்டன"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"எவற்றையெல்லாம் தடுக்கிறது என்பதைப் பார்க்க, தட்டவும்."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"அறிவிப்பு அமைப்புகளை மதிப்பாய்வு செய்யுங்கள்"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13ல், நீங்கள் நிறுவுகின்ற ஆப்ஸ் உங்களுக்கு அறிவிப்புகளை அனுப்ப உங்கள் அனுமதி தேவை. ஏற்கெனவே உள்ள ஆப்ஸுக்கு இந்த அனுமதியை மாற்ற தட்டவும்."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"பின்னர் நினைவூட்டு"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"நிராகரி"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"சிஸ்டம்"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> மொழிபெயர்க்கப்பட்டது."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"<xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> மொழியிலிருந்து <xliff:g id="TO_LANGUAGE">%2$s</xliff:g> மொழிக்கு மெசேஜ் மொழிபெயர்க்கப்பட்டது."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"பின்னணிச் செயல்பாடு"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"ஓர் ஆப்ஸ் பேட்டரியை அதிகமாகப் பயன்படுத்துதல்"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"ஆப்ஸ் செயல்பாட்டில் உள்ளது"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"பின்னணியில் <xliff:g id="APP">%1$s</xliff:g> இயங்கிக் கொண்டிருக்கிறது. பேட்டரி உபயோகத்தை நிர்வகிக்க, தட்டவும்."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> பேட்டரியின் ஆயுளைப் பாதிக்கலாம். செயலிலுள்ள ஆப்ஸைப் பார்க்க தட்டவும்."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"செயலிலுள்ள ஆப்ஸைப் பாருங்கள்"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"உங்கள் <xliff:g id="DEVICE">%1$s</xliff:g> சாதனத்திலிருந்து மொபைலின் கேமராவை அணுக முடியாது"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index 1c3d4fce719f..3b807415d47d 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -1224,7 +1224,7 @@
<string name="unsupported_display_size_show" msgid="980129850974919375">"ఎల్లప్పుడూ చూపు"</string>
<string name="unsupported_compile_sdk_message" msgid="7326293500707890537">"Android OS యొక్క అననుకూల వెర్షన్ కోసం <xliff:g id="APP_NAME">%1$s</xliff:g> రూపొందించబడింది మరియు ఊహించని సమస్యలు తలెత్తవచ్చు. యాప్ యొక్క అప్‌డేట్ చేసిన వెర్షన్ అందుబాటులో ఉండవచ్చు."</string>
<string name="unsupported_compile_sdk_show" msgid="1601210057960312248">"ఎల్లప్పుడూ చూపు"</string>
- <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"అప్‌డేట్ కోసం తనిఖీ చేయి"</string>
+ <string name="unsupported_compile_sdk_check_update" msgid="1103639989147664456">"అప్‌డేట్ కోసం చెక్ చేయి"</string>
<string name="smv_application" msgid="3775183542777792638">"<xliff:g id="APPLICATION">%1$s</xliff:g> యాప్ (<xliff:g id="PROCESS">%2$s</xliff:g> ప్రాసెస్) అది స్వయంగా అమలు చేసే ఖచ్చితమైన మోడ్ విధానాన్ని ఉల్లంఘించింది."</string>
<string name="smv_process" msgid="1398801497130695446">"ప్రక్రియ <xliff:g id="PROCESS">%1$s</xliff:g> అది స్వయంగా అమలు చేసే ఖచ్చితమైన మోడ్ విధానాన్ని ఉల్లంఘించింది."</string>
<string name="android_upgrading_title" product="default" msgid="7279077384220829683">"ఫోన్ అప్‌డేట్‌ అవుతోంది…"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"అంతరాయం కలిగించవద్దు మార్చబడింది"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"బ్లాక్ చేయబడిన దాన్ని తనిఖీ చేయడానికి నొక్కండి."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"నోటిఫికేషన్ సెట్టింగ్‌లను రివ్యూ చేయండి"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13లో, మీరు ఇన్‌స్టాల్ చేసే యాప్‌లకు నోటిఫికేషన్‌లను పంపడానికి మీ అనుమతి అవసరం. ఇప్పటికే ఉన్న యాప్‌ల కోసం ఈ అనుమతిని మార్చడానికి ట్యాప్ చేయండి."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"తర్వాత గుర్తు చేయి"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"విస్మరించండి"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"సిస్టమ్"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> అనువదించబడింది."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"మెసేజ్ <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> నుండి <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>‌కు అనువదించబడింది."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"బ్యాక్‌గ్రౌండ్ యాక్టివిటీ"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"యాప్ బ్యాటరీని డ్రెయిన్ చేస్తోంది"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"యాప్ ఇప్పటికీ యాక్టివ్‌గా ఉంది"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> బ్యాక్‌గ్రౌండ్‌లో రన్ అవుతోంది. బ్యాటరీ వినియోగాన్ని మేనేజ్ చేయడానికి."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> బ్యాటరీ జీవితకాలాన్ని ప్రభావితం చేయవచ్చు. యాక్టివ్ యాప్‌లను రివ్యూ చేయడానికి ట్యాప్ చేయండి."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"యాక్టివ్‌గా ఉన్న యాప్‌లను చెక్ చేయండి"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"మీ <xliff:g id="DEVICE">%1$s</xliff:g> నుండి ఫోన్ కెమెరాను యాక్సెస్ చేయడం సాధ్యపడదు"</string>
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index d52428329119..77e1ab18b95e 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"เปลี่ยน \"ห้ามรบกวน\" แล้ว"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"แตะเพื่อดูรายการที่ถูกบล็อก"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"ตรวจสอบการตั้งค่าการแจ้งเตือน"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"ใน Android 13 แอปที่คุณติดตั้งจะต้องได้รับสิทธิ์จากคุณเพื่อส่งการแจ้งเตือน แตะเพื่อเปลี่ยนแปลงสิทธิ์นี้สำหรับแอปที่มีอยู่"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"เตือนภายหลัง"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"ปิด"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"ระบบ"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index d0504747e3b2..bb071ad28acd 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Binago ang Huwag Istorbohin"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"I-tap para tingnan kung ano ang naka-block."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Suriin ang mga setting ng notification"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Sa Android 13, kakailanganin ng mga app na na-install mo ng pahintulot para magpadala ng mga notification. I-tap para baguhin ang pahintulot na ito para sa mga kasalukuyang app."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Ipaalala mamaya"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"I-dismiss"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"System"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index fbfec602e42a..add4cc3dd699 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Rahatsız Etmeyin modu değişti"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Nelerin engellendiğini kontrol etmek için dokunun."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Bildirim ayarlarını inceleyin"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13\'te yüklediğiniz uygulamaların bildirim göndermek için izninize ihtiyacı vardır. Mevcut uygulamalarda bu izni değiştirmek için dokunun."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Sonra hatırlat"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Kapat"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Sistem"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> Çevrildi."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Mesajın, <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>-<xliff:g id="TO_LANGUAGE">%2$s</xliff:g> çevirisi yapıldı."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Arka Plan Etkinliği"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Bir uygulama, pili hızlı tüketiyor"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Hâlâ etkin olan bir uygulama var"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> arka planda çalışıyor. Pil kullanımını yönetmek için dokunun."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> uygulaması pil ömrünü etkileyebilir. Etkin uygulamaları incelemek için dokunun."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Etkin uygulamaları kontrol edin"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"<xliff:g id="DEVICE">%1$s</xliff:g> cihazınızdan telefonun kamerasına erişilemiyor"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index ec4e52cbbac4..582dc57814f8 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -1935,36 +1935,21 @@
<string name="app_blocked_message" msgid="542972921087873023">"Додаток <xliff:g id="APP_NAME">%1$s</xliff:g> зараз недоступний."</string>
<string name="app_streaming_blocked_title" msgid="6090945835898766139">"Недоступно: <xliff:g id="ACTIVITY">%1$s</xliff:g>"</string>
<string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"Потрібен дозвіл"</string>
- <!-- no translation found for app_streaming_blocked_title_for_camera_dialog (3935701653713853065) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_fingerprint_dialog (3516853717714141951) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_microphone_dialog (544822455127171206) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_settings_dialog (196994247017450357) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_settings_dialog (8222710146267948647) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_title_for_settings_dialog (6895719984375299791) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message (5024599278277957935) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message (7491114163056552686) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message (1245180131667647277) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_permission_dialog (6306583663205997979) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_permission_dialog (6545624942642129664) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_permission_dialog (8462740631707923000) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_fingerprint_dialog (3470977315395784567) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_fingerprint_dialog (698460091901465092) -->
- <skip />
- <!-- no translation found for app_streaming_blocked_message_for_fingerprint_dialog (8552691971910603907) -->
- <skip />
+ <string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"Камера недоступна"</string>
+ <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"Продовжте на телефоні"</string>
+ <string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"Мікрофон недоступний"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"Налаштування Android TV недоступні"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"Налаштування планшета недоступні"</string>
+ <string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"Налаштування телефона недоступні"</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="5024599278277957935">"Цей додаток недоступний на вашому пристрої (<xliff:g id="DEVICE">%1$s</xliff:g>). Спробуйте натомість скористатися пристроєм Android TV."</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="7491114163056552686">"Цей додаток недоступний на вашому пристрої (<xliff:g id="DEVICE">%1$s</xliff:g>). Спробуйте натомість скористатися планшетом."</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="1245180131667647277">"Цей додаток недоступний на вашому пристрої (<xliff:g id="DEVICE">%1$s</xliff:g>). Спробуйте натомість скористатися телефоном."</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tv" msgid="6306583663205997979">"Цей додаток зараз недоступний на вашому пристрої (<xliff:g id="DEVICE">%1$s</xliff:g>). Спробуйте натомість скористатися пристроєм Android TV."</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet" msgid="6545624942642129664">"Цей додаток зараз недоступний на вашому пристрої (<xliff:g id="DEVICE">%1$s</xliff:g>). Спробуйте натомість скористатися планшетом."</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="default" msgid="8462740631707923000">"Цей додаток зараз недоступний на вашому пристрої (<xliff:g id="DEVICE">%1$s</xliff:g>). Спробуйте натомість скористатися телефоном."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"Цьому додатку потрібен додатковий рівень безпеки. Спробуйте натомість скористатися пристроєм Android TV."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"Цьому додатку потрібен додатковий рівень безпеки. Спробуйте натомість скористатися планшетом."</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"Цьому додатку потрібен додатковий рівень безпеки. Спробуйте натомість скористатися телефоном."</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"Цей додаток створений для старішої версії Android і може працювати неналежним чином. Спробуйте знайти оновлення або зв’яжіться з розробником."</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"Шукати оновлення"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"У вас є нові повідомлення"</string>
@@ -2070,7 +2055,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Налаштування режиму \"Не турбувати\" змінено"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Торкніться, щоб перевірити, що заблоковано."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Перегляньте налаштування сповіщень"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"В ОС Android 13 встановленим додаткам потрібно надати дозвіл, щоб вони могли надсилати сповіщення. Натисніть, щоб змінити цей дозвіл для наявних додатків."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Нагадати пізніше"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Закрити"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Система"</string>
@@ -2291,11 +2277,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> (перекладене повідомлення)."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Повідомлення перекладено (мова оригіналу: <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>, мова перекладу: <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>)."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Робота у фоновому режимі"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Додаток швидко розряджає акумулятор"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Додаток досі активний"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"Додаток <xliff:g id="APP">%1$s</xliff:g> працює у фоновому режимі. Натисніть, щоб керувати використанням заряду."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"Додаток <xliff:g id="APP">%1$s</xliff:g> може вплинути на час роботи акумулятора. Натисніть, щоб переглянути активні додатки."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Перевірте активні додатки"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Не вдається отримати доступ до камери телефона з пристрою <xliff:g id="DEVICE">%1$s</xliff:g>"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 80f88497934f..20c931e51716 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"\'ڈسٹرب نہ کریں\' تبدیل ہو گيا ہے"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"مسدود کی گئی چیزوں کو چیک کرنے کے لیے تھپتھپائیں۔"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"اطلاع کی ترتیبات کا جائزہ لیں"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"‏Android 13 میں، اطلاعات بھیجنے کے لیے آپ کے انسٹال کردہ ایپس کو آپ کی اجازت درکار ہوتی ہے۔ موجودہ ایپس کے لیے اس اجازت کو تبدیل کرنے کی خاطر تھپتھپائیں۔"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"بعد میں یاد دلائیں"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"برخاست کریں"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"سسٹم"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> کا ترجمہ کیا گیا۔"</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"پیغام کا ترجمہ <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> سے<xliff:g id="TO_LANGUAGE">%2$s</xliff:g> میں کیا گیا۔"</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"پس منظر کی سرگرمی"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"ایپ کی بیٹری ختم ہو رہی ہے"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"ایک ایپ اب بھی فعال ہے"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> پس منظر میں چل رہی ہے۔ بیٹری کے استعمال کا نظم کرنے کے لیے تھپتھپائیں۔"</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> ایپ بیٹری لائف کو متاثر کر سکتی ہے۔ فعال ایپس کا جائزہ لینے کے لیے تھپتھپائیں۔"</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"فعال ایپس چیک کریں"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"آپ کے <xliff:g id="DEVICE">%1$s</xliff:g> سے فون کے کیمرا تک رسائی حاصل نہیں کی جا سکتی"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index e00860e9dee7..6905f900b465 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Bezovta qilinmasin rejimi sozlamalari o‘zgartirildi"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Nimalar bloklanganini tekshirish uchun bosing"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Bildirishnoma sozlamalarini tekshiring"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Android 13 versiyasida oʻrnatiladigan ilovalar bildirishnoma yuborishiga ruxsat talab etiladi. Mavjud ilovalar uchun bu ruxsatni oʻzgartirish uchun bosing."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Keyinroq eslatilsin"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Yopish"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Tizim"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index 944b02d60fb4..154e8dbab0f7 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Cài đặt Không làm phiền đã thay đổi"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Nhấn để xem những thông báo bị chặn."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Xem lại chế độ cài đặt thông báo"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Trên Android 13, các ứng dụng mà bạn cài đặt sẽ cần bạn cấp quyền gửi thông báo. Hãy nhấn để thay đổi quyền này cho các ứng dụng hiện có."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Nhắc tôi sau"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Đóng"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Hệ thống"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"Đã dịch <xliff:g id="MESSAGE">%1$s</xliff:g>."</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"Đã dịch thông báo từ <xliff:g id="FROM_LANGUAGE">%1$s</xliff:g> sang <xliff:g id="TO_LANGUAGE">%2$s</xliff:g>."</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"Hoạt động trong nền"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"Một ứng dụng đang tiêu hao pin"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"Một ứng dụng vẫn đang hoạt động"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"<xliff:g id="APP">%1$s</xliff:g> đang chạy ẩn. Nhấn để quản lý mức sử dụng pin."</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"<xliff:g id="APP">%1$s</xliff:g> có thể ảnh hưởng đến thời lượng pin. Hãy nhấn để xem các ứng dụng đang hoạt động."</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"Xem các ứng dụng đang hoạt động"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"Không truy cập được vào máy ảnh trên điện thoại từ <xliff:g id="DEVICE">%1$s</xliff:g> của bạn"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 2053b6a4d992..33c78c279555 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"“勿扰”设置有变更"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"点按即可查看屏蔽内容。"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"查看通知设置"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"在 Android 13 中,您安装的应用需要您授予相应权限才能发送通知。点按即可为现有应用更改此权限。"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"稍后提醒我"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"关闭"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"系统"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index eb07e300c129..20410110b244 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -1934,20 +1934,20 @@
<string name="app_streaming_blocked_title" msgid="6090945835898766139">"無法使用「<xliff:g id="ACTIVITY">%1$s</xliff:g>」"</string>
<string name="app_streaming_blocked_title_for_permission_dialog" msgid="4483161748582966785">"需要權限"</string>
<string name="app_streaming_blocked_title_for_camera_dialog" msgid="3935701653713853065">"無法使用相機"</string>
- <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"請繼續在手機上操作"</string>
+ <string name="app_streaming_blocked_title_for_fingerprint_dialog" msgid="3516853717714141951">"請繼續透過手機操作"</string>
<string name="app_streaming_blocked_title_for_microphone_dialog" msgid="544822455127171206">"無法使用麥克風"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="tv" msgid="196994247017450357">"無法使用 Android TV 設定"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="tablet" msgid="8222710146267948647">"無法使用平板電腦設定"</string>
<string name="app_streaming_blocked_title_for_settings_dialog" product="default" msgid="6895719984375299791">"無法使用手機設定"</string>
- <string name="app_streaming_blocked_message" product="tv" msgid="5024599278277957935">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
- <string name="app_streaming_blocked_message" product="tablet" msgid="7491114163056552686">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string>
- <string name="app_streaming_blocked_message" product="default" msgid="1245180131667647277">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string>
- <string name="app_streaming_blocked_message_for_permission_dialog" product="tv" msgid="6306583663205997979">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用 Android TV 裝置。"</string>
- <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet" msgid="6545624942642129664">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用平板電腦。"</string>
- <string name="app_streaming_blocked_message_for_permission_dialog" product="default" msgid="8462740631707923000">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取這個應用程式,請改用手機。"</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"這個應用程式要求進行額外的安全性驗證,請改用 Android TV 裝置。"</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"這個應用程式要求進行額外的安全性驗證,請改用平板電腦。"</string>
- <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"這個應用程式要求進行額外的安全性驗證,請改用手機。"</string>
+ <string name="app_streaming_blocked_message" product="tv" msgid="5024599278277957935">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用 Android TV 裝置。"</string>
+ <string name="app_streaming_blocked_message" product="tablet" msgid="7491114163056552686">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用平板電腦。"</string>
+ <string name="app_streaming_blocked_message" product="default" msgid="1245180131667647277">"無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用手機。"</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tv" msgid="6306583663205997979">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用 Android TV 裝置。"</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="tablet" msgid="6545624942642129664">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用平板電腦。"</string>
+ <string name="app_streaming_blocked_message_for_permission_dialog" product="default" msgid="8462740631707923000">"目前無法在 <xliff:g id="DEVICE">%1$s</xliff:g> 上存取此應用程式,請改用手機。"</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tv" msgid="3470977315395784567">"此應用程式要求額外的安全措施,請改用 Android TV 裝置。"</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="tablet" msgid="698460091901465092">"此應用程式要求額外的安全措施,請改用平板電腦。"</string>
+ <string name="app_streaming_blocked_message_for_fingerprint_dialog" product="default" msgid="8552691971910603907">"此應用程式要求額外的安全措施,請改用手機。"</string>
<string name="deprecated_target_sdk_message" msgid="5203207875657579953">"此應用程式專為舊版 Android 打造,因此可能無法正常運作。請嘗試檢查更新,或與開發人員聯絡。"</string>
<string name="deprecated_target_sdk_app_store" msgid="8456784048558808909">"檢查更新"</string>
<string name="new_sms_notification_title" msgid="6528758221319927107">"您有新的訊息"</string>
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"請勿騷擾已變更"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"輕按即可查看封鎖內容。"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"查看通知設定"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"在 Android 13 中,您安裝的應用程式須獲得授權才能傳送通知。輕按即可變更現有應用程式的這項權限。"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"稍後提醒我"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"關閉"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"系統"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 44dba0be8eb9..5ec0a0c1d0f5 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"「零打擾」設定已變更"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"輕觸即可查看遭封鎖的項目。"</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"查看通知設定"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"在 Android 13 中,你安裝的應用程式必須獲得授權,才能傳送通知。輕觸即可變更現有應用程式的這項權限。"</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"稍後提醒我"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"關閉"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"系統"</string>
@@ -2274,11 +2275,9 @@
<string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"已翻譯<xliff:g id="MESSAGE">%1$s</xliff:g>。"</string>
<string name="ui_translation_accessibility_translation_finished" msgid="3057830947610088465">"訊息內容已從<xliff:g id="FROM_LANGUAGE">%1$s</xliff:g>翻成<xliff:g id="TO_LANGUAGE">%2$s</xliff:g>。"</string>
<string name="notification_channel_abusive_bg_apps" msgid="6092140213264920355">"背景活動"</string>
- <!-- no translation found for notification_title_abusive_bg_apps (994230770856147656) -->
- <skip />
+ <string name="notification_title_abusive_bg_apps" msgid="994230770856147656">"某個應用程式正在耗用大量電力"</string>
<string name="notification_title_long_running_fgs" msgid="8170284286477131587">"某個應用程式目前仍在運作"</string>
- <!-- no translation found for notification_content_abusive_bg_apps (5296898075922695259) -->
- <skip />
+ <string name="notification_content_abusive_bg_apps" msgid="5296898075922695259">"「<xliff:g id="APP">%1$s</xliff:g>」正在背景運作。輕觸即可管理電池用量。"</string>
<string name="notification_content_long_running_fgs" msgid="8258193410039977101">"「<xliff:g id="APP">%1$s</xliff:g>」應用程式可能會影響電池續航力。輕觸即可查看使用中的應用程式。"</string>
<string name="notification_action_check_bg_apps" msgid="4758877443365362532">"查看使用中的應用程式"</string>
<string name="vdm_camera_access_denied" product="default" msgid="6102378580971542473">"無法從 <xliff:g id="DEVICE">%1$s</xliff:g> 存取手機的相機"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 76c39ac35ccb..ee1711cd15f8 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -2053,7 +2053,8 @@
<string name="zen_upgrade_notification_title" msgid="8198167698095298717">"Ukungaphazamisi kushintshile"</string>
<string name="zen_upgrade_notification_content" msgid="5228458567180124005">"Thepha ukuze uhlole ukuthi yini evinjelwe."</string>
<string name="review_notification_settings_title" msgid="5102557424459810820">"Buyekeza amasethingi wesaziso"</string>
- <string name="review_notification_settings_text" msgid="5696497037817525074">"Ku-Android 13, ama-app owafakayo adinga imvume yakho yokuthumela izaziso. Thepha ukuze ushintshe le mvume yama-app akhona kakade."</string>
+ <!-- no translation found for review_notification_settings_text (5916244866751849279) -->
+ <skip />
<string name="review_notification_settings_remind_me_action" msgid="1081081018678480907">"Ngikhumbuze ngesinye isikhathi"</string>
<string name="review_notification_settings_dismiss" msgid="4160916504616428294">"Chitha"</string>
<string name="notification_app_name_system" msgid="3045196791746735601">"Isistimu"</string>
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index d2f31b05313d..515ea5006667 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -2422,6 +2422,15 @@
<!-- When closing the current activity, this is the animation that is
run on the current activity (which is exiting the screen). -->
<attr name="activityCloseExitAnimation" format="reference" />
+ <!-- When closing a dream activity, this is the animation that is
+ run on the dream activity (which is exiting the screen). -->
+ <attr name="dreamActivityCloseExitAnimation" format="reference" />
+ <!-- When opening a dream activity, this is the animation that is
+ run on the dream activity (which is entering the screen). -->
+ <attr name="dreamActivityOpenEnterAnimation" format="reference" />
+ <!-- When opening a dream activity, this is the animation that is
+ run on the old activity (which is exiting the screen). -->
+ <attr name="dreamActivityOpenExitAnimation" format="reference" />
<!-- When opening an activity in a new task, this is the animation that is
run on the activity of the new task (which is entering the screen). -->
<attr name="taskOpenEnterAnimation" format="reference" />
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 7d8e06df2185..24ea5a2bc14d 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1987,6 +1987,10 @@
STREAM_MUSIC as if it's on TV platform. -->
<bool name="config_single_volume">false</bool>
+ <!-- Flag indicating whether notification and ringtone volumes
+ are controlled together (aliasing is true) or not. -->
+ <bool name="config_alias_ring_notif_stream_types">true</bool>
+
<!-- Flag indicating whether platform level volume adjustments are enabled for remote sessions
on grouped devices. -->
<bool name="config_volumeAdjustmentForRemoteGroupSessions">true</bool>
@@ -3451,6 +3455,22 @@
<!-- Specify if the fingerprint hardware support gestures-->
<bool name="config_fingerprintSupportsGestures">false</bool>
+ <!-- The time (in millis) to wait for a power button before sending
+ a successful auth in biometric prompt(for side fingerprint) -->
+ <integer name="config_sidefpsBpPowerPressWindow">300</integer>
+
+ <!-- The time (in millis) to wait for a power button before sending a
+ successful auth in to keyguard(for side fingerprint) -->
+ <integer name="config_sidefpsKeyguardPowerPressWindow">300</integer>
+
+ <!-- The time (in millis) that a power event will ignore future authentications
+ (for side fingerprint) -->
+ <integer name="config_sidefpsPostAuthDowntime">400</integer>
+
+ <!-- The time (in millis) that a finger tap will wait for a power button
+ before dismissing the power dialog during enrollment(for side fingerprint) -->
+ <integer name="config_sidefpsEnrollPowerPressWindow">300</integer>
+
<!-- This config is used to force VoiceInteractionService to start on certain low ram devices.
It declares the package name of VoiceInteractionService that should be started. -->
<string translatable="false" name="config_forceVoiceInteractionServicePackage"></string>
@@ -3712,6 +3732,9 @@
"Guest" and "Reset guest". -->
<bool name="config_guestUserAutoCreated">false</bool>
+ <!-- If true, owner can change guest user ephemeral state via UI option -->
+ <bool name="config_guestUserAllowEphemeralStateChange">true</bool>
+
<!-- Enforce strong auth on boot. Setting this to false represents a security risk and should
not be ordinarily done. The only case in which this might be permissible is in a car head
unit where there are hardware mechanisms to protect the device (physical keys) and not
@@ -4324,6 +4347,9 @@
<!-- Name of a font family to use for medium body text. -->
<string name="config_bodyFontFamilyMedium" translatable="false">sans-serif-medium</string>
+ <!-- Name of the font family to use in the default lockscreen clock -->
+ <string name="config_clockFontFamily" translatable="false">monospace</string>
+
<!-- Size of icon shown beside a preference locked by admin -->
<dimen name="config_restrictedIconSize">@dimen/restricted_icon_size_material</dimen>
@@ -5090,16 +5116,24 @@
-->
<color name="config_letterboxBackgroundColor">@android:color/system_neutral2_900</color>
- <!-- Horizonal position of a center of the letterboxed app window.
+ <!-- Horizontal position of a center of the letterboxed app window.
0 corresponds to the left side of the screen and 1 to the right side. If given value < 0
- or > 1, it is ignored and central positionis used (0.5). -->
+ or > 1, it is ignored and central position is used (0.5). -->
<item name="config_letterboxHorizontalPositionMultiplier" format="float" type="dimen">0.5</item>
- <!-- Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
- device orientation. -->
- <bool name="config_letterboxIsReachabilityEnabled">false</bool>
+ <!-- Vertical position of a center of the letterboxed app window.
+ 0 corresponds to the upper side of the screen and 1 to the lower side. If given value < 0
+ or > 1, it is ignored and central position is used (0.5). -->
+ <item name="config_letterboxVerticalPositionMultiplier" format="float" type="dimen">0.5</item>
+
+ <!-- Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps.
+ -->
+ <bool name="config_letterboxIsHorizontalReachabilityEnabled">false</bool>
+
+ <!-- Whether vertical reachability repositioning is allowed for letterboxed fullscreen apps. -->
+ <bool name="config_letterboxIsVerticalReachabilityEnabled">false</bool>
- <!-- Default horizonal position of the letterboxed app window when reachability is
+ <!-- Default horizontal position of the letterboxed app window when reachability is
enabled and an app is fullscreen in landscape device orientation. When reachability is
enabled, the position can change between left, center and right. This config defines the
default one:
@@ -5107,11 +5141,30 @@
- Option 1 - Center.
- Option 2 - Right.
If given value is outside of this range, the option 1 (center) is assummed. -->
- <integer name="config_letterboxDefaultPositionForReachability">1</integer>
+ <integer name="config_letterboxDefaultPositionForHorizontalReachability">1</integer>
+
+ <!-- Default vertical position of the letterboxed app window when reachability is
+ enabled and an app is fullscreen in portrait device orientation. When reachability is
+ enabled, the position can change between top, center and bottom. This config defines the
+ default one:
+ - Option 0 - Top.
+ - Option 1 - Center.
+ - Option 2 - Bottom.
+ If given value is outside of this range, the option 1 (center) is assummed. -->
+ <integer name="config_letterboxDefaultPositionForVerticalReachability">1</integer>
<!-- Whether displaying letterbox education is enabled for letterboxed fullscreen apps. -->
<bool name="config_letterboxIsEducationEnabled">false</bool>
+ <!-- Default min aspect ratio for unresizable apps which are eligible for size compat mode.
+ Values <= 1.0 will be ignored. Activity min/max aspect ratio restrictions will still be
+ espected so this override can control the maximum screen area that can be occupied by
+ the app in the letterbox mode. -->
+ <item name="config_letterboxDefaultMinAspectRatioForUnresizableApps" format="float" type="dimen">0.0</item>
+
+ <!-- Whether using split screen aspect ratio as a default aspect ratio for unresizable apps. -->
+ <bool name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled">false</bool>
+
<!-- Whether a camera compat controller is enabled to allow the user to apply or revert
treatment for stretched issues in camera viewfinder. -->
<bool name="config_isCameraCompatControlForStretchedIssuesEnabled">false</bool>
@@ -5773,4 +5826,7 @@
<string-array name="config_serviceStateLocationAllowedPackages">
<item>"com.android.phone"</item>
</string-array>
+
+ <!-- Whether the wake screen on notifications feature is available. -->
+ <bool name="config_pulseOnNotificationsAvailable">true</bool>
</resources>
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index b754100a3ed6..2542268a153a 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -684,7 +684,7 @@
<!-- Lighting and shadow properties -->
<dimen name="light_y">0dp</dimen>
- <dimen name="light_z">600dp</dimen>
+ <dimen name="light_z">500dp</dimen>
<dimen name="light_radius">800dp</dimen>
<item type="dimen" format="float" name="ambient_shadow_alpha">0.039</item>
<item type="dimen" format="float" name="spot_shadow_alpha">0.19</item>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 4c0178a8a887..26eee0db8aa1 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1739,6 +1739,8 @@
<string name="fingerprint_error_security_update_required">Sensor temporarily disabled.</string>
<!-- Generic error message shown when fingerprint needs calibration [CHAR LIMIT=150] -->
<string name="fingerprint_error_bad_calibration">Can\u2019t use fingerprint sensor. Visit a repair provider</string>
+ <!-- Generic error message shown when the power button has been pressed. [CHAR LIMIT=150] -->
+ <string name="fingerprint_error_power_pressed">Power button pressed</string>
<!-- Template to be used to name enrolled fingerprints by default. -->
<string name="fingerprint_name_template">Finger <xliff:g id="fingerId" example="1">%d</xliff:g></string>
@@ -1826,9 +1828,9 @@
<!-- Message shown during acquisition when the sensor is dirty [CHAR LIMIT=100] -->
<string name="face_acquired_sensor_dirty">Clean the top of your screen, including the black bar</string>
<!-- Message shown during acquisition when dark glasses were detected [CHAR LIMIT=75] -->
- <string name="face_acquired_dark_glasses_detected">Your face must be fully visible</string>
+ <string name="face_acquired_dark_glasses_detected">@string/face_acquired_dark_glasses_detected_alt</string>
<!-- Message shown during acquisition when a mouth covering was detected [CHAR LIMIT=75] -->
- <string name="face_acquired_mouth_covering_detected">Your face must be fully visible</string>
+ <string name="face_acquired_mouth_covering_detected">@string/face_acquired_mouth_covering_detected_alt</string>
<!-- Message shown during face acquisition when the sensor needs to be recalibrated [CHAR LIMIT=75] -->
<string name="face_acquired_recalibrate_alt">Can\u2019t create your face model. Try again.</string>
diff --git a/core/res/res/values/styles.xml b/core/res/res/values/styles.xml
index 5d17047d7430..bad05e077b7d 100644
--- a/core/res/res/values/styles.xml
+++ b/core/res/res/values/styles.xml
@@ -85,6 +85,9 @@ please see styles_device_defaults.xml.
<item name="activityOpenExitAnimation">@anim/activity_open_exit</item>
<item name="activityCloseEnterAnimation">@anim/activity_close_enter</item>
<item name="activityCloseExitAnimation">@anim/activity_close_exit</item>
+ <item name="dreamActivityCloseExitAnimation">@anim/dream_activity_close_exit</item>
+ <item name="dreamActivityOpenEnterAnimation">@anim/dream_activity_open_enter</item>
+ <item name="dreamActivityOpenExitAnimation">@anim/dream_activity_open_exit</item>
<item name="taskOpenEnterAnimation">@anim/task_open_enter</item>
<item name="taskOpenExitAnimation">@anim/task_open_exit</item>
<item name="launchTaskBehindTargetAnimation">@anim/launch_task_behind_target</item>
diff --git a/core/res/res/values/styles_car.xml b/core/res/res/values/styles_car.xml
index ca3ba936630e..0655fde70934 100644
--- a/core/res/res/values/styles_car.xml
+++ b/core/res/res/values/styles_car.xml
@@ -14,96 +14,15 @@
limitations under the License.
-->
<resources>
- <!-- Car text -->
- <style name="CarBody1">
- <item name="textStyle">normal</item>
- <item name="textSize">@dimen/car_body1_size</item>
- <item name="textColor">@color/car_body1</item>
- </style>
-
- <style name="CarBody1.Light">
- <item name="textColor">@color/car_body1_light</item>
- </style>
-
- <style name="CarBody1.Dark">
- <item name="textColor">@color/car_body2_dark</item>
- </style>
-
- <style name="CarBody2">
- <item name="textStyle">normal</item>
- <item name="textSize">@dimen/car_body2_size</item>
- <item name="textColor">@color/car_body2</item>
- </style>
-
- <style name="CarBody2.Dark">
- <item name="textColor">@color/car_body2_dark</item>
- </style>
- <style name="CarBody2.Light">
- <item name="textColor">@color/car_body2_light</item>
- </style>
-
- <style name="CarBody3">
- <item name="textStyle">normal</item>
- <item name="textSize">@dimen/car_body3_size</item>
- <item name="textColor">@color/car_body3</item>
- </style>
-
- <!-- The smallest styling for body text. The color of this text changes based on the day/night
- mode. -->
- <style name="CarBody4">
+ <!-- The Dialog message text style-->
+ <style name="CarDialogMessageText">
<item name="textStyle">normal</item>
<item name="textSize">@dimen/car_body4_size</item>
<item name="textColor">@color/car_body4</item>
</style>
-
- <style name="CarAction1">
- <item name="textStyle">bold</item>
- <item name="textSize">@dimen/car_action1_size</item>
- <item name="textColor">@color/control_default_material</item>
- </style>
-
- <style name="CarAction1.Dark">
- <item name="textColor">@color/car_highlight_dark</item>
- </style>
- <style name="CarAction1.Light">
- <item name="textColor">@color/car_highlight_light</item>
- </style>
-
- <!-- The styling for title text. The color of this text changes based on day/night mode. -->
- <style name="CarTitle" >
- <item name="textStyle">bold</item>
- <item name="textSize">@dimen/car_title2_size</item>
- <item name="textColor">@color/car_title</item>
- </style>
-
- <!-- Title text that is permanently a dark color. -->
- <style name="CarTitle.Dark" >
- <item name="textColor">@color/car_title_dark</item>
- </style>
-
- <!-- Title text that is permanently a light color. -->
- <style name="CarTitle.Light" >
- <item name="textColor">@color/car_title_light</item>
- </style>
-
- <!-- Action bar -->
- <style name="ActionBarTitle" parent="@style/Widget.DeviceDefault.TextView">
- <item name="android:singleLine">true</item>
- <item name="android:textAppearance">?attr/textAppearanceLarge</item>
- </style>
-
- <style name="ActionBarButton"
- parent="@style/Widget.DeviceDefault.Button.Borderless.Colored">
- <item name="android:textAppearance">@style/ActionBarButtonTextAppearance</item>
- <!-- Button's internal horizontal padding -->
- <item name="android:paddingStart">@*android:dimen/car_padding_3</item>
- <item name="android:paddingEnd">@*android:dimen/car_padding_3</item>
- <item name="android:drawablePadding">@*android:dimen/car_padding_2</item>
- <item name="android:maxWidth">@*android:dimen/action_bar_button_max_width</item>
- </style>
-
- <style name="ActionBarButtonTextAppearance"
- parent="@style/TextAppearance.DeviceDefault.Widget.Button.Borderless.Colored">
- <item name="android:textAllCaps">false</item>
+ <!-- This style makes Dialog button text use medium font weight. -->
+ <style name="CarDialogButtonText">
+ <item name="android:textAppearance">@style/TextAppearance.DeviceDefault.Widget.Button</item>
+ <item name="android:textColor">@color/control_default_material</item>
</style>
</resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index e2ace054e15c..74ef49d3b901 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -273,6 +273,7 @@
<java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
<java-symbol type="bool" name="action_bar_embed_tabs" />
<java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
+ <java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
<java-symbol type="bool" name="config_avoidGfxAccel" />
<java-symbol type="bool" name="config_bluetooth_address_validation" />
<java-symbol type="integer" name="config_chooser_max_targets_per_row" />
@@ -388,6 +389,7 @@
<java-symbol type="bool" name="config_supportsInsecureLockScreen" />
<java-symbol type="bool" name="config_guestUserEphemeral" />
<java-symbol type="bool" name="config_guestUserAutoCreated" />
+ <java-symbol type="bool" name="config_guestUserAllowEphemeralStateChange" />
<java-symbol type="bool" name="config_localDisplaysMirrorContent" />
<java-symbol type="array" name="config_localPrivateDisplayPorts" />
<java-symbol type="integer" name="config_defaultDisplayDefaultColorMode" />
@@ -1561,6 +1563,7 @@
<java-symbol type="layout" name="time_picker_dialog" />
<java-symbol type="layout" name="tooltip" />
<java-symbol type="layout" name="transient_notification" />
+ <java-symbol type="layout" name="transient_notification_with_icon" />
<java-symbol type="layout" name="voice_interaction_session" />
<java-symbol type="layout" name="web_text_view_dropdown" />
<java-symbol type="layout" name="webview_find" />
@@ -1693,6 +1696,10 @@
<java-symbol type="anim" name="task_fragment_close_exit" />
<java-symbol type="anim" name="task_fragment_open_enter" />
<java-symbol type="anim" name="task_fragment_open_exit" />
+ <java-symbol type="anim" name="task_fragment_clear_top_close_enter" />
+ <java-symbol type="anim" name="task_fragment_clear_top_close_exit" />
+ <java-symbol type="anim" name="task_fragment_clear_top_open_enter" />
+ <java-symbol type="anim" name="task_fragment_clear_top_open_exit" />
<java-symbol type="array" name="config_autoRotationTiltTolerance" />
<java-symbol type="array" name="config_longPressVibePattern" />
@@ -2604,10 +2611,15 @@
<java-symbol type="string" name="fingerprint_recalibrate_notification_name" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_title" />
<java-symbol type="string" name="fingerprint_recalibrate_notification_content" />
+ <java-symbol type="string" name="fingerprint_error_power_pressed" />
<!-- Fingerprint config -->
<java-symbol type="integer" name="config_fingerprintMaxTemplatesPerUser"/>
<java-symbol type="bool" name="config_fingerprintSupportsGestures"/>
+ <java-symbol type="integer" name="config_sidefpsBpPowerPressWindow"/>
+ <java-symbol type="integer" name="config_sidefpsKeyguardPowerPressWindow"/>
+ <java-symbol type="integer" name="config_sidefpsPostAuthDowntime"/>
+ <java-symbol type="integer" name="config_sidefpsEnrollPowerPressWindow"/>
<!-- Face authentication messages -->
<java-symbol type="string" name="face_recalibrate_notification_name" />
@@ -3325,6 +3337,7 @@
<java-symbol type="string" name="config_dozeTapSensorType" />
<java-symbol type="array" name="config_dozeTapSensorPostureMapping" />
<java-symbol type="bool" name="config_dozePulsePickup" />
+ <java-symbol type="bool" name="config_pulseOnNotificationsAvailable" />
<!-- Used for MimeIconUtils. -->
<java-symbol type="drawable" name="ic_doc_apk" />
@@ -4376,9 +4389,14 @@
<java-symbol type="integer" name="config_letterboxBackgroundType" />
<java-symbol type="color" name="config_letterboxBackgroundColor" />
<java-symbol type="dimen" name="config_letterboxHorizontalPositionMultiplier" />
- <java-symbol type="bool" name="config_letterboxIsReachabilityEnabled" />
- <java-symbol type="integer" name="config_letterboxDefaultPositionForReachability" />
+ <java-symbol type="dimen" name="config_letterboxVerticalPositionMultiplier" />
+ <java-symbol type="bool" name="config_letterboxIsHorizontalReachabilityEnabled" />
+ <java-symbol type="bool" name="config_letterboxIsVerticalReachabilityEnabled" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForHorizontalReachability" />
+ <java-symbol type="integer" name="config_letterboxDefaultPositionForVerticalReachability" />
<java-symbol type="bool" name="config_letterboxIsEducationEnabled" />
+ <java-symbol type="dimen" name="config_letterboxDefaultMinAspectRatioForUnresizableApps" />
+ <java-symbol type="bool" name="config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled" />
<java-symbol type="bool" name="config_isCameraCompatControlForStretchedIssuesEnabled" />
<java-symbol type="bool" name="config_hideDisplayCutoutWithDisplayArea" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index bfb2fd57975f..a2d4bafef41c 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -31,7 +31,6 @@ import static org.junit.Assert.assertTrue;
import android.annotation.Nullable;
import android.app.Activity;
-import android.app.ActivityManager;
import android.app.ActivityThread;
import android.app.ActivityThread.ActivityClientRecord;
import android.app.IApplicationThread;
@@ -571,53 +570,6 @@ public class ActivityThreadTest {
}
@Test
- public void testHandleProcessConfigurationChanged_DependOnProcessState() {
- final ActivityThread activityThread = ActivityThread.currentActivityThread();
- final Configuration origConfig = activityThread.getConfiguration();
- final int newDpi = origConfig.densityDpi + 10;
- final Configuration newConfig = new Configuration(origConfig);
- newConfig.seq++;
- newConfig.densityDpi = newDpi;
-
- activityThread.updateProcessState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY,
- false /* fromIPC */);
-
- applyProcessConfiguration(activityThread, newConfig);
- try {
- // In the cached state, the configuration is only set as pending and not applied.
- assertEquals(origConfig.densityDpi, activityThread.getConfiguration().densityDpi);
- assertTrue(activityThread.isCachedProcessState());
- } finally {
- // The foreground state is the default state of instrumentation.
- activityThread.updateProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE,
- false /* fromIPC */);
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
-
- try {
- // The state becomes non-cached, the pending configuration should be applied.
- assertEquals(newConfig.densityDpi, activityThread.getConfiguration().densityDpi);
- assertFalse(activityThread.isCachedProcessState());
- } finally {
- // Restore to the original configuration.
- activityThread.getConfiguration().seq = origConfig.seq - 1;
- applyProcessConfiguration(activityThread, origConfig);
- }
- }
-
- private static void applyProcessConfiguration(ActivityThread thread, Configuration config) {
- final ClientTransaction clientTransaction = newTransaction(thread,
- null /* activityToken */);
- clientTransaction.addCallback(ConfigurationChangeItem.obtain(config));
- final IApplicationThread appThread = thread.getApplicationThread();
- try {
- appThread.scheduleTransaction(clientTransaction);
- } catch (Exception ignored) {
- }
- InstrumentationRegistry.getInstrumentation().waitForIdleSync();
- }
-
- @Test
public void testResumeAfterNewIntent() {
final Activity activity = mActivityTestRule.launchActivity(new Intent());
final ActivityThread activityThread = activity.getActivityThread();
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 7338c3a3ea8a..ddc05e09c395 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -204,6 +204,19 @@ public class AdaptiveIconDrawableTest extends AndroidTestCase {
assertEquals(100, Color.alpha(bitmap.getPixel(50, 50)));
}
+ @Test
+ public void testSetBounds() throws Exception {
+ mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
+ mIconDrawable.setBounds(0, 0, 100, 100);
+
+ assertEquals(new Rect(-25, -25, 125, 125), mBackgroundDrawable.getBounds());
+ assertEquals(new Rect(-25, -25, 125, 125), mForegroundDrawable.getBounds());
+
+ mIconDrawable.setBounds(10, 10, 110, 110);
+ assertEquals(new Rect(-15, -15, 135, 135), mBackgroundDrawable.getBounds());
+ assertEquals(new Rect(-15, -15, 135, 135), mForegroundDrawable.getBounds());
+ }
+
//
// Utils
//
diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java
index c504f0cf2d94..dcb1835204dd 100644
--- a/core/tests/coretests/src/android/view/InsetsControllerTest.java
+++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java
@@ -214,19 +214,23 @@ public class InsetsControllerTest {
}
@Test
- public void testFrameDoesntMatchDisplay() {
- mController.onFrameChanged(new Rect(0, 0, 100, 100));
- mController.getState().setDisplayFrame(new Rect(0, 0, 200, 200));
- InsetsSourceControl control =
- new InsetsSourceControl(
- ITYPE_STATUS_BAR, mLeash, new Point(), Insets.of(0, 10, 0, 0));
- mController.onControlsChanged(new InsetsSourceControl[] { control });
+ public void testFrameDoesntOverlapWithInsets() {
WindowInsetsAnimationControlListener controlListener =
mock(WindowInsetsAnimationControlListener.class);
- mController.controlWindowInsetsAnimation(0, 0 /* durationMs */, new LinearInterpolator(),
- new CancellationSignal(), controlListener);
- mController.addOnControllableInsetsChangedListener(
- (controller, typeMask) -> assertEquals(0, typeMask));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> {
+ // The frame doesn't overlap with status bar.
+ mController.onFrameChanged(new Rect(0, 10, 100, 100));
+
+ InsetsSourceControl control =
+ new InsetsSourceControl(
+ ITYPE_STATUS_BAR, mLeash, new Point(), Insets.of(0, 10, 0, 0));
+ mController.onControlsChanged(new InsetsSourceControl[]{control});
+ mController.controlWindowInsetsAnimation(0, 0 /* durationMs */,
+ new LinearInterpolator(),
+ new CancellationSignal(), controlListener);
+ mController.addOnControllableInsetsChangedListener(
+ (controller, typeMask) -> assertEquals(0, typeMask));
+ });
verify(controlListener).onCancelled(null);
verify(controlListener, never()).onReady(any(), anyInt());
}
diff --git a/core/tests/coretests/src/android/widget/TextViewTest.java b/core/tests/coretests/src/android/widget/TextViewTest.java
index 47ce2d87e69f..cc4fbab1f190 100644
--- a/core/tests/coretests/src/android/widget/TextViewTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewTest.java
@@ -304,6 +304,23 @@ public class TextViewTest {
assertFalse(mTextView.isCursorVisible());
}
+ @Test(expected = NullPointerException.class)
+ @UiThreadTest
+ public void setTextCharArrayNullThrows() {
+ mTextView = new TextView(mActivity);
+ mTextView.setText((char[]) null, 0, 0);
+ }
+
+ @Test
+ @UiThreadTest
+ public void setTextCharArrayValidAfterSetTextString() {
+ mTextView = new TextView(mActivity);
+ mTextView.setText(new char[] { 'h', 'i'}, 0, 2);
+ CharSequence charWrapper = mTextView.getText();
+ mTextView.setText("null out char wrapper");
+ assertEquals("hi", charWrapper.toString());
+ }
+
private String createLongText() {
int size = 600 * 1000;
final StringBuilder builder = new StringBuilder(size);
diff --git a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
index f448cb3091e7..b5194f637395 100644
--- a/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
+++ b/core/tests/coretests/src/android/window/WindowOnBackInvokedDispatcherTest.java
@@ -66,6 +66,7 @@ public class WindowOnBackInvokedDispatcherTest {
MockitoAnnotations.initMocks(this);
mDispatcher = new WindowOnBackInvokedDispatcher(true /* applicationCallbackEnabled */);
mDispatcher.attachToWindow(mWindowSession, mWindow);
+ mDispatcher.onWindowFocusChanged(true);
}
private void waitForIdle() {
@@ -152,4 +153,31 @@ public class WindowOnBackInvokedDispatcherTest {
waitForIdle();
verify(mCallback2).onBackStarted();
}
+
+ @Test
+ public void skipBackInvokeWhenNoFocus() throws RemoteException {
+ ArgumentCaptor<OnBackInvokedCallbackInfo> captor =
+ ArgumentCaptor.forClass(OnBackInvokedCallbackInfo.class);
+
+ mDispatcher.registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCallback1);
+
+ verify(mWindowSession, times(1)).setOnBackInvokedCallbackInfo(
+ Mockito.eq(mWindow),
+ captor.capture());
+
+ verify(mWindowSession).setOnBackInvokedCallbackInfo(Mockito.eq(mWindow), captor.capture());
+
+ // Should invoke back if it's still in focused.
+ captor.getValue().getCallback().onBackInvoked();
+ waitForIdle();
+ verify(mCallback1).onBackInvoked();
+
+ // In case the focus has lost during back gesture.
+ mDispatcher.onWindowFocusChanged(false);
+
+ captor.getValue().getCallback().onBackInvoked();
+ waitForIdle();
+ verifyZeroInteractions(mCallback1);
+ }
}
diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
index b38e1c274b45..e8c7ce0b312b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java
@@ -84,17 +84,23 @@ import android.service.chooser.ChooserTarget;
import android.view.View;
import androidx.annotation.CallSuper;
+import androidx.test.espresso.matcher.BoundedDiagnosingMatcher;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
+import com.android.internal.R;
import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import com.android.internal.app.chooser.DisplayResolveInfo;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.widget.GridLayoutManager;
+import com.android.internal.widget.RecyclerView;
+import org.hamcrest.Description;
import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
@@ -260,6 +266,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
final IChooserWrapper activity = (IChooserWrapper) mActivityRule.launchActivity(
@@ -283,6 +290,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, "chooser test"));
@@ -303,6 +311,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -323,6 +332,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -346,6 +356,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -372,6 +383,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -397,6 +409,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -418,6 +431,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -472,6 +486,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -512,6 +527,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -546,6 +562,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(null);
Intent sendIntent = createSendTextIntent();
@@ -581,6 +598,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -643,6 +661,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(ChooserActivityOverrideData.getInstance().resolverListController.getLastChosen())
.thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
@@ -682,6 +701,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final IChooserWrapper activity = (IChooserWrapper)
@@ -714,6 +734,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ChooserActivity activity =
@@ -741,6 +762,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
MetricsLogger mockLogger = ChooserActivityOverrideData.getInstance().metricsLogger;
@@ -770,6 +792,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final IChooserWrapper activity = (IChooserWrapper)
@@ -840,6 +863,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final IChooserWrapper activity = (IChooserWrapper)
@@ -916,6 +940,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -952,6 +977,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -991,6 +1017,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1085,6 +1112,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1118,6 +1146,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1151,6 +1180,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1183,6 +1213,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
@@ -1212,6 +1243,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1244,6 +1276,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1278,6 +1311,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
when(
@@ -1317,6 +1351,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1359,6 +1394,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1403,6 +1439,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1479,6 +1516,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -1560,6 +1598,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
// Create direct share target
@@ -1632,6 +1671,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
// Create direct share target
@@ -1680,6 +1720,25 @@ public class ChooserActivityTest {
wrapper.getAdapter().getItem(1).getDisplayLabel(), is("testTitle1"));
}
+ @Test
+ public void testUpdateMaxTargetsPerRow_columnCountIsUpdated() throws InterruptedException {
+ updateMaxTargetsPerRowResource(/* targetsPerRow= */ 4);
+ givenAppTargets(/* appCount= */ 16);
+ Intent sendIntent = createSendTextIntent();
+ final ChooserActivity activity =
+ mActivityRule.launchActivity(Intent.createChooser(sendIntent, null));
+
+ updateMaxTargetsPerRowResource(/* targetsPerRow= */ 6);
+ InstrumentationRegistry.getInstrumentation()
+ .runOnMainSync(() -> activity.onConfigurationChanged(
+ InstrumentationRegistry.getInstrumentation()
+ .getContext().getResources().getConfiguration()));
+
+ waitForIdle();
+ onView(withIdFromRuntimeResource("resolver_list"))
+ .check(matches(withGridColumnCount(6)));
+ }
+
// This test is too long and too slow and should not be taken as an example for future tests.
@Test @Ignore
public void testDirectTargetLoggingWithAppTargetNotRankedPortrait()
@@ -1720,6 +1779,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -2035,6 +2095,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -2117,6 +2178,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -2204,6 +2266,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -2273,6 +2336,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(resolvedComponentInfos);
@@ -2421,6 +2485,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
Intent sendIntent = createSendTextIntent();
@@ -2450,6 +2515,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
Intent sendIntent = createSendTextIntent();
@@ -2503,6 +2569,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
Intent chooserIntent = createChooserIntent(createSendTextIntent(),
@@ -2639,6 +2706,7 @@ public class ChooserActivityTest {
when(ChooserActivityOverrideData.getInstance().resolverListController.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
// Create caller target which is duplicate with one of app targets
@@ -3032,6 +3100,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
when(
@@ -3041,6 +3110,7 @@ public class ChooserActivityTest {
.getResolversForIntent(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(workResolvedComponentInfos));
when(
@@ -3050,6 +3120,7 @@ public class ChooserActivityTest {
.getResolversForIntentAsUser(
Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class),
eq(UserHandle.SYSTEM)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
@@ -3063,6 +3134,65 @@ public class ChooserActivityTest {
return withText(getRuntimeResourceId(id, "string"));
}
+ private static GridRecyclerSpanCountMatcher withGridColumnCount(int columnCount) {
+ return new GridRecyclerSpanCountMatcher(Matchers.is(columnCount));
+ }
+
+ private static class GridRecyclerSpanCountMatcher extends
+ BoundedDiagnosingMatcher<View, RecyclerView> {
+
+ private final Matcher<Integer> mIntegerMatcher;
+
+ private GridRecyclerSpanCountMatcher(Matcher<Integer> integerMatcher) {
+ super(RecyclerView.class);
+ this.mIntegerMatcher = integerMatcher;
+ }
+
+ @Override
+ protected void describeMoreTo(Description description) {
+ description.appendText("RecyclerView grid layout span count to match: ");
+ this.mIntegerMatcher.describeTo(description);
+ }
+
+ @Override
+ protected boolean matchesSafely(RecyclerView view, Description mismatchDescription) {
+ int spanCount = ((GridLayoutManager) view.getLayoutManager()).getSpanCount();
+ if (this.mIntegerMatcher.matches(spanCount)) {
+ return true;
+ } else {
+ mismatchDescription.appendText("RecyclerView grid layout span count was ")
+ .appendValue(spanCount);
+ return false;
+ }
+ }
+ }
+
+ private void givenAppTargets(int appCount) {
+ List<ResolvedComponentInfo> resolvedComponentInfos =
+ createResolvedComponentsForTest(appCount);
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resolverListController
+ .getResolversForIntent(
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
+ Mockito.isA(List.class)))
+ .thenReturn(resolvedComponentInfos);
+ }
+
+ private void updateMaxTargetsPerRowResource(int targetsPerRow) {
+ ChooserActivityOverrideData.getInstance().resources = Mockito.spy(
+ InstrumentationRegistry.getInstrumentation().getContext().getResources());
+ when(
+ ChooserActivityOverrideData
+ .getInstance()
+ .resources
+ .getInteger(R.integer.config_chooser_max_targets_per_row))
+ .thenReturn(targetsPerRow);
+ }
+
// ChooserWrapperActivity inherits from the framework ChooserActivity, so if the framework
// resources have been updated since the framework was last built/pushed, the inherited behavior
// (which is the focus of our testing) will still be implemented in terms of the old resource
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
index 43fba529182e..92c05b0fe9fc 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverActivityTest.java
@@ -96,6 +96,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -127,6 +128,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
waitForIdle();
@@ -171,6 +173,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
waitForIdle();
@@ -203,6 +206,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
.thenReturn(resolvedComponentInfos.get(0).getResolveInfoAt(0));
@@ -273,6 +277,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
final ResolverWrapperActivity activity = mActivityRule.launchActivity(sendIntent);
@@ -317,6 +322,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
.thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
@@ -807,6 +813,7 @@ public class ResolverActivityTest {
createResolvedComponentsForTestWithOtherProfile(2, /* userId */ 10);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
Intent sendIntent = createSendImageIntent();
@@ -831,6 +838,7 @@ public class ResolverActivityTest {
createResolvedComponentsForTest(1);
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
Intent sendIntent = createSendImageIntent();
@@ -888,6 +896,7 @@ public class ResolverActivityTest {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(resolvedComponentInfos);
when(sOverrides.resolverListController.getLastChosen())
.thenReturn(resolvedComponentInfos.get(1).getResolveInfoAt(0));
@@ -965,13 +974,16 @@ public class ResolverActivityTest {
List<ResolvedComponentInfo> workResolvedComponentInfos) {
when(sOverrides.resolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
when(sOverrides.workResolverListController.getResolversForIntent(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class))).thenReturn(workResolvedComponentInfos);
when(sOverrides.workResolverListController.getResolversForIntentAsUser(Mockito.anyBoolean(),
Mockito.anyBoolean(),
+ Mockito.anyBoolean(),
Mockito.isA(List.class),
eq(UserHandle.SYSTEM)))
.thenReturn(new ArrayList<>(personalResolvedComponentInfos));
diff --git a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
index e16d44854516..42593f60094b 100644
--- a/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/ResolverListControllerTest.java
@@ -20,11 +20,14 @@ import static junit.framework.Assert.assertEquals;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.mockito.ArgumentMatchers.intThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyLong;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.usage.IUsageStatsManager;
@@ -48,6 +51,7 @@ import com.android.internal.app.ResolverActivity.ResolvedComponentInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -78,6 +82,8 @@ public class ResolverListControllerTest {
Configuration config = new Configuration();
config.locale = Locale.getDefault();
List<ResolveInfo> services = new ArrayList<>();
+ mUsm = new UsageStatsManager(mMockContext, mMockService);
+ when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
when(mMockPackageManager.queryIntentServices(any(), anyInt())).thenReturn(services);
when(mMockResources.getConfiguration()).thenReturn(config);
when(mMockContext.getResources()).thenReturn(mMockResources);
@@ -112,8 +118,6 @@ public class ResolverListControllerTest {
doAnswer(answer).when(mMockService).reportChooserSelection(
anyString(), anyInt(), anyString(), any(), anyString());
when(mMockContext.getOpPackageName()).thenReturn(refererPackage);
- mUsm = new UsageStatsManager(mMockContext, mMockService);
- when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
mController.sort(new ArrayList<ResolvedComponentInfo>());
@@ -129,8 +133,6 @@ public class ResolverListControllerTest {
Intent sendIntent = createSendImageIntent(annotation);
String refererPackage = "test_referer_package";
List<ResolvedComponentInfo> resolvedComponents = createResolvedComponentsForTest(10);
- mUsm = new UsageStatsManager(mMockContext, mMockService);
- when(mMockContext.getSystemService(Context.USAGE_STATS_SERVICE)).thenReturn(mUsm);
mController = new ResolverListController(mMockContext, mMockPackageManager, sendIntent,
refererPackage, UserHandle.USER_CURRENT, /* userHandle */ UserHandle.SYSTEM);
List<ResolvedComponentInfo> topKList = new ArrayList<>(resolvedComponents);
@@ -151,6 +153,102 @@ public class ResolverListControllerTest {
sortList, topKList);
}
+ @Test
+ public void getResolversForIntent_usesResultsFromPackageManager() {
+ mockStats();
+ List<ResolveInfo> infos = new ArrayList<>();
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+ when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+ any(UserHandle.class))).thenReturn(infos);
+ mController = new ResolverListController(mMockContext, mMockPackageManager,
+ createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+ /* userHandle= */ UserHandle.SYSTEM);
+ List<Intent> intents = new ArrayList<>();
+ intents.add(createActionMainIntent());
+
+ List<ResolvedComponentInfo> resolvers = mController
+ .getResolversForIntent(
+ /* shouldGetResolvedFilter= */ true,
+ /* shouldGetActivityMetadata= */ true,
+ /* shouldGetOnlyDefaultActivities= */ true,
+ intents);
+
+ assertThat(resolvers, hasSize(1));
+ assertThat(resolvers.get(0).getResolveInfoAt(0), is(infos.get(0)));
+ }
+
+ @Test
+ public void getResolversForIntent_shouldGetOnlyDefaultActivitiesTrue_addsFlag() {
+ mockStats();
+ List<ResolveInfo> infos = new ArrayList<>();
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+ when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+ any(UserHandle.class))).thenReturn(infos);
+ mController = new ResolverListController(mMockContext, mMockPackageManager,
+ createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+ /* userHandle= */ UserHandle.SYSTEM);
+ List<Intent> intents = new ArrayList<>();
+ intents.add(createActionMainIntent());
+
+ mController
+ .getResolversForIntent(
+ /* shouldGetResolvedFilter= */ true,
+ /* shouldGetActivityMetadata= */ true,
+ /* shouldGetOnlyDefaultActivities= */ true,
+ intents);
+
+ verify(mMockPackageManager).queryIntentActivitiesAsUser(any(),
+ containsFlag(PackageManager.MATCH_DEFAULT_ONLY), any());
+ }
+
+ @Test
+ public void getResolversForIntent_shouldGetOnlyDefaultActivitiesFalse_doesNotAddFlag() {
+ mockStats();
+ List<ResolveInfo> infos = new ArrayList<>();
+ infos.add(ResolverDataProvider.createResolveInfo(0, UserHandle.USER_CURRENT));
+ when(mMockPackageManager.queryIntentActivitiesAsUser(any(), anyInt(),
+ any(UserHandle.class))).thenReturn(infos);
+ mController = new ResolverListController(mMockContext, mMockPackageManager,
+ createSendImageIntent("test"), null, UserHandle.USER_CURRENT,
+ /* userHandle= */ UserHandle.SYSTEM);
+ List<Intent> intents = new ArrayList<>();
+ intents.add(createActionMainIntent());
+
+ mController
+ .getResolversForIntent(
+ /* shouldGetResolvedFilter= */ true,
+ /* shouldGetActivityMetadata= */ true,
+ /* shouldGetOnlyDefaultActivities= */ false,
+ intents);
+
+ verify(mMockPackageManager).queryIntentActivitiesAsUser(any(),
+ doesNotContainFlag(PackageManager.MATCH_DEFAULT_ONLY), any());
+ }
+
+ private int containsFlag(int flag) {
+ return intThat(new FlagMatcher(flag, /* contains= */ true));
+ }
+
+ private int doesNotContainFlag(int flag) {
+ return intThat(new FlagMatcher(flag, /* contains= */ false));
+ }
+
+ public static class FlagMatcher implements ArgumentMatcher<Integer> {
+
+ private final int mFlag;
+ private final boolean mContains;
+
+ public FlagMatcher(int flag, boolean contains) {
+ mFlag = flag;
+ mContains = contains;
+ }
+
+ @Override
+ public boolean matches(Integer integer) {
+ return ((integer & mFlag) != 0) == mContains;
+ }
+ }
+
private UsageStats initStats(String packageName, String action,
String annotation, int count) {
ArrayMap<String, ArrayMap<String, Integer>> chooserCounts = new ArrayMap<>();
@@ -174,6 +272,24 @@ public class ResolverListControllerTest {
return sendIntent;
}
+ private Intent createActionMainIntent() {
+ Intent sendIntent = new Intent();
+ sendIntent.setAction(Intent.ACTION_MAIN);
+ sendIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+ return sendIntent;
+ }
+
+ private void mockStats() {
+ final List<UsageStats> slices = new ArrayList<>();
+ ParceledListSlice<UsageStats> stats = new ParceledListSlice<>(slices);
+ try {
+ when(mMockService.queryUsageStats(anyInt(), anyLong(), anyLong(), anyString(),
+ anyInt())).thenReturn(stats);
+ } catch (RemoteException e) {
+ e.printStackTrace();
+ }
+ }
+
private Integer getCount(
UsageStatsManager usm, String packageName, String action, String annotation) {
if (usm == null) {
diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
index 09fc7ea6fffd..e068730e9bda 100644
--- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java
@@ -36,6 +36,7 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.only;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -83,6 +84,7 @@ public class FrameTrackerTest {
private StatsLogWrapper mStatsLog;
private ArgumentCaptor<OnJankDataListener> mListenerCapture;
private SurfaceControl mSurfaceControl;
+ private ArgumentCaptor<Runnable> mRunnableArgumentCaptor;
@Before
public void setup() {
@@ -99,6 +101,8 @@ public class FrameTrackerTest {
mSurfaceControl = new SurfaceControl.Builder().setName("Surface").build();
mViewRootWrapper = mock(ViewRootWrapper.class);
when(mViewRootWrapper.getSurfaceControl()).thenReturn(mSurfaceControl);
+ doNothing().when(mViewRootWrapper).addSurfaceChangedCallback(any());
+ doNothing().when(mViewRootWrapper).removeSurfaceChangedCallback(any());
mSurfaceControlWrapper = mock(SurfaceControlWrapper.class);
mListenerCapture = ArgumentCaptor.forClass(OnJankDataListener.class);
@@ -109,23 +113,29 @@ public class FrameTrackerTest {
mChoreographer = mock(ChoreographerWrapper.class);
mStatsLog = mock(StatsLogWrapper.class);
+ mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class);
}
private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) {
+ InteractionJankMonitor monitor = mock(InteractionJankMonitor.class);
Handler handler = mRule.getActivity().getMainThreadHandler();
Session session = new Session(cuj, postfix);
Configuration config = mock(Configuration.class);
when(config.isSurfaceOnly()).thenReturn(surfaceOnly);
when(config.getSurfaceControl()).thenReturn(mSurfaceControl);
when(config.shouldDeferMonitor()).thenReturn(true);
+ View view = mRule.getActivity().getWindow().getDecorView();
+ Handler spyHandler = spy(new Handler(handler.getLooper()));
+ when(config.getView()).thenReturn(surfaceOnly ? null : view);
+ when(config.getHandler()).thenReturn(spyHandler);
FrameTracker frameTracker = Mockito.spy(
- new FrameTracker(session, handler, mRenderer, mViewRootWrapper,
+ new FrameTracker(monitor, session, spyHandler, mRenderer, mViewRootWrapper,
mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog,
/* traceThresholdMissedFrames= */ 1,
/* traceThresholdFrameTimeMillis= */ -1,
/* FrameTrackerListener= */ null, config));
doNothing().when(frameTracker).triggerPerfetto();
- doNothing().when(frameTracker).postTraceStartMarker();
+ doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture());
return frameTracker;
}
@@ -140,6 +150,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// send first frame with a long duration - should not be taken into account
@@ -173,6 +184,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
@@ -208,6 +220,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// send first frame - janky
@@ -243,6 +256,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
@@ -278,6 +292,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
@@ -319,6 +334,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// send first frame - not janky
@@ -332,7 +348,7 @@ public class FrameTrackerTest {
tracker.end(FrameTracker.REASON_END_NORMAL);
// Send incomplete callback for 102L
- sendSfFrame(102L, JANK_NONE);
+ sendSfFrame(tracker, 102L, JANK_NONE);
// Send janky but complete callbck fo 103L
sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L);
@@ -356,6 +372,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer).addObserver(any());
// First frame - not janky
@@ -380,6 +397,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// end the trace session
@@ -403,6 +421,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mRenderer, only()).addObserver(any());
// end the trace session at the same vsync id, end vsync id will less than the begin one.
@@ -444,6 +463,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - not janky
@@ -479,6 +499,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - janky
@@ -514,6 +535,7 @@ public class FrameTrackerTest {
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
// First frame - not janky
@@ -548,6 +570,7 @@ public class FrameTrackerTest {
CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true);
when(mChoreographer.getVsyncId()).thenReturn(100L);
tracker.begin();
+ mRunnableArgumentCaptor.getValue().run();
verify(mSurfaceControlWrapper).addJankStatsListener(any(), any());
sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 100L);
sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 101L);
@@ -594,7 +617,7 @@ public class FrameTrackerTest {
if (!tracker.mSurfaceOnly) {
sendHwuiFrame(tracker, durationMillis, vsyncId, firstWindowFrame);
}
- sendSfFrame(vsyncId, jankType);
+ sendSfFrame(tracker, vsyncId, jankType);
}
private void sendHwuiFrame(FrameTracker tracker, long durationMillis, long vsyncId,
@@ -604,12 +627,18 @@ public class FrameTrackerTest {
.getMetric(FrameMetrics.FIRST_DRAW_FRAME);
doReturn(TimeUnit.MILLISECONDS.toNanos(durationMillis))
.when(mWrapper).getMetric(FrameMetrics.TOTAL_DURATION);
+ final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ doNothing().when(tracker).postCallback(captor.capture());
tracker.onFrameMetricsAvailable(0);
+ captor.getValue().run();
}
- private void sendSfFrame(long vsyncId, @JankType int jankType) {
+ private void sendSfFrame(FrameTracker tracker, long vsyncId, @JankType int jankType) {
+ final ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
+ doNothing().when(tracker).postCallback(captor.capture());
mListenerCapture.getValue().onJankDataAvailable(new JankData[] {
new JankData(vsyncId, jankType)
});
+ captor.getValue().run();
}
}
diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
index 5a6fd5317bbc..d96f041c13f8 100644
--- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
+++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java
@@ -93,7 +93,7 @@ public class InteractionJankMonitorTest {
@Test
public void testBeginEnd() {
InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
- FrameTracker tracker = createMockedFrameTracker(null);
+ FrameTracker tracker = createMockedFrameTracker(monitor, null);
doReturn(tracker).when(monitor).createFrameTracker(any(), any());
doNothing().when(tracker).begin();
doReturn(true).when(tracker).end(anyInt());
@@ -134,7 +134,7 @@ public class InteractionJankMonitorTest {
public void testBeginTimeout() {
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
InteractionJankMonitor monitor = createMockedInteractionJankMonitor();
- FrameTracker tracker = createMockedFrameTracker(null);
+ FrameTracker tracker = createMockedFrameTracker(monitor, null);
doReturn(tracker).when(monitor).createFrameTracker(any(), any());
doNothing().when(tracker).begin();
doReturn(true).when(tracker).cancel(anyInt());
@@ -180,7 +180,8 @@ public class InteractionJankMonitorTest {
return monitor;
}
- private FrameTracker createMockedFrameTracker(FrameTracker.FrameTrackerListener listener) {
+ private FrameTracker createMockedFrameTracker(InteractionJankMonitor monitor,
+ FrameTracker.FrameTrackerListener listener) {
Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX));
doReturn(false).when(session).logToStatsd();
@@ -190,6 +191,7 @@ public class InteractionJankMonitorTest {
ViewRootWrapper viewRoot = spy(new ViewRootWrapper(mView.getViewRootImpl()));
doNothing().when(viewRoot).addSurfaceChangedCallback(any());
+ doNothing().when(viewRoot).removeSurfaceChangedCallback(any());
SurfaceControlWrapper surfaceControl = mock(SurfaceControlWrapper.class);
doNothing().when(surfaceControl).addJankStatsListener(any(), any());
@@ -200,15 +202,18 @@ public class InteractionJankMonitorTest {
Configuration configuration = mock(Configuration.class);
when(configuration.isSurfaceOnly()).thenReturn(false);
+ when(configuration.getView()).thenReturn(mView);
+ when(configuration.getHandler()).thenReturn(mView.getHandler());
- FrameTracker tracker = spy(new FrameTracker(session, mWorker.getThreadHandler(),
+ FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(),
threadedRenderer, viewRoot, surfaceControl, choreographer,
new FrameMetricsWrapper(), new StatsLogWrapper(),
/* traceThresholdMissedFrames= */ 1,
/* traceThresholdFrameTimeMillis= */ -1, listener, configuration));
- doNothing().when(tracker).postTraceStartMarker();
+ doNothing().when(tracker).postTraceStartMarker(any());
doNothing().when(tracker).triggerPerfetto();
+ doReturn(configuration.getHandler()).when(tracker).getHandler();
return tracker;
}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index 0f05be06bff6..c53fb23ba948 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -20,6 +20,7 @@ import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_B
import static com.google.common.truth.Truth.assertThat;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.Parcel;
import android.os.UserHandle;
@@ -48,7 +49,11 @@ public class RegisterStatusBarResultTest {
final ArrayMap<String, StatusBarIcon> iconMap = new ArrayMap<>();
iconMap.put(dumyIconKey, new StatusBarIcon("com.android.internal.statusbar.test",
UserHandle.of(100), 123, 1, 2, "dummyIconDescription"));
-
+ final LetterboxDetails letterboxDetails = new LetterboxDetails(
+ /* letterboxInnerBounds= */ new Rect(1, 2, 3, 4),
+ /* letterboxFullBounds= */ new Rect(5, 6, 7, 8),
+ /* appAppearance= */ 321
+ );
final RegisterStatusBarResult original = new RegisterStatusBarResult(iconMap,
0x2 /* disabledFlags1 */,
0x4 /* appearance */,
@@ -62,7 +67,8 @@ public class RegisterStatusBarResultTest {
BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
new InsetsVisibilities() /* requestedVisibilities */,
"test" /* packageName */,
- new int[0] /* transientBarTypes */);
+ new int[0] /* transientBarTypes */,
+ new LetterboxDetails[] {letterboxDetails});
final RegisterStatusBarResult copy = clone(original);
@@ -84,6 +90,7 @@ public class RegisterStatusBarResultTest {
assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
+ assertThat(copy.mLetterboxDetails).isEqualTo(original.mLetterboxDetails);
}
private RegisterStatusBarResult clone(RegisterStatusBarResult original) {
diff --git a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
index 8d3751e6ad6c..47f70ddf2d42 100644
--- a/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
+++ b/core/tests/mockingcoretests/src/android/app/activity/ActivityThreadClientTest.java
@@ -207,8 +207,8 @@ public class ActivityThreadClientTest {
final Configuration currentConfig = new Configuration();
assertFalse("Must not report change if no public diff",
- shouldReportChange(0 /* publicDiff */, currentConfig, newConfig,
- null /* sizeBuckets */, 0 /* handledConfigChanges */));
+ shouldReportChange(currentConfig, newConfig, null /* sizeBuckets */,
+ 0 /* handledConfigChanges */));
final int[] verticalThresholds = {100, 400};
final SizeConfigurationBuckets buckets = new SizeConfigurationBuckets(
@@ -221,25 +221,25 @@ public class ActivityThreadClientTest {
newConfig.screenHeightDp = 300;
assertFalse("Must not report changes if the diff is small and not handled",
- shouldReportChange(CONFIG_SCREEN_SIZE /* publicDiff */, currentConfig,
- newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
+ shouldReportChange(currentConfig, newConfig, buckets,
+ CONFIG_FONT_SCALE /* handledConfigChanges */));
assertTrue("Must report changes if the small diff is handled",
- shouldReportChange(CONFIG_SCREEN_SIZE /* publicDiff */, currentConfig, newConfig,
- buckets, CONFIG_SCREEN_SIZE /* handledConfigChanges */));
+ shouldReportChange(currentConfig, newConfig, buckets,
+ CONFIG_SCREEN_SIZE /* handledConfigChanges */));
currentConfig.fontScale = 0.8f;
newConfig.fontScale = 1.2f;
assertTrue("Must report handled changes regardless of small unhandled change",
- shouldReportChange(CONFIG_SCREEN_SIZE | CONFIG_FONT_SCALE /* publicDiff */,
- currentConfig, newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
+ shouldReportChange(currentConfig, newConfig, buckets,
+ CONFIG_FONT_SCALE /* handledConfigChanges */));
newConfig.screenHeightDp = 500;
assertFalse("Must not report changes if there's unhandled big changes",
- shouldReportChange(CONFIG_SCREEN_SIZE | CONFIG_FONT_SCALE /* publicDiff */,
- currentConfig, newConfig, buckets, CONFIG_FONT_SCALE /* handledConfigChanges */));
+ shouldReportChange(currentConfig, newConfig, buckets,
+ CONFIG_FONT_SCALE /* handledConfigChanges */));
}
private void recreateAndVerifyNoRelaunch(ActivityThread activityThread, TestActivity activity) {
diff --git a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
index fa4aa803c75e..ed857e8fc960 100644
--- a/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
+++ b/core/tests/mockingcoretests/src/android/window/SizeConfigurationBucketsTest.java
@@ -88,26 +88,15 @@ public class SizeConfigurationBucketsTest {
}
/**
- * Tests that null size configuration buckets unflips the correct configuration flags.
+ * Tests that {@code null} size configuration buckets do not unflip the configuration flags.
*/
@Test
public void testNullSizeConfigurationBuckets() {
- // Check that all 3 size configurations are filtered out of the diff if the buckets are null
- // and non-size attributes of screen layout are unchanged. Add a non-size related config
- // change (i.e. CONFIG_LOCALE) to test that the diff is not set to zero.
final int diff = CONFIG_SCREEN_SIZE | CONFIG_SMALLEST_SCREEN_SIZE | CONFIG_SCREEN_LAYOUT
| CONFIG_LOCALE;
final int filteredDiffNonSizeLayoutUnchanged = SizeConfigurationBuckets.filterDiff(diff,
Configuration.EMPTY, Configuration.EMPTY, null);
- assertEquals(CONFIG_LOCALE, filteredDiffNonSizeLayoutUnchanged);
-
- // Check that only screen size and smallest screen size are filtered out of the diff if the
- // buckets are null and non-size attributes of screen layout are changed.
- final Configuration newConfig = new Configuration();
- newConfig.screenLayout |= SCREENLAYOUT_ROUND_YES;
- final int filteredDiffNonSizeLayoutChanged = SizeConfigurationBuckets.filterDiff(diff,
- Configuration.EMPTY, newConfig, null);
- assertEquals(CONFIG_SCREEN_LAYOUT | CONFIG_LOCALE, filteredDiffNonSizeLayoutChanged);
+ assertEquals(diff, filteredDiffNonSizeLayoutUnchanged);
}
/**
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index f7c2b732a77b..c6d9ebab297d 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -163,6 +163,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1941440781": {
+ "message": "Creating Pending Move-to-back: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Task.java"
+ },
"-1939861963": {
"message": "Create root task displayId=%d winMode=%d",
"level": "VERBOSE",
@@ -571,12 +577,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "-1521427940": {
- "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"-1517908912": {
"message": "requestScrollCapture: caught exception dispatching to window.token=%s",
"level": "WARN",
@@ -757,6 +757,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "-1383884640": {
+ "message": " allReady query: used=%b override=%b defer=%d states=[%s]",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/Transition.java"
+ },
"-1376035390": {
"message": "No task found",
"level": "DEBUG",
@@ -1501,6 +1507,12 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-636553602": {
+ "message": "commitVisibility: %s: visible=%b visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"-635082269": {
"message": "******** booted=%b msg=%b haveBoot=%b haveApp=%b haveWall=%b wallEnabled=%b haveKeyguard=%b",
"level": "INFO",
@@ -1693,6 +1705,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "-417730399": {
+ "message": "Preparing to sync a window that was already in the sync, so try dropping buffer. win=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"-415865166": {
"message": "findFocusedWindow: Found new focus @ %s",
"level": "VERBOSE",
@@ -2071,6 +2089,12 @@
"group": "WM_DEBUG_IME",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
+ "-57572004": {
+ "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b canCustomizeAppTransition=%b Callers=%s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+ "at": "com\/android\/server\/wm\/AppTransition.java"
+ },
"-55185509": {
"message": "setFocusedTask: taskId=%d touchedActivity=%s",
"level": "DEBUG",
@@ -2113,6 +2137,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimation.java"
},
+ "-4263657": {
+ "message": "Got a buffer for request id=%d but latest request is id=%d. Since the buffer is out-of-date, drop it. win=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_SYNC_ENGINE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"3593205": {
"message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
"level": "VERBOSE",
@@ -2143,6 +2173,12 @@
"group": "WM_DEBUG_RECENTS_ANIMATIONS",
"at": "com\/android\/server\/wm\/RecentsAnimationController.java"
},
+ "25888308": {
+ "message": "Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_RESIZE",
+ "at": "com\/android\/server\/wm\/WindowState.java"
+ },
"35398067": {
"message": "goodToGo(): onAnimationStart, transit=%s, apps=%d, wallpapers=%d, nonApps=%d",
"level": "DEBUG",
@@ -2527,12 +2563,6 @@
"group": "WM_DEBUG_ADD_REMOVE",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "352982444": {
- "message": " allReady query: used=%b override=%b states=[%s]",
- "level": "VERBOSE",
- "group": "WM_DEBUG_WINDOW_TRANSITIONS",
- "at": "com\/android\/server\/wm\/Transition.java"
- },
"355720268": {
"message": "stopFreezingDisplayLocked: Unfreezing now",
"level": "DEBUG",
@@ -2575,6 +2605,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/TaskFragment.java"
},
+ "385237117": {
+ "message": "moveFocusableActivityToTop: already on top and focused, activity=%s",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
+ },
"385595355": {
"message": "Starting animation on %s: type=%d, anim=%s",
"level": "VERBOSE",
@@ -2797,12 +2833,6 @@
"group": "WM_DEBUG_FOCUS_LIGHT",
"at": "com\/android\/server\/wm\/DisplayContent.java"
},
- "625447638": {
- "message": "Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b reportOrientationChanged=%b",
- "level": "VERBOSE",
- "group": "WM_DEBUG_RESIZE",
- "at": "com\/android\/server\/wm\/WindowState.java"
- },
"628276090": {
"message": "Delaying app transition for screen rotation animation to finish",
"level": "VERBOSE",
@@ -3049,6 +3079,12 @@
"group": "WM_DEBUG_STATES",
"at": "com\/android\/server\/wm\/RootWindowContainer.java"
},
+ "898260097": {
+ "message": "Creating Pending Pip-Enter: %s",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java"
+ },
"898863925": {
"message": "Attempted to add QS dialog window with unknown token %s. Aborting.",
"level": "WARN",
@@ -3103,6 +3139,12 @@
"group": "WM_DEBUG_LOCKTASK",
"at": "com\/android\/server\/wm\/LockTaskController.java"
},
+ "956467125": {
+ "message": "Reparenting Activity to embedded TaskFragment, but the Activity is not collected",
+ "level": "WARN",
+ "group": "WM_DEBUG_WINDOW_TRANSITIONS",
+ "at": "com\/android\/server\/wm\/WindowOrganizerController.java"
+ },
"958338552": {
"message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found",
"level": "VERBOSE",
@@ -3379,11 +3421,11 @@
"group": "WM_DEBUG_BOOT",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
- "1246035185": {
- "message": "stopFreezingDisplayLocked: Returning waitingForConfig=%b, waitingForRemoteRotation=%b, mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, mClientFreezingScreen=%b, mOpeningApps.size()=%d",
+ "1239439010": {
+ "message": "moveFocusableActivityToTop: set focused, activity=%s",
"level": "DEBUG",
- "group": "WM_DEBUG_ORIENTATION",
- "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ "group": "WM_DEBUG_FOCUS",
+ "at": "com\/android\/server\/wm\/ActivityRecord.java"
},
"1252594551": {
"message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d",
@@ -3481,6 +3523,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/ScreenRotationAnimation.java"
},
+ "1360176455": {
+ "message": "stopFreezingDisplayLocked: Returning waitingForConfig=%b, waitingForRemoteDisplayChange=%b, mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, mClientFreezingScreen=%b, mOpeningApps.size()=%d",
+ "level": "DEBUG",
+ "group": "WM_DEBUG_ORIENTATION",
+ "at": "com\/android\/server\/wm\/WindowManagerService.java"
+ },
"1364126018": {
"message": "Resumed activity; dropping state of: %s",
"level": "INFO",
@@ -3505,6 +3553,12 @@
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/TaskDisplayArea.java"
},
+ "1393721079": {
+ "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]",
+ "level": "VERBOSE",
+ "group": "WM_DEBUG_CONFIGURATION",
+ "at": "com\/android\/server\/wm\/RemoteDisplayChangeController.java"
+ },
"1396893178": {
"message": "createRootTask unknown displayId=%d",
"level": "ERROR",
@@ -3955,12 +4009,6 @@
"group": "WM_DEBUG_STARTING_WINDOW",
"at": "com\/android\/server\/wm\/ActivityRecord.java"
},
- "1856211951": {
- "message": "moveFocusableActivityToTop: already on top, activity=%s",
- "level": "DEBUG",
- "group": "WM_DEBUG_FOCUS",
- "at": "com\/android\/server\/wm\/ActivityRecord.java"
- },
"1856783490": {
"message": "resumeTopActivity: Restarting %s",
"level": "DEBUG",
@@ -4171,6 +4219,12 @@
"group": "WM_DEBUG_ANIM",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
+ "2079410261": {
+ "message": "applyAnimation: override requested, but it is prohibited by policy.",
+ "level": "ERROR",
+ "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
+ "at": "com\/android\/server\/wm\/AppTransition.java"
+ },
"2083556954": {
"message": "Set mOrientationChanging of %s",
"level": "VERBOSE",
@@ -4224,12 +4278,6 @@
"level": "VERBOSE",
"group": "WM_DEBUG_ORIENTATION",
"at": "com\/android\/server\/wm\/DisplayRotation.java"
- },
- "2137411379": {
- "message": "applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b Callers=%s",
- "level": "VERBOSE",
- "group": "WM_DEBUG_APP_TRANSITIONS_ANIM",
- "at": "com\/android\/server\/wm\/AppTransition.java"
}
},
"groups": {
diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml
index 84e949a18c52..f8c015f28a50 100644
--- a/data/fonts/fonts.xml
+++ b/data/fonts/fonts.xml
@@ -932,7 +932,7 @@
<font weight="700" style="normal" fallbackFor="serif">NotoSerifLao-Bold.ttf</font>
</family>
<family lang="und-Laoo" variant="compact">
- <font weight="400" style="normal">NotoSansLaoUI-Regular.ttf
+ <font weight="400" style="normal" postScriptName="NotoSansLaoUI">NotoSansLaoUI-Regular.ttf
</font>
<font weight="700" style="normal">NotoSansLaoUI-Bold.ttf</font>
</family>
diff --git a/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
index 1629b6ace35d..239621eeed1e 100644
--- a/graphics/java/android/graphics/ImageDecoder.java
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -40,6 +40,7 @@ import android.graphics.drawable.Drawable;
import android.graphics.drawable.NinePatchDrawable;
import android.net.Uri;
import android.os.Build;
+import android.os.Trace;
import android.system.ErrnoException;
import android.system.Os;
import android.util.DisplayMetrics;
@@ -223,13 +224,21 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
return nCreate(mData, mOffset, mLength, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "ByteArraySource{len=" + mLength + "}";
+ }
}
private static class ByteBufferSource extends Source {
ByteBufferSource(@NonNull ByteBuffer buffer) {
mBuffer = buffer;
+ mLength = mBuffer.limit() - mBuffer.position();
}
+
private final ByteBuffer mBuffer;
+ private final int mLength;
@Override
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
@@ -241,6 +250,11 @@ public final class ImageDecoder implements AutoCloseable {
ByteBuffer buffer = mBuffer.slice();
return nCreate(buffer, buffer.position(), buffer.limit(), preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "ByteBufferSource{len=" + mLength + "}";
+ }
}
private static class ContentResolverSource extends Source {
@@ -285,6 +299,16 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ String uri = mUri.toString();
+ if (uri.length() > 90) {
+ // We want to keep the Uri usable - usually the authority and the end is important.
+ uri = uri.substring(0, 80) + ".." + uri.substring(uri.length() - 10);
+ }
+ return "ContentResolverSource{uri=" + uri + "}";
+ }
}
@NonNull
@@ -399,6 +423,11 @@ public final class ImageDecoder implements AutoCloseable {
return createFromStream(is, false, preferAnimation, this);
}
}
+
+ @Override
+ public String toString() {
+ return "InputStream{s=" + mInputStream + "}";
+ }
}
/**
@@ -444,6 +473,11 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAsset(ais, preferAnimation, this);
}
}
+
+ @Override
+ public String toString() {
+ return "AssetInputStream{s=" + mAssetInputStream + "}";
+ }
}
private static class ResourceSource extends Source {
@@ -485,6 +519,17 @@ public final class ImageDecoder implements AutoCloseable {
return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ // Try to return a human-readable name for debugging purposes.
+ try {
+ return "Resource{name=" + mResources.getResourceName(mResId) + "}";
+ } catch (Resources.NotFoundException e) {
+ // It's ok if we don't find it, fall back to ID.
+ }
+ return "Resource{id=" + mResId + "}";
+ }
}
/**
@@ -521,6 +566,11 @@ public final class ImageDecoder implements AutoCloseable {
InputStream is = mAssets.open(mFileName);
return createFromAsset((AssetInputStream) is, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "AssetSource{file=" + mFileName + "}";
+ }
}
private static class FileSource extends Source {
@@ -534,6 +584,11 @@ public final class ImageDecoder implements AutoCloseable {
public ImageDecoder createImageDecoder(boolean preferAnimation) throws IOException {
return createFromFile(mFile, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "FileSource{file=" + mFile + "}";
+ }
}
private static class CallableSource extends Source {
@@ -557,6 +612,11 @@ public final class ImageDecoder implements AutoCloseable {
}
return createFromAssetFileDescriptor(assetFd, preferAnimation, this);
}
+
+ @Override
+ public String toString() {
+ return "CallableSource{obj=" + mCallable.toString() + "}";
+ }
}
/**
@@ -1763,61 +1823,65 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Drawable decodeDrawableImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeDrawable");
try (ImageDecoder decoder = src.createImageDecoder(true /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
- if (decoder.mUnpremultipliedRequired) {
- // Though this could be supported (ignored) for opaque images,
- // it seems better to always report this error.
- throw new IllegalStateException("Cannot decode a Drawable " +
- "with unpremultiplied pixels!");
- }
+ try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) {
+ if (decoder.mUnpremultipliedRequired) {
+ // Though this could be supported (ignored) for opaque images,
+ // it seems better to always report this error.
+ throw new IllegalStateException(
+ "Cannot decode a Drawable with unpremultiplied pixels!");
+ }
- if (decoder.mMutable) {
- throw new IllegalStateException("Cannot decode a mutable " +
- "Drawable!");
- }
+ if (decoder.mMutable) {
+ throw new IllegalStateException("Cannot decode a mutable Drawable!");
+ }
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap and after decode set the density on the resulting bitmap
- final int srcDensity = decoder.computeDensity(src);
- if (decoder.mAnimated) {
- // AnimatedImageDrawable calls postProcessAndRelease only if
- // mPostProcessor exists.
- ImageDecoder postProcessPtr = decoder.mPostProcessor == null ?
- null : decoder;
- decoder.checkState(true);
- Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
- postProcessPtr, decoder.mDesiredWidth,
- decoder.mDesiredHeight, decoder.getColorSpacePtr(),
- decoder.checkForExtended(), srcDensity,
- src.computeDstDensity(), decoder.mCropRect,
- decoder.mInputStream, decoder.mAssetFd);
- // d has taken ownership of these objects.
- decoder.mInputStream = null;
- decoder.mAssetFd = null;
- return d;
- }
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap and after decode set the density on the resulting bitmap
+ final int srcDensity = decoder.computeDensity(src);
+ if (decoder.mAnimated) {
+ // AnimatedImageDrawable calls postProcessAndRelease only if
+ // mPostProcessor exists.
+ ImageDecoder postProcessPtr = decoder.mPostProcessor == null ? null : decoder;
+ decoder.checkState(true);
+ Drawable d = new AnimatedImageDrawable(decoder.mNativePtr,
+ postProcessPtr, decoder.mDesiredWidth,
+ decoder.mDesiredHeight, decoder.getColorSpacePtr(),
+ decoder.checkForExtended(), srcDensity,
+ src.computeDstDensity(), decoder.mCropRect,
+ decoder.mInputStream, decoder.mAssetFd);
+ // d has taken ownership of these objects.
+ decoder.mInputStream = null;
+ decoder.mAssetFd = null;
+ return d;
+ }
- Bitmap bm = decoder.decodeBitmapInternal();
- bm.setDensity(srcDensity);
+ Bitmap bm = decoder.decodeBitmapInternal();
+ bm.setDensity(srcDensity);
- Resources res = src.getResources();
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- Rect opticalInsets = new Rect();
- bm.getOpticalInsets(opticalInsets);
- Rect padding = decoder.mOutPaddingRect;
- if (padding == null) {
- padding = new Rect();
+ Resources res = src.getResources();
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ Rect opticalInsets = new Rect();
+ bm.getOpticalInsets(opticalInsets);
+ Rect padding = decoder.mOutPaddingRect;
+ if (padding == null) {
+ padding = new Rect();
+ }
+ nGetPadding(decoder.mNativePtr, padding);
+ return new NinePatchDrawable(res, bm, np, padding,
+ opticalInsets, null);
}
- nGetPadding(decoder.mNativePtr, padding);
- return new NinePatchDrawable(res, bm, np, padding,
- opticalInsets, null);
- }
- return new BitmapDrawable(res, bm);
+ return new BitmapDrawable(res, bm);
+ }
+ } finally {
+ // Close the ImageDecoder#decode trace.
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
}
}
@@ -1867,26 +1931,51 @@ public final class ImageDecoder implements AutoCloseable {
@NonNull
private static Bitmap decodeBitmapImpl(@NonNull Source src,
@Nullable OnHeaderDecodedListener listener) throws IOException {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ImageDecoder#decodeBitmap");
try (ImageDecoder decoder = src.createImageDecoder(false /*preferAnimation*/)) {
decoder.mSource = src;
decoder.callHeaderDecoded(listener, src);
+ try (ImageDecoderSourceTrace unused = new ImageDecoderSourceTrace(decoder)) {
+ // this call potentially manipulates the decoder so it must be performed prior to
+ // decoding the bitmap
+ final int srcDensity = decoder.computeDensity(src);
+ Bitmap bm = decoder.decodeBitmapInternal();
+ bm.setDensity(srcDensity);
- // this call potentially manipulates the decoder so it must be performed prior to
- // decoding the bitmap
- final int srcDensity = decoder.computeDensity(src);
- Bitmap bm = decoder.decodeBitmapInternal();
- bm.setDensity(srcDensity);
-
- Rect padding = decoder.mOutPaddingRect;
- if (padding != null) {
- byte[] np = bm.getNinePatchChunk();
- if (np != null && NinePatch.isNinePatchChunk(np)) {
- nGetPadding(decoder.mNativePtr, padding);
+ Rect padding = decoder.mOutPaddingRect;
+ if (padding != null) {
+ byte[] np = bm.getNinePatchChunk();
+ if (np != null && NinePatch.isNinePatchChunk(np)) {
+ nGetPadding(decoder.mNativePtr, padding);
+ }
}
+ return bm;
}
+ } finally {
+ // Close the ImageDecoder#decode trace.
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
- return bm;
+ /**
+ * This describes the decoder in traces to ease debugging. It has to be called after
+ * header has been decoded and width/height have been populated. It should be used
+ * inside a try-with-resources call to automatically complete the trace.
+ */
+ private static AutoCloseable traceDecoderSource(ImageDecoder decoder) {
+ final boolean resourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES);
+ if (resourceTracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder));
}
+
+ return new AutoCloseable() {
+ @Override
+ public void close() throws Exception {
+ if (resourceTracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+ };
}
// This method may modify the decoder so it must be called prior to performing the decode
@@ -1994,6 +2083,66 @@ public final class ImageDecoder implements AutoCloseable {
}
}
+ /**
+ * Returns a short string describing what passed ImageDecoder is loading -
+ * it reports image dimensions, desired dimensions (if any) and source resource.
+ *
+ * The string appears in perf traces to simplify search for slow or memory intensive
+ * image loads.
+ *
+ * Example: ID#w=300;h=250;dw=150;dh=150;src=Resource{name=@resource}
+ *
+ * @hide
+ */
+ private static String describeDecoderForTrace(@NonNull ImageDecoder decoder) {
+ StringBuilder builder = new StringBuilder();
+ // Source dimensions
+ builder.append("ID#w=");
+ builder.append(decoder.mWidth);
+ builder.append(";h=");
+ builder.append(decoder.mHeight);
+ // Desired dimensions (if present)
+ if (decoder.mDesiredWidth != decoder.mWidth
+ || decoder.mDesiredHeight != decoder.mHeight) {
+ builder.append(";dw=");
+ builder.append(decoder.mDesiredWidth);
+ builder.append(";dh=");
+ builder.append(decoder.mDesiredHeight);
+ }
+ // Source description
+ builder.append(";src=");
+ builder.append(decoder.mSource);
+ return builder.toString();
+ }
+
+ /**
+ * Records a trace with information about the source being decoded - dimensions,
+ * desired dimensions and source information.
+ *
+ * It significantly eases debugging of slow resource loads on main thread and
+ * possible large memory consumers.
+ *
+ * @hide
+ */
+ private static final class ImageDecoderSourceTrace implements AutoCloseable {
+
+ private final boolean mResourceTracingEnabled;
+
+ ImageDecoderSourceTrace(ImageDecoder decoder) {
+ mResourceTracingEnabled = Trace.isTagEnabled(Trace.TRACE_TAG_RESOURCES);
+ if (mResourceTracingEnabled) {
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, describeDecoderForTrace(decoder));
+ }
+ }
+
+ @Override
+ public void close() {
+ if (mResourceTracingEnabled) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ }
+ }
+ }
+
private static native ImageDecoder nCreate(long asset,
boolean preferAnimation, Source src) throws IOException;
private static native ImageDecoder nCreate(ByteBuffer buffer, int position, int limit,
diff --git a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
index 2f56b181f455..6939a72019e8 100644
--- a/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
+++ b/graphics/java/android/graphics/drawable/AdaptiveIconDrawable.java
@@ -26,8 +26,6 @@ import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
import android.content.res.TypedArray;
-import android.graphics.Bitmap;
-import android.graphics.BitmapShader;
import android.graphics.BlendMode;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -39,8 +37,6 @@ import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
-import android.graphics.Shader;
-import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.PathParser;
@@ -106,7 +102,8 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
private static final float DEFAULT_VIEW_PORT_SCALE = 1f / (1 + 2 * EXTRA_INSET_PERCENTAGE);
/**
- * Clip path defined in R.string.config_icon_mask.
+ * Unused path.
+ * TODO: Remove once the layoutLib is updated
*/
private static Path sMask;
@@ -114,9 +111,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
* Scaled mask based on the view bounds.
*/
private final Path mMask;
- private final Path mMaskScaleOnly;
+ private final Path mMaskTransformed;
private final Matrix mMaskMatrix;
private final Region mTransparentRegion;
+ private final Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
* Indices used to access {@link #mLayerState.mChildDrawable} array for foreground and
@@ -131,19 +129,10 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
*/
LayerState mLayerState;
- private Shader mLayersShader;
- private Bitmap mLayersBitmap;
-
private final Rect mTmpOutRect = new Rect();
private Rect mHotspotBounds;
private boolean mMutated;
- private boolean mSuspendChildInvalidation;
- private boolean mChildRequestedInvalidation;
- private final Canvas mCanvas;
- private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG |
- Paint.FILTER_BITMAP_FLAG);
-
/**
* Constructor used for xml inflation.
*/
@@ -157,19 +146,16 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
*/
AdaptiveIconDrawable(@Nullable LayerState state, @Nullable Resources res) {
mLayerState = createConstantState(state, res);
- // config_icon_mask from context bound resource may have been chaged using
+ // config_icon_mask from context bound resource may have been changed using
// OverlayManager. Read that one first.
Resources r = ActivityThread.currentActivityThread() == null
? Resources.getSystem()
: ActivityThread.currentActivityThread().getApplication().getResources();
- // TODO: either make sMask update only when config_icon_mask changes OR
- // get rid of it all-together in layoutlib
- sMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
- mMask = new Path(sMask);
- mMaskScaleOnly = new Path(mMask);
+ mMask = PathParser.createPathFromPathData(r.getString(R.string.config_icon_mask));
+ mMaskTransformed = new Path();
mMaskMatrix = new Matrix();
- mCanvas = new Canvas();
mTransparentRegion = new Region();
+ mPaint.setColor(Color.BLACK);
}
private ChildDrawable createChildDrawable(Drawable drawable) {
@@ -280,7 +266,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
* @return the mask path object used to clip the drawable
*/
public Path getIconMask() {
- return mMask;
+ return mMaskTransformed;
}
/**
@@ -322,92 +308,47 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
if (bounds.isEmpty()) {
return;
}
- updateLayerBounds(bounds);
- }
-
- private void updateLayerBounds(Rect bounds) {
- if (bounds.isEmpty()) {
- return;
- }
- try {
- suspendChildInvalidation();
- updateLayerBoundsInternal(bounds);
- updateMaskBoundsInternal(bounds);
- } finally {
- resumeChildInvalidation();
- }
- }
-
- /**
- * Set the child layer bounds bigger than the view port size by {@link #DEFAULT_VIEW_PORT_SCALE}
- */
- private void updateLayerBoundsInternal(Rect bounds) {
- int cX = bounds.width() / 2;
- int cY = bounds.height() / 2;
+ // Set the child layer bounds bigger than the view port size
+ // by {@link #DEFAULT_VIEW_PORT_SCALE}
+ float cX = bounds.exactCenterX();
+ float cY = bounds.exactCenterY();
+ float insetWidth = bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2);
+ float insetHeight = bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2);
+ final Rect outRect = mTmpOutRect;
+ outRect.set(
+ (int) (cX - insetWidth),
+ (int) (cY - insetHeight),
+ (int) (cX + insetWidth),
+ (int) (cY + insetHeight));
for (int i = 0, count = mLayerState.N_CHILDREN; i < count; i++) {
final ChildDrawable r = mLayerState.mChildren[i];
- final Drawable d = r.mDrawable;
- if (d == null) {
- continue;
+ if (r.mDrawable != null) {
+ r.mDrawable.setBounds(outRect);
}
-
- int insetWidth = (int) (bounds.width() / (DEFAULT_VIEW_PORT_SCALE * 2));
- int insetHeight = (int) (bounds.height() / (DEFAULT_VIEW_PORT_SCALE * 2));
- final Rect outRect = mTmpOutRect;
- outRect.set(cX - insetWidth, cY - insetHeight, cX + insetWidth, cY + insetHeight);
-
- d.setBounds(outRect);
}
- }
-
- private void updateMaskBoundsInternal(Rect b) {
- // reset everything that depends on the view bounds
- mMaskMatrix.setScale(b.width() / MASK_SIZE, b.height() / MASK_SIZE);
- sMask.transform(mMaskMatrix, mMaskScaleOnly);
- mMaskMatrix.postTranslate(b.left, b.top);
- sMask.transform(mMaskMatrix, mMask);
+ // Update the clipping mask
+ mMaskMatrix.setScale(bounds.width() / MASK_SIZE, bounds.height() / MASK_SIZE);
+ mMaskMatrix.postTranslate(bounds.left, bounds.top);
+ mMask.transform(mMaskMatrix, mMaskTransformed);
- if (mLayersBitmap == null || mLayersBitmap.getWidth() != b.width()
- || mLayersBitmap.getHeight() != b.height()) {
- mLayersBitmap = Bitmap.createBitmap(b.width(), b.height(), Bitmap.Config.ARGB_8888);
- }
-
- mPaint.setShader(null);
+ // Clear the transparent region, it is calculated lazily
mTransparentRegion.setEmpty();
- mLayersShader = null;
}
@Override
public void draw(Canvas canvas) {
- if (mLayersBitmap == null) {
- return;
+ int saveCount = canvas.save();
+ canvas.clipPath(mMaskTransformed);
+ canvas.drawPaint(mPaint);
+ if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
+ mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(canvas);
}
- if (mLayersShader == null) {
- mCanvas.setBitmap(mLayersBitmap);
- mCanvas.drawColor(Color.BLACK);
- if (mLayerState.mChildren[BACKGROUND_ID].mDrawable != null) {
- mLayerState.mChildren[BACKGROUND_ID].mDrawable.draw(mCanvas);
- }
- if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
- mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(mCanvas);
- }
- mLayersShader = new BitmapShader(mLayersBitmap, TileMode.CLAMP, TileMode.CLAMP);
- mPaint.setShader(mLayersShader);
- }
- if (mMaskScaleOnly != null) {
- Rect bounds = getBounds();
- canvas.translate(bounds.left, bounds.top);
- canvas.drawPath(mMaskScaleOnly, mPaint);
- canvas.translate(-bounds.left, -bounds.top);
+ if (mLayerState.mChildren[FOREGROUND_ID].mDrawable != null) {
+ mLayerState.mChildren[FOREGROUND_ID].mDrawable.draw(canvas);
}
- }
-
- @Override
- public void invalidateSelf() {
- mLayersShader = null;
- super.invalidateSelf();
+ canvas.restoreToCount(saveCount);
}
@Override
@@ -600,37 +541,9 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
return false;
}
- /**
- * Temporarily suspends child invalidation.
- *
- * @see #resumeChildInvalidation()
- */
- private void suspendChildInvalidation() {
- mSuspendChildInvalidation = true;
- }
-
- /**
- * Resumes child invalidation after suspension, immediately performing an
- * invalidation if one was requested by a child during suspension.
- *
- * @see #suspendChildInvalidation()
- */
- private void resumeChildInvalidation() {
- mSuspendChildInvalidation = false;
-
- if (mChildRequestedInvalidation) {
- mChildRequestedInvalidation = false;
- invalidateSelf();
- }
- }
-
@Override
public void invalidateDrawable(@NonNull Drawable who) {
- if (mSuspendChildInvalidation) {
- mChildRequestedInvalidation = true;
- } else {
- invalidateSelf();
- }
+ invalidateSelf();
}
@Override
@@ -714,6 +627,13 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
@Override
public void setAlpha(int alpha) {
mPaint.setAlpha(alpha);
+ final ChildDrawable[] array = mLayerState.mChildren;
+ for (int i = 0; i < mLayerState.N_CHILDREN; i++) {
+ final Drawable dr = array[i].mDrawable;
+ if (dr != null) {
+ dr.setAlpha(alpha);
+ }
+ }
}
@Override
@@ -816,10 +736,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
}
}
- if (changed) {
- updateLayerBounds(getBounds());
- }
-
return changed;
}
@@ -835,10 +751,6 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
}
}
- if (changed) {
- updateLayerBounds(getBounds());
- }
-
return changed;
}
@@ -979,6 +891,7 @@ public class AdaptiveIconDrawable extends Drawable implements Drawable.Callback
int mDensity;
// The density to use when inflating/looking up the children drawables. A value of 0 means
+
// use the system's density.
int mSrcDensityOverride = 0;
diff --git a/graphics/java/android/graphics/drawable/RippleDrawable.java b/graphics/java/android/graphics/drawable/RippleDrawable.java
index 74cad1aaa057..417a27d0f506 100644
--- a/graphics/java/android/graphics/drawable/RippleDrawable.java
+++ b/graphics/java/android/graphics/drawable/RippleDrawable.java
@@ -321,6 +321,7 @@ public class RippleDrawable extends LayerDrawable {
boolean pressed = false;
boolean focused = false;
boolean hovered = false;
+ boolean windowFocused = false;
for (int state : stateSet) {
if (state == R.attr.state_enabled) {
@@ -331,10 +332,12 @@ public class RippleDrawable extends LayerDrawable {
pressed = true;
} else if (state == R.attr.state_hovered) {
hovered = true;
+ } else if (state == R.attr.state_window_focused) {
+ windowFocused = true;
}
}
setRippleActive(enabled && pressed);
- setBackgroundActive(hovered, focused, pressed);
+ setBackgroundActive(hovered, focused, pressed, windowFocused);
return changed;
}
@@ -358,7 +361,8 @@ public class RippleDrawable extends LayerDrawable {
}
}
- private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed) {
+ private void setBackgroundActive(boolean hovered, boolean focused, boolean pressed,
+ boolean windowFocused) {
if (mState.mRippleStyle == STYLE_SOLID) {
if (mBackground == null && (hovered || focused)) {
mBackground = new RippleBackground(this, mHotspotBounds, isBounded());
@@ -370,7 +374,7 @@ public class RippleDrawable extends LayerDrawable {
} else {
if (focused || hovered) {
if (!pressed) {
- enterPatternedBackgroundAnimation(focused, hovered);
+ enterPatternedBackgroundAnimation(focused, hovered, windowFocused);
}
} else {
exitPatternedBackgroundAnimation();
@@ -840,9 +844,14 @@ public class RippleDrawable extends LayerDrawable {
invalidateSelf(false);
}
- private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered) {
+ private void enterPatternedBackgroundAnimation(boolean focused, boolean hovered,
+ boolean windowFocused) {
mBackgroundOpacity = 0;
- mTargetBackgroundOpacity = focused ? .6f : hovered ? .2f : 0f;
+ if (focused) {
+ mTargetBackgroundOpacity = windowFocused ? .6f : .2f;
+ } else {
+ mTargetBackgroundOpacity = hovered ? .2f : 0f;
+ }
if (mBackgroundAnimation != null) mBackgroundAnimation.cancel();
// after cancel
mRunBackgroundAnimation = true;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
index 921552b6cfbb..68ff806c6765 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/CommonFoldingFeature.java
@@ -199,7 +199,7 @@ public final class CommonFoldingFeature {
throw new IllegalArgumentException(
"Display feature rectangle cannot have zero width and height simultaneously.");
}
- this.mRect = rect;
+ this.mRect = new Rect(rect);
}
/** Returns the type of the feature. */
@@ -217,7 +217,7 @@ public final class CommonFoldingFeature {
/** Returns the bounds of the feature. */
@NonNull
public Rect getRect() {
- return mRect;
+ return new Rect(mRect);
}
@Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
index fdcb7be597d5..cc2bb63ca8e1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/DeviceStateManagerFoldingFeatureProducer.java
@@ -22,7 +22,6 @@ import static androidx.window.common.CommonFoldingFeature.COMMON_STATE_UNKNOWN;
import static androidx.window.common.CommonFoldingFeature.parseListFromString;
import android.annotation.NonNull;
-import android.annotation.Nullable;
import android.content.Context;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateManager.DeviceStateCallback;
@@ -30,22 +29,25 @@ import android.text.TextUtils;
import android.util.Log;
import android.util.SparseIntArray;
+import androidx.window.util.AcceptOnceConsumer;
import androidx.window.util.BaseDataProducer;
-import androidx.window.util.DataProducer;
import com.android.internal.R;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
- * An implementation of {@link androidx.window.util.DataProducer} that returns the device's posture
- * by mapping the state returned from {@link DeviceStateManager} to values provided in the resources
- * config at {@link R.array#config_device_state_postures}.
+ * An implementation of {@link androidx.window.util.BaseDataProducer} that returns
+ * the device's posture by mapping the state returned from {@link DeviceStateManager} to
+ * values provided in the resources' config at {@link R.array#config_device_state_postures}.
*/
-public final class DeviceStateManagerFoldingFeatureProducer extends
- BaseDataProducer<List<CommonFoldingFeature>> {
+public final class DeviceStateManagerFoldingFeatureProducer
+ extends BaseDataProducer<List<CommonFoldingFeature>> {
private static final String TAG =
DeviceStateManagerFoldingFeatureProducer.class.getSimpleName();
private static final boolean DEBUG = false;
@@ -54,15 +56,11 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
private int mCurrentDeviceState = INVALID_DEVICE_STATE;
- private final DeviceStateCallback mDeviceStateCallback = (state) -> {
- mCurrentDeviceState = state;
- notifyDataChanged();
- };
@NonNull
- private final DataProducer<String> mRawFoldSupplier;
+ private final BaseDataProducer<String> mRawFoldSupplier;
public DeviceStateManagerFoldingFeatureProducer(@NonNull Context context,
- @NonNull DataProducer<String> rawFoldSupplier) {
+ @NonNull BaseDataProducer<String> rawFoldSupplier) {
mRawFoldSupplier = rawFoldSupplier;
String[] deviceStatePosturePairs = context.getResources()
.getStringArray(R.array.config_device_state_postures);
@@ -70,7 +68,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
String[] deviceStatePostureMapping = deviceStatePosturePair.split(":");
if (deviceStatePostureMapping.length != 2) {
if (DEBUG) {
- Log.e(TAG, "Malformed device state posture pair: " + deviceStatePosturePair);
+ Log.e(TAG, "Malformed device state posture pair: "
+ + deviceStatePosturePair);
}
continue;
}
@@ -82,7 +81,8 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
posture = Integer.parseInt(deviceStatePostureMapping[1]);
} catch (NumberFormatException e) {
if (DEBUG) {
- Log.e(TAG, "Failed to parse device state or posture: " + deviceStatePosturePair,
+ Log.e(TAG, "Failed to parse device state or posture: "
+ + deviceStatePosturePair,
e);
}
continue;
@@ -92,32 +92,95 @@ public final class DeviceStateManagerFoldingFeatureProducer extends
}
if (mDeviceStateToPostureMap.size() > 0) {
- context.getSystemService(DeviceStateManager.class)
- .registerCallback(context.getMainExecutor(), mDeviceStateCallback);
+ DeviceStateCallback deviceStateCallback = (state) -> {
+ mCurrentDeviceState = state;
+ mRawFoldSupplier.getData(this::notifyFoldingFeatureChange);
+ };
+ Objects.requireNonNull(context.getSystemService(DeviceStateManager.class))
+ .registerCallback(context.getMainExecutor(), deviceStateCallback);
}
}
- @Override
- @Nullable
- public Optional<List<CommonFoldingFeature>> getData() {
- final int globalHingeState = globalHingeState();
- Optional<String> displayFeaturesString = mRawFoldSupplier.getData();
- if (displayFeaturesString.isEmpty() || TextUtils.isEmpty(displayFeaturesString.get())) {
- return Optional.empty();
+ /**
+ * Add a callback to mCallbacks if there is no device state. This callback will be run
+ * once a device state is set. Otherwise,run the callback immediately.
+ */
+ private void runCallbackWhenValidState(@NonNull Consumer<List<CommonFoldingFeature>> callback,
+ String displayFeaturesString) {
+ if (isCurrentStateValid()) {
+ callback.accept(calculateFoldingFeature(displayFeaturesString));
+ } else {
+ // This callback will be added to mCallbacks and removed once it runs once.
+ AcceptOnceConsumer<List<CommonFoldingFeature>> singleRunCallback =
+ new AcceptOnceConsumer<>(this, callback);
+ addDataChangedCallback(singleRunCallback);
}
- return Optional.of(parseListFromString(displayFeaturesString.get(), globalHingeState));
+ }
+
+ /**
+ * Checks to find {@link DeviceStateManagerFoldingFeatureProducer#mCurrentDeviceState} in the
+ * {@link DeviceStateManagerFoldingFeatureProducer#mDeviceStateToPostureMap} which was
+ * initialized in the constructor of {@link DeviceStateManagerFoldingFeatureProducer}.
+ * Returns a boolean value of whether the device state is valid.
+ */
+ private boolean isCurrentStateValid() {
+ // If the device state is not found in the map, indexOfKey returns a negative number.
+ return mDeviceStateToPostureMap.indexOfKey(mCurrentDeviceState) >= 0;
}
@Override
- protected void onListenersChanged(Set<Runnable> callbacks) {
+ protected void onListenersChanged(
+ @NonNull Set<Consumer<List<CommonFoldingFeature>>> callbacks) {
super.onListenersChanged(callbacks);
if (callbacks.isEmpty()) {
- mRawFoldSupplier.removeDataChangedCallback(this::notifyDataChanged);
+ mCurrentDeviceState = INVALID_DEVICE_STATE;
+ mRawFoldSupplier.removeDataChangedCallback(this::notifyFoldingFeatureChange);
+ } else {
+ mRawFoldSupplier.addDataChangedCallback(this::notifyFoldingFeatureChange);
+ }
+ }
+
+ @NonNull
+ @Override
+ public Optional<List<CommonFoldingFeature>> getCurrentData() {
+ Optional<String> displayFeaturesString = mRawFoldSupplier.getCurrentData();
+ if (!isCurrentStateValid()) {
+ return Optional.empty();
+ } else {
+ return displayFeaturesString.map(this::calculateFoldingFeature);
+ }
+ }
+
+ /**
+ * Adds the data to the storeFeaturesConsumer when the data is ready.
+ * @param storeFeaturesConsumer a consumer to collect the data when it is first available.
+ */
+ public void getData(Consumer<List<CommonFoldingFeature>> storeFeaturesConsumer) {
+ mRawFoldSupplier.getData((String displayFeaturesString) -> {
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ storeFeaturesConsumer.accept(new ArrayList<>());
+ } else {
+ runCallbackWhenValidState(storeFeaturesConsumer, displayFeaturesString);
+ }
+ });
+ }
+
+ private void notifyFoldingFeatureChange(String displayFeaturesString) {
+ if (!isCurrentStateValid()) {
+ return;
+ }
+ if (TextUtils.isEmpty(displayFeaturesString)) {
+ notifyDataChanged(new ArrayList<>());
} else {
- mRawFoldSupplier.addDataChangedCallback(this::notifyDataChanged);
+ notifyDataChanged(calculateFoldingFeature(displayFeaturesString));
}
}
+ private List<CommonFoldingFeature> calculateFoldingFeature(String displayFeaturesString) {
+ final int globalHingeState = globalHingeState();
+ return parseListFromString(displayFeaturesString, globalHingeState);
+ }
+
private int globalHingeState() {
return mDeviceStateToPostureMap.get(mCurrentDeviceState, COMMON_STATE_UNKNOWN);
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
index 69ad1badce60..7906342d445d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/common/RawFoldingFeatureProducer.java
@@ -32,6 +32,7 @@ import com.android.internal.R;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Implementation of {@link androidx.window.util.DataProducer} that produces a
@@ -40,7 +41,7 @@ import java.util.Set;
* settings where the {@link String} property is saved with the key
* {@link RawFoldingFeatureProducer#DISPLAY_FEATURES}. If this value is null or empty then the
* value in {@link android.content.res.Resources} is used. If both are empty then
- * {@link RawFoldingFeatureProducer#getData()} returns an empty object.
+ * {@link RawFoldingFeatureProducer#getData} returns an empty object.
* {@link RawFoldingFeatureProducer} listens to changes in the setting so that it can override
* the system {@link CommonFoldingFeature} data.
*/
@@ -63,12 +64,13 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
@Override
@NonNull
- public Optional<String> getData() {
+ public void getData(Consumer<String> dataConsumer) {
String displayFeaturesString = getFeatureString();
if (displayFeaturesString == null) {
- return Optional.empty();
+ dataConsumer.accept("");
+ } else {
+ dataConsumer.accept(displayFeaturesString);
}
- return Optional.of(displayFeaturesString);
}
/**
@@ -84,7 +86,7 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
@Override
- protected void onListenersChanged(Set<Runnable> callbacks) {
+ protected void onListenersChanged(Set<Consumer<String>> callbacks) {
if (callbacks.isEmpty()) {
unregisterObserversIfNeeded();
} else {
@@ -92,6 +94,12 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
}
}
+ @NonNull
+ @Override
+ public Optional<String> getCurrentData() {
+ return Optional.of(getFeatureString());
+ }
+
/**
* Registers settings observers, if needed. When settings observers are registered for this
* producer callbacks for changes in data will be triggered.
@@ -125,8 +133,8 @@ public final class RawFoldingFeatureProducer extends BaseDataProducer<String> {
@Override
public void onChange(boolean selfChange, Uri uri) {
if (mDisplayFeaturesUri.equals(uri)) {
- notifyDataChanged();
+ notifyDataChanged(getFeatureString());
}
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index 3ff531573f1f..0dba6b0049c0 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -72,6 +72,7 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
@NonNull Configuration parentConfig);
void onActivityReparentToTask(int taskId, @NonNull Intent activityIntent,
@NonNull IBinder activityToken);
+ void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType);
}
/**
@@ -323,4 +324,18 @@ class JetpackTaskFragmentOrganizer extends TaskFragmentOrganizer {
mCallback.onActivityReparentToTask(taskId, activityIntent, activityToken);
}
}
+
+ @Override
+ public void onTaskFragmentError(@NonNull IBinder errorCallbackToken,
+ @Nullable TaskFragmentInfo taskFragmentInfo,
+ int opType, @NonNull Throwable exception) {
+ if (taskFragmentInfo != null) {
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ mFragmentInfos.put(fragmentToken, taskFragmentInfo);
+ }
+
+ if (mCallback != null) {
+ mCallback.onTaskFragmentError(taskFragmentInfo, opType);
+ }
+ }
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index c9a0d7d99cc6..c688080ad929 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -16,8 +16,11 @@
package androidx.window.extensions.embedding;
+import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
@@ -43,6 +46,7 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.SystemProperties;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
@@ -70,6 +74,8 @@ import java.util.function.Consumer;
public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmentCallback,
ActivityEmbeddingComponent {
static final String TAG = "SplitController";
+ static final boolean ENABLE_SHELL_TRANSITIONS =
+ SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@VisibleForTesting
@GuardedBy("mLock")
@@ -94,6 +100,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
private final List<SplitInfo> mLastReportedSplitStates = new ArrayList<>();
private final Handler mHandler;
private final Object mLock = new Object();
+ private final ActivityStartMonitor mActivityStartMonitor;
public SplitController() {
final MainThreadExecutor executor = new MainThreadExecutor();
@@ -105,7 +112,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
new LifecycleCallbacks());
// Intercept activity starts to route activities to new containers if necessary.
Instrumentation instrumentation = activityThread.getInstrumentation();
- instrumentation.addMonitor(new ActivityStartMonitor());
+ mActivityStartMonitor = new ActivityStartMonitor();
+ instrumentation.addMonitor(mActivityStartMonitor);
}
/** Updates the embedding rules applied to future activity launches. */
@@ -290,6 +298,37 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
}
}
+ @Override
+ public void onTaskFragmentError(@Nullable TaskFragmentInfo taskFragmentInfo, int opType) {
+ synchronized (mLock) {
+ switch (opType) {
+ case HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT:
+ case HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT: {
+ final TaskFragmentContainer container;
+ if (taskFragmentInfo != null) {
+ container = getContainer(taskFragmentInfo.getFragmentToken());
+ } else {
+ container = null;
+ }
+ if (container == null) {
+ break;
+ }
+
+ // Update the latest taskFragmentInfo and perform necessary clean-up
+ container.setInfo(taskFragmentInfo);
+ container.clearPendingAppearedActivities();
+ if (container.isEmpty()) {
+ mPresenter.cleanupContainer(container, false /* shouldFinishDependent */);
+ }
+ break;
+ }
+ default:
+ Log.e(TAG, "onTaskFragmentError: taskFragmentInfo = " + taskFragmentInfo
+ + ", opType = " + opType);
+ }
+ }
+ }
+
/** Called on receiving {@link #onTaskFragmentVanished(TaskFragmentInfo)} for cleanup. */
private void cleanupTaskFragment(@NonNull IBinder taskFragmentToken) {
for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
@@ -332,6 +371,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* bounds is large enough for at least one split rule.
*/
private void updateAnimationOverride(@NonNull TaskContainer taskContainer) {
+ if (ENABLE_SHELL_TRANSITIONS) {
+ // TODO(b/207070762): cleanup with legacy app transition
+ // Animation will be handled by WM Shell with Shell transition enabled.
+ return;
+ }
if (!taskContainer.isTaskBoundsInitialized()
|| !taskContainer.isWindowingModeInitialized()) {
// We don't know about the Task bounds/windowingMode yet.
@@ -381,6 +425,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* in a state that the caller shouldn't handle.
*/
@VisibleForTesting
+ @GuardedBy("mLock")
boolean resolveActivityToContainer(@NonNull Activity activity, boolean isOnReparent) {
if (isInPictureInPicture(activity) || activity.isFinishing()) {
// We don't embed activity when it is in PIP, or finishing. Return true since we don't
@@ -607,6 +652,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Checks if there is a rule to split the two activities. If there is one, puts them into split
* and returns {@code true}. Otherwise, returns {@code false}.
*/
+ @GuardedBy("mLock")
private boolean putActivitiesIntoSplitIfNecessary(@NonNull Activity primaryActivity,
@NonNull Activity secondaryActivity) {
final SplitPairRule splitRule = getSplitRule(primaryActivity, secondaryActivity);
@@ -793,6 +839,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* Returns a container for the new activity intent to launch into as splitting with the primary
* activity.
*/
+ @GuardedBy("mLock")
@Nullable
private TaskFragmentContainer getSecondaryContainerForSplitIfAny(
@NonNull WindowContainerTransaction wct, @NonNull Activity primaryActivity,
@@ -865,6 +912,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* if needed.
* @param taskId parent Task of the new TaskFragment.
*/
+ @GuardedBy("mLock")
TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
@Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId) {
if (activityInTask == null) {
@@ -1373,6 +1421,11 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
return ActivityThread.currentActivityThread().getActivity(activityToken);
}
+ @VisibleForTesting
+ ActivityStartMonitor getActivityStartMonitor() {
+ return mActivityStartMonitor;
+ }
+
/**
* Gets the token of the initial TaskFragment that embedded this activity. Do not rely on it
* after creation because the activity could be reparented.
@@ -1524,7 +1577,10 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
* A monitor that intercepts all activity start requests originating in the client process and
* can amend them to target a specific task fragment to form a split.
*/
- private class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
+ @VisibleForTesting
+ class ActivityStartMonitor extends Instrumentation.ActivityMonitor {
+ @VisibleForTesting
+ Intent mCurrentIntent;
@Override
public Instrumentation.ActivityResult onStartActivity(@NonNull Context who,
@@ -1552,11 +1608,29 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen
// the dedicated container.
options.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
launchedInTaskFragment.getTaskFragmentToken());
+ mCurrentIntent = intent;
}
}
return super.onStartActivity(who, intent, options);
}
+
+ @Override
+ public void onStartActivityResult(int result, @NonNull Bundle bOptions) {
+ super.onStartActivityResult(result, bOptions);
+ if (mCurrentIntent != null && result != START_SUCCESS) {
+ // Clear the pending appeared intent if the activity was not started successfully.
+ final IBinder token = bOptions.getBinder(
+ ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN);
+ if (token != null) {
+ final TaskFragmentContainer container = getContainer(token);
+ if (container != null) {
+ container.clearPendingAppearedIntentIfNeeded(mCurrentIntent);
+ }
+ }
+ }
+ mCurrentIntent = null;
+ }
}
/**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
index 586ac1f212a1..5cc496a225c2 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentAnimationSpec.java
@@ -180,9 +180,18 @@ class TaskFragmentAnimationSpec {
Animation loadOpenAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
- final Animation animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_open_enter
- : com.android.internal.R.anim.task_fragment_open_exit);
+ final Animation animation;
+ // Background color on TaskDisplayArea has already been set earlier in
+ // WindowContainer#getAnimationAdapter.
+ if (target.showBackdrop) {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_clear_top_open_enter
+ : com.android.internal.R.anim.task_fragment_clear_top_open_exit);
+ } else {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_open_enter
+ : com.android.internal.R.anim.task_fragment_open_exit);
+ }
animation.initialize(target.localBounds.width(), target.localBounds.height(),
wholeAnimationBounds.width(), wholeAnimationBounds.height());
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
@@ -192,9 +201,16 @@ class TaskFragmentAnimationSpec {
Animation loadCloseAnimation(@NonNull RemoteAnimationTarget target,
@NonNull Rect wholeAnimationBounds) {
final boolean isEnter = target.mode != MODE_CLOSING;
- final Animation animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
- ? com.android.internal.R.anim.task_fragment_close_enter
- : com.android.internal.R.anim.task_fragment_close_exit);
+ final Animation animation;
+ if (target.showBackdrop) {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_clear_top_close_enter
+ : com.android.internal.R.anim.task_fragment_clear_top_close_exit);
+ } else {
+ animation = mTransitionAnimation.loadDefaultAnimationRes(isEnter
+ ? com.android.internal.R.anim.task_fragment_close_enter
+ : com.android.internal.R.anim.task_fragment_close_exit);
+ }
animation.initialize(target.localBounds.width(), target.localBounds.height(),
wholeAnimationBounds.width(), wholeAnimationBounds.height());
animation.scaleCurrentDuration(mTransitionAnimationScaleSetting);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index abf32a26efa2..37f5b6dc399e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -193,11 +193,32 @@ class TaskFragmentContainer {
mPendingAppearedActivities.remove(pendingAppearedActivity);
}
+ void clearPendingAppearedActivities() {
+ mPendingAppearedActivities.clear();
+ mPendingAppearedIntent = null;
+ }
+
@Nullable
Intent getPendingAppearedIntent() {
return mPendingAppearedIntent;
}
+ void setPendingAppearedIntent(@Nullable Intent intent) {
+ mPendingAppearedIntent = intent;
+ }
+
+ /**
+ * Clears the pending appeared Intent if it is the same as given Intent. Otherwise, the
+ * pending appeared Intent is cleared when TaskFragmentInfo is set and is not empty (has
+ * running activities).
+ */
+ void clearPendingAppearedIntentIfNeeded(@NonNull Intent intent) {
+ if (mPendingAppearedIntent == null || mPendingAppearedIntent != intent) {
+ return;
+ }
+ mPendingAppearedIntent = null;
+ }
+
boolean hasActivity(@NonNull IBinder token) {
if (mInfo != null && mInfo.getActivities().contains(token)) {
return true;
@@ -230,13 +251,18 @@ class TaskFragmentContainer {
void setInfo(@NonNull TaskFragmentInfo info) {
if (!mIsFinished && mInfo == null && info.isEmpty()) {
- // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if it is
- // still empty after timeout.
+ // onTaskFragmentAppeared with empty info. We will remove the TaskFragment if no
+ // pending appeared intent/activities. Otherwise, wait and removing the TaskFragment if
+ // it is still empty after timeout.
mAppearEmptyTimeout = () -> {
mAppearEmptyTimeout = null;
mController.onTaskFragmentAppearEmptyTimeout(this);
};
- mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
+ if (mPendingAppearedIntent != null || !mPendingAppearedActivities.isEmpty()) {
+ mController.getHandler().postDelayed(mAppearEmptyTimeout, APPEAR_EMPTY_TIMEOUT_MS);
+ } else {
+ mAppearEmptyTimeout.run();
+ }
} else if (mAppearEmptyTimeout != null && !info.isEmpty()) {
mController.getHandler().removeCallbacks(mAppearEmptyTimeout);
mAppearEmptyTimeout = null;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index c1d1c8e8d4e0..6bfb16a3c22d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -25,8 +25,7 @@ import static androidx.window.util.ExtensionHelper.transformToWindowSpaceRect;
import android.annotation.Nullable;
import android.app.Activity;
-import android.app.ActivityManager;
-import android.app.ActivityManager.AppTask;
+import android.app.ActivityClient;
import android.app.Application;
import android.app.WindowConfiguration;
import android.content.Context;
@@ -34,7 +33,6 @@ import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
import android.util.ArrayMap;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.window.common.CommonFoldingFeature;
@@ -46,7 +44,6 @@ import androidx.window.util.DataProducer;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import java.util.function.Consumer;
@@ -66,7 +63,7 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
- public WindowLayoutComponentImpl(Context context) {
+ public WindowLayoutComponentImpl(@NonNull Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
RawFoldingFeatureProducer foldingFeatureProducer = new RawFoldingFeatureProducer(context);
@@ -83,8 +80,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*/
public void addWindowLayoutInfoListener(@NonNull Activity activity,
@NonNull Consumer<WindowLayoutInfo> consumer) {
+ mFoldingFeatureProducer.getData((features) -> {
+ // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, features);
+ consumer.accept(newWindowLayout);
+ });
mWindowLayoutChangeListeners.put(activity, consumer);
- onDisplayFeaturesChanged();
}
/**
@@ -92,18 +93,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param consumer no longer interested in receiving updates to {@link WindowLayoutInfo}
*/
- public void removeWindowLayoutInfoListener(
- @NonNull Consumer<WindowLayoutInfo> consumer) {
+ public void removeWindowLayoutInfoListener(@NonNull Consumer<WindowLayoutInfo> consumer) {
mWindowLayoutChangeListeners.values().remove(consumer);
- onDisplayFeaturesChanged();
- }
-
- void updateWindowLayout(@NonNull Activity activity,
- @NonNull WindowLayoutInfo newLayout) {
- Consumer<WindowLayoutInfo> consumer = mWindowLayoutChangeListeners.get(activity);
- if (consumer != null) {
- consumer.accept(newLayout);
- }
}
@NonNull
@@ -111,7 +102,6 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
return mWindowLayoutChangeListeners.keySet();
}
- @NonNull
private boolean isListeningForLayoutChanges(IBinder token) {
for (Activity activity: getActivitiesListeningForLayoutChanges()) {
if (token.equals(activity.getWindow().getAttributes().token)) {
@@ -128,12 +118,12 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
/**
* A convenience method to translate from the common feature state to the extensions feature
* state. More specifically, translates from {@link CommonFoldingFeature.State} to
- * {@link FoldingFeature.STATE_FLAT} or {@link FoldingFeature.STATE_HALF_OPENED}. If it is not
+ * {@link FoldingFeature#STATE_FLAT} or {@link FoldingFeature#STATE_HALF_OPENED}. If it is not
* possible to translate, then we will return a {@code null} value.
*
* @param state if it matches a value in {@link CommonFoldingFeature.State}, {@code null}
- * otherwise. @return a {@link FoldingFeature.STATE_FLAT} or
- * {@link FoldingFeature.STATE_HALF_OPENED} if the given state matches a value in
+ * otherwise. @return a {@link FoldingFeature#STATE_FLAT} or
+ * {@link FoldingFeature#STATE_HALF_OPENED} if the given state matches a value in
* {@link CommonFoldingFeature.State} and {@code null} otherwise.
*/
@Nullable
@@ -147,17 +137,24 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
}
}
- private void onDisplayFeaturesChanged() {
+ private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
for (Activity activity : getActivitiesListeningForLayoutChanges()) {
- WindowLayoutInfo newLayout = getWindowLayoutInfo(activity);
- updateWindowLayout(activity, newLayout);
+ // Get the WindowLayoutInfo from the activity and pass the value to the layoutConsumer.
+ Consumer<WindowLayoutInfo> layoutConsumer = mWindowLayoutChangeListeners.get(activity);
+ WindowLayoutInfo newWindowLayout = getWindowLayoutInfo(activity, storedFeatures);
+ layoutConsumer.accept(newWindowLayout);
}
}
- @NonNull
- private WindowLayoutInfo getWindowLayoutInfo(@NonNull Activity activity) {
- List<DisplayFeature> displayFeatures = getDisplayFeatures(activity);
- return new WindowLayoutInfo(displayFeatures);
+ /**
+ * Translates the {@link DisplayFeature} into a {@link WindowLayoutInfo} when a
+ * valid state is found.
+ * @param activity a proxy for the {@link android.view.Window} that contains the
+ */
+ private WindowLayoutInfo getWindowLayoutInfo(
+ @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
+ List<DisplayFeature> displayFeatureList = getDisplayFeatures(activity, storedFeatures);
+ return new WindowLayoutInfo(displayFeatureList);
}
/**
@@ -175,67 +172,54 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
*
* @param activity a proxy for the {@link android.view.Window} that contains the
* {@link DisplayFeature}.
- * @return a {@link List} of valid {@link DisplayFeature} that
* are within the {@link android.view.Window} of the {@link Activity}
*/
- private List<DisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
+ private List<DisplayFeature> getDisplayFeatures(
+ @NonNull Activity activity, List<CommonFoldingFeature> storedFeatures) {
List<DisplayFeature> features = new ArrayList<>();
- int displayId = activity.getDisplay().getDisplayId();
- if (displayId != DEFAULT_DISPLAY) {
- Log.w(TAG, "This sample doesn't support display features on secondary displays");
+ if (!shouldReportDisplayFeatures(activity)) {
return features;
}
- if (isTaskInMultiWindowMode(activity)) {
- // It is recommended not to report any display features in multi-window mode, since it
- // won't be possible to synchronize the display feature positions with window movement.
- return features;
- }
-
- Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
- if (storedFeatures.isPresent()) {
- for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
- Integer state = convertToExtensionState(baseFeature.getState());
- if (state == null) {
- continue;
- }
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
+ int displayId = activity.getDisplay().getDisplayId();
+ for (CommonFoldingFeature baseFeature : storedFeatures) {
+ Integer state = convertToExtensionState(baseFeature.getState());
+ if (state == null) {
+ continue;
+ }
+ Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
- if (!isRectZero(featureRect)) {
- // TODO(b/228641877) Remove guarding if when fixed.
- features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
- }
+ if (!isRectZero(featureRect)) {
+ // TODO(b/228641877): Remove guarding when fixed.
+ features.add(new FoldingFeature(featureRect, baseFeature.getType(), state));
}
}
return features;
}
/**
- * Checks whether the task associated with the activity is in multi-window. If task info is not
- * available it defaults to {@code true}.
+ * Checks whether display features should be reported for the activity.
+ * TODO(b/238948678): Support reporting display features in all windowing modes.
*/
- private boolean isTaskInMultiWindowMode(@NonNull Activity activity) {
- final ActivityManager am = activity.getSystemService(ActivityManager.class);
- if (am == null) {
- return true;
- }
-
- final List<AppTask> appTasks = am.getAppTasks();
- final int taskId = activity.getTaskId();
- AppTask task = null;
- for (AppTask t : appTasks) {
- if (t.getTaskInfo().taskId == taskId) {
- task = t;
- break;
- }
+ private boolean shouldReportDisplayFeatures(@NonNull Activity activity) {
+ int displayId = activity.getDisplay().getDisplayId();
+ if (displayId != DEFAULT_DISPLAY) {
+ // Display features are not supported on secondary displays.
+ return false;
}
- if (task == null) {
- // The task might be removed on the server already.
- return true;
+ final int taskWindowingMode = ActivityClient.getInstance().getTaskWindowingMode(
+ activity.getActivityToken());
+ if (taskWindowingMode == -1) {
+ // If we cannot determine the task windowing mode for any reason, it is likely that we
+ // won't be able to determine its position correctly as well. DisplayFeatures' bounds
+ // in this case can't be computed correctly, so we should skip.
+ return false;
}
- return WindowConfiguration.inMultiWindowMode(task.getTaskInfo().getWindowingMode());
+ // It is recommended not to report any display features in multi-window mode, since it
+ // won't be possible to synchronize the display feature positions with window movement.
+ return !WindowConfiguration.inMultiWindowMode(taskWindowingMode);
}
/**
@@ -262,7 +246,8 @@ public class WindowLayoutComponentImpl implements WindowLayoutComponent {
private void onDisplayFeaturesChangedIfListening(Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || isListeningForLayoutChanges(token)) {
- onDisplayFeaturesChanged();
+ mFoldingFeatureProducer.getData(
+ WindowLayoutComponentImpl.this::onDisplayFeaturesChanged);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 970f0a2af632..5bfb0ebdcaa8 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -28,41 +28,42 @@ import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
-import android.util.Log;
import androidx.annotation.NonNull;
import androidx.window.common.CommonFoldingFeature;
import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
import androidx.window.common.EmptyLifecycleCallbacksAdapter;
import androidx.window.common.RawFoldingFeatureProducer;
-import androidx.window.util.DataProducer;
+import androidx.window.util.BaseDataProducer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.Optional;
/**
* Reference implementation of androidx.window.sidecar OEM interface for use with
* WindowManager Jetpack.
*/
class SampleSidecarImpl extends StubSidecar {
- private static final String TAG = "SampleSidecar";
-
- private final DataProducer<List<CommonFoldingFeature>> mFoldingFeatureProducer;
-
+ private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
SampleSidecarImpl(Context context) {
((Application) context.getApplicationContext())
.registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
- DataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
- mFoldingFeatureProducer = new DeviceStateManagerFoldingFeatureProducer(context,
- settingsFeatureProducer);
+ BaseDataProducer<String> settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+ BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
+ new DeviceStateManagerFoldingFeatureProducer(context,
+ settingsFeatureProducer);
- mFoldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
+ foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
}
- private void onDisplayFeaturesChanged() {
+ private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) {
+ mStoredFeatures = storedFeatures;
+ }
+
+ private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
+ setStoredFeatures(storedFeatures);
updateDeviceState(getDeviceState());
for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
@@ -79,16 +80,16 @@ class SampleSidecarImpl extends StubSidecar {
}
private int deviceStateFromFeature() {
- List<CommonFoldingFeature> storedFeatures = mFoldingFeatureProducer.getData()
- .orElse(Collections.emptyList());
- for (int i = 0; i < storedFeatures.size(); i++) {
- CommonFoldingFeature feature = storedFeatures.get(i);
+ for (int i = 0; i < mStoredFeatures.size(); i++) {
+ CommonFoldingFeature feature = mStoredFeatures.get(i);
final int state = feature.getState();
switch (state) {
case CommonFoldingFeature.COMMON_STATE_FLAT:
return SidecarDeviceState.POSTURE_OPENED;
case CommonFoldingFeature.COMMON_STATE_HALF_OPENED:
return SidecarDeviceState.POSTURE_HALF_OPENED;
+ case CommonFoldingFeature.COMMON_STATE_UNKNOWN:
+ return SidecarDeviceState.POSTURE_UNKNOWN;
}
}
return SidecarDeviceState.POSTURE_UNKNOWN;
@@ -109,7 +110,6 @@ class SampleSidecarImpl extends StubSidecar {
private List<SidecarDisplayFeature> getDisplayFeatures(@NonNull Activity activity) {
int displayId = activity.getDisplay().getDisplayId();
if (displayId != DEFAULT_DISPLAY) {
- Log.w(TAG, "This sample doesn't support display features on secondary displays");
return Collections.emptyList();
}
@@ -119,18 +119,15 @@ class SampleSidecarImpl extends StubSidecar {
return Collections.emptyList();
}
- Optional<List<CommonFoldingFeature>> storedFeatures = mFoldingFeatureProducer.getData();
List<SidecarDisplayFeature> features = new ArrayList<>();
- if (storedFeatures.isPresent()) {
- for (CommonFoldingFeature baseFeature : storedFeatures.get()) {
- SidecarDisplayFeature feature = new SidecarDisplayFeature();
- Rect featureRect = baseFeature.getRect();
- rotateRectToDisplayRotation(displayId, featureRect);
- transformToWindowSpaceRect(activity, featureRect);
- feature.setRect(featureRect);
- feature.setType(baseFeature.getType());
- features.add(feature);
- }
+ for (CommonFoldingFeature baseFeature : mStoredFeatures) {
+ SidecarDisplayFeature feature = new SidecarDisplayFeature();
+ Rect featureRect = baseFeature.getRect();
+ rotateRectToDisplayRotation(displayId, featureRect);
+ transformToWindowSpaceRect(activity, featureRect);
+ feature.setRect(featureRect);
+ feature.setType(baseFeature.getType());
+ features.add(feature);
}
return Collections.unmodifiableList(features);
}
@@ -138,7 +135,7 @@ class SampleSidecarImpl extends StubSidecar {
@Override
protected void onListenersChanged() {
if (hasListeners()) {
- onDisplayFeaturesChanged();
+ onDisplayFeaturesChanged(mStoredFeatures);
}
}
@@ -158,7 +155,7 @@ class SampleSidecarImpl extends StubSidecar {
private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
IBinder token = activity.getWindow().getAttributes().token;
if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
- onDisplayFeaturesChanged();
+ onDisplayFeaturesChanged(mStoredFeatures);
}
}
}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
new file mode 100644
index 000000000000..7624b693ac43
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/AcceptOnceConsumer.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.util;
+
+import android.annotation.NonNull;
+
+import java.util.function.Consumer;
+
+/**
+ * A base class that works with {@link BaseDataProducer} to add/remove a consumer that should
+ * only be used once when {@link BaseDataProducer#notifyDataChanged} is called.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
+ */
+public class AcceptOnceConsumer<T> implements Consumer<T> {
+ private final Consumer<T> mCallback;
+ private final DataProducer<T> mProducer;
+
+ public AcceptOnceConsumer(@NonNull DataProducer<T> producer, @NonNull Consumer<T> callback) {
+ mProducer = producer;
+ mCallback = callback;
+ }
+
+ @Override
+ public void accept(@NonNull T t) {
+ mCallback.accept(t);
+ mProducer.removeDataChangedCallback(this);
+ }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
index 930db3b701b7..0da44ac36a6e 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/BaseDataProducer.java
@@ -19,38 +19,48 @@ package androidx.window.util;
import androidx.annotation.NonNull;
import java.util.LinkedHashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
/**
* Base class that provides the implementation for the callback mechanism of the
* {@link DataProducer} API.
*
- * @param <T> The type of data this producer returns through {@link #getData()}.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public abstract class BaseDataProducer<T> implements DataProducer<T> {
- private final Set<Runnable> mCallbacks = new LinkedHashSet<>();
+ private final Set<Consumer<T>> mCallbacks = new LinkedHashSet<>();
@Override
- public final void addDataChangedCallback(@NonNull Runnable callback) {
+ public final void addDataChangedCallback(@NonNull Consumer<T> callback) {
mCallbacks.add(callback);
+ Optional<T> currentData = getCurrentData();
+ currentData.ifPresent(callback);
onListenersChanged(mCallbacks);
}
@Override
- public final void removeDataChangedCallback(@NonNull Runnable callback) {
+ public final void removeDataChangedCallback(@NonNull Consumer<T> callback) {
mCallbacks.remove(callback);
onListenersChanged(mCallbacks);
}
- protected void onListenersChanged(Set<Runnable> callbacks) {}
+ protected void onListenersChanged(Set<Consumer<T>> callbacks) {}
/**
- * Called to notify all registered callbacks that the data provided by {@link #getData()} has
- * changed.
+ * @return the current data if available and {@code Optional.empty()} otherwise.
*/
- protected void notifyDataChanged() {
- for (Runnable callback : mCallbacks) {
- callback.run();
+ @NonNull
+ public abstract Optional<T> getCurrentData();
+
+ /**
+ * Called to notify all registered consumers that the data provided
+ * by {@link DataProducer#getData} has changed.
+ */
+ protected void notifyDataChanged(T value) {
+ for (Consumer<T> callback : mCallbacks) {
+ callback.accept(value);
}
}
-}
+} \ No newline at end of file
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
index d4d1a23b756b..ec301dc34aaa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/DataProducer.java
@@ -18,26 +18,27 @@ package androidx.window.util;
import android.annotation.NonNull;
-import java.util.Optional;
+import java.util.function.Consumer;
/**
- * Produces data through {@link #getData()} and provides a mechanism for receiving a callback when
- * the data managed by the produces has changed.
+ * Produces data through {@link DataProducer#getData} and provides a mechanism for receiving
+ * a callback when the data managed by the produces has changed.
*
- * @param <T> The type of data this producer returns through {@link #getData()}.
+ * @param <T> The type of data this producer returns through {@link DataProducer#getData}.
*/
public interface DataProducer<T> {
/**
- * Returns the data currently stored in the provider, or {@link Optional#empty()} if the
- * provider has no data.
+ * Emits the first available data at that point in time.
+ * @param dataConsumer a {@link Consumer} that will receive one value.
*/
- Optional<T> getData();
+ void getData(@NonNull Consumer<T> dataConsumer);
/**
- * Adds a callback to be notified when the data returned from {@link #getData()} has changed.
+ * Adds a callback to be notified when the data returned
+ * from {@link DataProducer#getData} has changed.
*/
- void addDataChangedCallback(@NonNull Runnable callback);
+ void addDataChangedCallback(@NonNull Consumer<T> callback);
- /** Removes a callback previously added with {@link #addDataChangedCallback(Runnable)}. */
- void removeDataChangedCallback(@NonNull Runnable callback);
+ /** Removes a callback previously added with {@link #addDataChangedCallback(Consumer)}. */
+ void removeDataChangedCallback(@NonNull Consumer<T> callback);
}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index ad496a906a33..4bc503369d0e 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -16,6 +16,7 @@
package androidx.window.extensions.embedding;
+import static android.app.ActivityManager.START_CANCELED;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
@@ -293,6 +294,26 @@ public class SplitControllerTest {
}
@Test
+ public void testOnStartActivityResultError() {
+ final Intent intent = new Intent();
+ final TaskContainer taskContainer = new TaskContainer(TASK_ID);
+ final TaskFragmentContainer container = new TaskFragmentContainer(null /* activity */,
+ intent, taskContainer, mSplitController);
+ final SplitController.ActivityStartMonitor monitor =
+ mSplitController.getActivityStartMonitor();
+
+ container.setPendingAppearedIntent(intent);
+ final Bundle bundle = new Bundle();
+ bundle.putBinder(ActivityOptions.KEY_LAUNCH_TASK_FRAGMENT_TOKEN,
+ container.getTaskFragmentToken());
+ monitor.mCurrentIntent = intent;
+ doReturn(container).when(mSplitController).getContainer(any());
+
+ monitor.onStartActivityResult(START_CANCELED, bundle);
+ assertNull(container.getPendingAppearedIntent());
+ }
+
+ @Test
public void testOnActivityCreated() {
mSplitController.onActivityCreated(mActivity);
@@ -865,6 +886,8 @@ public class SplitControllerTest {
assertSplitPair(activityBelow, mActivity, true /* matchParentBounds */);
}
+ // Suppress GuardedBy warning on unit tests
+ @SuppressWarnings("GuardedBy")
@Test
public void testResolveActivityToContainer_minDimensions_shouldExpandSplitContainer() {
final Activity primaryActivity = createMockActivity();
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
index 28c2773e25cb..44c7e6c611de 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/TaskFragmentContainerTest.java
@@ -209,21 +209,21 @@ public class TaskFragmentContainerTest {
assertNull(container.mAppearEmptyTimeout);
- // Not set if it is not appeared empty.
- final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
- doReturn(new ArrayList<>()).when(info).getActivities();
- doReturn(false).when(info).isEmpty();
- container.setInfo(info);
-
- assertNull(container.mAppearEmptyTimeout);
-
// Set timeout if the first info set is empty.
+ final TaskFragmentInfo info = mock(TaskFragmentInfo.class);
container.mInfo = null;
doReturn(true).when(info).isEmpty();
container.setInfo(info);
assertNotNull(container.mAppearEmptyTimeout);
+ // Not set if it is not appeared empty.
+ doReturn(new ArrayList<>()).when(info).getActivities();
+ doReturn(false).when(info).isEmpty();
+ container.setInfo(info);
+
+ assertNull(container.mAppearEmptyTimeout);
+
// Remove timeout after the container becomes non-empty.
doReturn(false).when(info).isEmpty();
container.setInfo(info);
@@ -232,6 +232,7 @@ public class TaskFragmentContainerTest {
// Running the timeout will call into SplitController.onTaskFragmentAppearEmptyTimeout.
container.mInfo = null;
+ container.setPendingAppearedIntent(mIntent);
doReturn(true).when(info).isEmpty();
container.setInfo(info);
container.mAppearEmptyTimeout.run();
diff --git a/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml b/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml
new file mode 100644
index 000000000000..bf325bd84c00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/decor_button_dark_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item app:state_task_focused="true" android:color="#FF000000" />
+ <item android:color="#33000000" />
+</selector>
diff --git a/libs/WindowManager/Shell/res/color/decor_button_light_color.xml b/libs/WindowManager/Shell/res/color/decor_button_light_color.xml
new file mode 100644
index 000000000000..2e48bca7786a
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/decor_button_light_color.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item app:state_task_focused="true" android:color="#FFFFFFFF" />
+ <item android:color="#33FFFFFF" />
+</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml b/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
new file mode 100644
index 000000000000..1ecc13e4da38
--- /dev/null
+++ b/libs/WindowManager/Shell/res/color/decor_caption_title_color.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <!-- Fading the to 85% blackness -->
+ <item app:state_task_focused="true" android:color="#D8D8D8" />
+ <!-- Fading the to 95% blackness -->
+ <item android:color="#F2F2F2" />
+</selector>
diff --git a/libs/WindowManager/Shell/res/color/taskbar_background.xml b/libs/WindowManager/Shell/res/color/taskbar_background.xml
index 329e5b9b31a0..b3d260299106 100644
--- a/libs/WindowManager/Shell/res/color/taskbar_background.xml
+++ b/libs/WindowManager/Shell/res/color/taskbar_background.xml
@@ -14,6 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
+<!-- Should be the same as in packages/apps/Launcher3/res/color-v31/taskbar_background.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:color="@android:color/system_neutral1_500" android:lStar="35" />
+ <item android:color="@android:color/system_neutral1_500" android:lStar="15" />
</selector> \ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
new file mode 100644
index 000000000000..8207365a737d
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_caption_title.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<shape android:shape="rectangle"
+ android:tintMode="multiply"
+ android:tint="@color/decor_caption_title_color"
+ xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="?android:attr/colorPrimary" />
+</shape>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
new file mode 100644
index 000000000000..f2f1a1d55dee
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_close_button_dark.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ android:tint="@color/decor_button_dark_color"
+ >
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M6.9,4.0l-2.9,2.9 9.1,9.1 -9.1,9.200001 2.9,2.799999 9.1,-9.1 9.1,9.1 2.9,-2.799999 -9.1,-9.200001 9.1,-9.1 -2.9,-2.9 -9.1,9.2z"/>
+ </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
new file mode 100644
index 000000000000..ab4e29ac97e5
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="32.0dp"
+ android:height="32.0dp"
+ android:viewportWidth="32.0"
+ android:viewportHeight="32.0"
+ android:tint="@color/decor_button_dark_color">
+ <group android:scaleX="0.5"
+ android:scaleY="0.5"
+ android:translateX="8.0"
+ android:translateY="8.0" >
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+ </group>
+</vector>
+
+
diff --git a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
index cb516cdbe49b..df5985c605d1 100644
--- a/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
+++ b/libs/WindowManager/Shell/res/layout/bubble_overflow_container.xml
@@ -30,7 +30,8 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_horizontal"
- android:gravity="center"/>
+ android:gravity="center"
+ android:clipChildren="false"/>
<LinearLayout
android:id="@+id/bubble_overflow_empty_state"
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
new file mode 100644
index 000000000000..a112f1933dd1
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/caption"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="end"
+ android:background="@drawable/decor_caption_title">
+ <Button
+ android:id="@+id/maximize_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/maximize_button_text"
+ android:background="@drawable/decor_maximize_button_dark"
+ android:duplicateParentState="true"/>
+ <Button
+ android:id="@+id/close_window"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_margin="5dp"
+ android:padding="4dp"
+ android:layout_gravity="center_vertical|end"
+ android:contentDescription="@string/close_button_text"
+ android:background="@drawable/decor_close_button_dark"
+ android:duplicateParentState="true"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
+
diff --git a/libs/WindowManager/Shell/res/values/attrs.xml b/libs/WindowManager/Shell/res/values/attrs.xml
index 4aaeef8afcb0..2aad4c1c1805 100644
--- a/libs/WindowManager/Shell/res/values/attrs.xml
+++ b/libs/WindowManager/Shell/res/values/attrs.xml
@@ -19,4 +19,8 @@
<attr name="icon" format="reference" />
<attr name="text" format="string" />
</declare-styleable>
+
+ <declare-styleable name="MessageState">
+ <attr name="state_task_focused" format="boolean"/>
+ </declare-styleable>
</resources>
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index a24311fb1f21..68a08513e7f5 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -157,7 +157,7 @@
<string name="accessibility_bubble_dismissed">Bubble dismissed.</string>
<!-- Description of the restart button in the hint of size compatibility mode. [CHAR LIMIT=NONE] -->
- <string name="restart_button_description">Tap to restart this app and go full screen.</string>
+ <string name="restart_button_description">Tap to restart this app for a better view.</string>
<!-- Description of the camera compat button for applying stretched issues treatment in the hint for
compatibility control. [CHAR LIMIT=NONE] -->
@@ -186,4 +186,12 @@
<!-- Button text for dismissing the letterbox education dialog. [CHAR LIMIT=20] -->
<string name="letterbox_education_got_it">Got it</string>
+ <!-- Accessibility description of the letterbox education toast expand to dialog button. [CHAR LIMIT=NONE] -->
+ <string name="letterbox_education_expand_button_description">Expand for more information.</string>
+
+ <!-- Freeform window caption strings -->
+ <!-- Accessibility text for the maximize window button [CHAR LIMIT=NONE] -->
+ <string name="maximize_button_text">Maximize</string>
+ <!-- Accessibility text for the close window button [CHAR LIMIT=NONE] -->
+ <string name="close_button_text">Close</string>
</resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
index 14ba9df93f24..764e650a807c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/RootDisplayAreaOrganizer.java
@@ -85,6 +85,8 @@ public class RootDisplayAreaOrganizer extends DisplayAreaOrganizer {
}
mDisplayAreasInfo.remove(displayId);
+ mLeashes.get(displayId).release();
+ mLeashes.remove(displayId);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
index 31f0ef0192ae..7d7c59eb17da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellTaskOrganizer.java
@@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.IntDef;
import android.annotation.NonNull;
@@ -55,6 +56,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -179,33 +181,41 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
private final Optional<RecentTasksController> mRecentTasks;
@Nullable
+ private final UnfoldAnimationController mUnfoldAnimationController;
+
+ @Nullable
private RunningTaskInfo mLastFocusedTaskInfo;
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context) {
this(null /* taskOrganizerController */, mainExecutor, context, null /* compatUI */,
+ Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
CompatUIController compatUI) {
this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
+ Optional.empty() /* unfoldAnimationController */,
Optional.empty() /* recentTasksController */);
}
public ShellTaskOrganizer(ShellExecutor mainExecutor, Context context, @Nullable
CompatUIController compatUI,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
this(null /* taskOrganizerController */, mainExecutor, context, compatUI,
- recentTasks);
+ unfoldAnimationController, recentTasks);
}
@VisibleForTesting
protected ShellTaskOrganizer(ITaskOrganizerController taskOrganizerController,
ShellExecutor mainExecutor, Context context, @Nullable CompatUIController compatUI,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
super(taskOrganizerController, mainExecutor);
mCompatUI = compatUI;
mRecentTasks = recentTasks;
+ mUnfoldAnimationController = unfoldAnimationController.orElse(null);
if (compatUI != null) {
compatUI.setCompatUICallback(this);
}
@@ -437,6 +447,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
if (listener != null) {
listener.onTaskAppeared(info.getTaskInfo(), info.getLeash());
}
+ if (mUnfoldAnimationController != null) {
+ mUnfoldAnimationController.onTaskAppeared(info.getTaskInfo(), info.getLeash());
+ }
notifyLocusVisibilityIfNeeded(info.getTaskInfo());
notifyCompatUI(info.getTaskInfo(), listener);
}
@@ -458,6 +471,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task info changed taskId=%d", taskInfo.taskId);
+
+ if (mUnfoldAnimationController != null) {
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+ }
+
final TaskAppearedInfo data = mTasks.get(taskInfo.taskId);
final TaskListener oldListener = getTaskListener(data.getTaskInfo());
final TaskListener newListener = getTaskListener(taskInfo);
@@ -482,7 +500,9 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
|| (taskInfo.topActivityType == WindowConfiguration.ACTIVITY_TYPE_HOME
&& taskInfo.isVisible);
final boolean focusTaskChanged = (mLastFocusedTaskInfo == null
- || mLastFocusedTaskInfo.taskId != taskInfo.taskId) && isFocusedOrHome;
+ || mLastFocusedTaskInfo.taskId != taskInfo.taskId
+ || mLastFocusedTaskInfo.getWindowingMode() != taskInfo.getWindowingMode())
+ && isFocusedOrHome;
if (focusTaskChanged) {
for (int i = 0; i < mFocusListeners.size(); i++) {
mFocusListeners.valueAt(i).onFocusTaskChanged(taskInfo);
@@ -507,8 +527,13 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
public void onTaskVanished(RunningTaskInfo taskInfo) {
synchronized (mLock) {
ProtoLog.v(WM_SHELL_TASK_ORG, "Task vanished taskId=%d", taskInfo.taskId);
+ if (mUnfoldAnimationController != null) {
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+ }
+
final int taskId = taskInfo.taskId;
- final TaskListener listener = getTaskListener(mTasks.get(taskId).getTaskInfo());
+ final TaskAppearedInfo appearedInfo = mTasks.get(taskId);
+ final TaskListener listener = getTaskListener(appearedInfo.getTaskInfo());
mTasks.remove(taskId);
if (listener != null) {
listener.onTaskVanished(taskInfo);
@@ -518,6 +543,11 @@ public class ShellTaskOrganizer extends TaskOrganizer implements
notifyCompatUI(taskInfo, null /* taskListener */);
// Notify the recent tasks that a task has been removed
mRecentTasks.ifPresent(recentTasks -> recentTasks.onTaskRemoved(taskInfo));
+
+ if (!ENABLE_SHELL_TRANSITIONS && (appearedInfo.getLeash() != null)) {
+ // Preemptively clean up the leash only if shell transitions are not enabled
+ appearedInfo.getLeash().release();
+ }
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
new file mode 100644
index 000000000000..82b0270698e4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/activityembedding/ActivityEmbeddingController.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.activityembedding;
+
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+
+import android.content.Context;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.transition.Transitions;
+
+/**
+ * Responsible for handling ActivityEmbedding related transitions.
+ */
+public class ActivityEmbeddingController implements Transitions.TransitionHandler {
+
+ private final Context mContext;
+ private final Transitions mTransitions;
+
+ public ActivityEmbeddingController(Context context, Transitions transitions) {
+ mContext = context;
+ mTransitions = transitions;
+ }
+
+ /** Registers to handle transitions. */
+ public void init() {
+ mTransitions.addHandler(this);
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ // TODO(b/207070762) Handle AE animation as a part of other transitions.
+ // Only handle the transition if all containers are embedded.
+ for (TransitionInfo.Change change : info.getChanges()) {
+ if (!isEmbedded(change)) {
+ return false;
+ }
+ }
+
+ // TODO(b/207070762) Implement AE animation.
+ startTransaction.apply();
+ finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ return null;
+ }
+
+ private static boolean isEmbedded(@NonNull TransitionInfo.Change change) {
+ return (change.getFlags() & FLAG_IS_EMBEDDED) != 0;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
index 2aead9392e59..a0dde6ad168d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
@@ -53,6 +53,19 @@ public class Interpolators {
public static final Interpolator LINEAR_OUT_SLOW_IN = new PathInterpolator(0f, 0f, 0.2f, 1f);
/**
+ * The accelerated emphasized interpolator. Used for hero / emphasized movement of content that
+ * is disappearing e.g. when moving off screen.
+ */
+ public static final Interpolator EMPHASIZED_ACCELERATE = new PathInterpolator(
+ 0.3f, 0f, 0.8f, 0.15f);
+
+ /**
+ * The decelerating emphasized interpolator. Used for hero / emphasized movement of content that
+ * is appearing e.g. when coming from off screen
+ */
+ public static final Interpolator EMPHASIZED_DECELERATE = new PathInterpolator(
+ 0.05f, 0.7f, 0.1f, 1f);
+ /**
* Interpolator to be used when animating a move based on a click. Pair with enough duration.
*/
public static final Interpolator TOUCH_RESPONSE = new PathInterpolator(0.3f, 0f, 0.1f, 1f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
index b483fe03e80f..312af4ff7bc2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/PhysicsAnimator.kt
@@ -829,8 +829,12 @@ class PhysicsAnimator<T> private constructor (target: T) {
/** Cancels all in progress animations on all properties. */
fun cancel() {
- cancelAction(flingAnimations.keys)
- cancelAction(springAnimations.keys)
+ if (flingAnimations.size > 0) {
+ cancelAction(flingAnimations.keys)
+ }
+ if (springAnimations.size > 0) {
+ cancelAction(springAnimations.keys)
+ }
}
/** Cancels in progress animations on the provided properties only. */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
deleted file mode 100644
index 3f0b01bef0ce..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPair.java
+++ /dev/null
@@ -1,357 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import android.app.ActivityManager;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.protolog.common.ProtoLog;
-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.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitWindowManager;
-
-import java.io.PrintWriter;
-
-/**
- * An app-pairs consisting of {@link #mRootTaskInfo} that acts as the hierarchy parent of
- * {@link #mTaskInfo1} and {@link #mTaskInfo2} in the pair.
- * Also includes all UI for managing the pair like the divider.
- */
-class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayoutHandler {
- private static final String TAG = AppPair.class.getSimpleName();
-
- private ActivityManager.RunningTaskInfo mRootTaskInfo;
- private SurfaceControl mRootTaskLeash;
- private ActivityManager.RunningTaskInfo mTaskInfo1;
- private SurfaceControl mTaskLeash1;
- private ActivityManager.RunningTaskInfo mTaskInfo2;
- private SurfaceControl mTaskLeash2;
- private SurfaceControl mDimLayer1;
- private SurfaceControl mDimLayer2;
- private final SurfaceSession mSurfaceSession = new SurfaceSession();
-
- private final AppPairsController mController;
- private final SyncTransactionQueue mSyncQueue;
- private final DisplayController mDisplayController;
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private SplitLayout mSplitLayout;
-
- private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
- new SplitWindowManager.ParentContainerCallbacks() {
- @Override
- public void attachToParentSurface(SurfaceControl.Builder b) {
- b.setParent(mRootTaskLeash);
- }
-
- @Override
- public void onLeashReady(SurfaceControl leash) {
- mSyncQueue.runInSync(t -> t
- .show(leash)
- .setLayer(leash, Integer.MAX_VALUE)
- .setPosition(leash,
- mSplitLayout.getDividerBounds().left,
- mSplitLayout.getDividerBounds().top));
- }
- };
-
- AppPair(AppPairsController controller) {
- mController = controller;
- mSyncQueue = controller.getSyncTransactionQueue();
- mDisplayController = controller.getDisplayController();
- mDisplayImeController = controller.getDisplayImeController();
- mDisplayInsetsController = controller.getDisplayInsetsController();
- }
-
- int getRootTaskId() {
- return mRootTaskInfo != null ? mRootTaskInfo.taskId : INVALID_TASK_ID;
- }
-
- private int getTaskId1() {
- return mTaskInfo1 != null ? mTaskInfo1.taskId : INVALID_TASK_ID;
- }
-
- private int getTaskId2() {
- return mTaskInfo2 != null ? mTaskInfo2.taskId : INVALID_TASK_ID;
- }
-
- boolean contains(int taskId) {
- return taskId == getRootTaskId() || taskId == getTaskId1() || taskId == getTaskId2();
- }
-
- boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2) {
- ProtoLog.v(WM_SHELL_TASK_ORG, "pair task1=%d task2=%d in AppPair=%s",
- task1.taskId, task2.taskId, this);
-
- if (!task1.supportsMultiWindow || !task2.supportsMultiWindow) {
- ProtoLog.e(WM_SHELL_TASK_ORG,
- "Can't pair tasks that doesn't support multi window, "
- + "task1.supportsMultiWindow=%b, task2.supportsMultiWindow=%b",
- task1.supportsMultiWindow, task2.supportsMultiWindow);
- return false;
- }
-
- mTaskInfo1 = task1;
- mTaskInfo2 = task2;
- mSplitLayout = new SplitLayout(TAG + "SplitDivider",
- mDisplayController.getDisplayContext(mRootTaskInfo.displayId),
- mRootTaskInfo.configuration, this /* layoutChangeListener */,
- mParentContainerCallbacks, mDisplayImeController, mController.getTaskOrganizer(),
- SplitLayout.PARALLAX_DISMISSING);
- mDisplayInsetsController.addInsetsChangedListener(mRootTaskInfo.displayId, mSplitLayout);
-
- final WindowContainerToken token1 = task1.token;
- final WindowContainerToken token2 = task2.token;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- wct.setHidden(mRootTaskInfo.token, false)
- .reparent(token1, mRootTaskInfo.token, true /* onTop */)
- .reparent(token2, mRootTaskInfo.token, true /* onTop */)
- .setWindowingMode(token1, WINDOWING_MODE_MULTI_WINDOW)
- .setWindowingMode(token2, WINDOWING_MODE_MULTI_WINDOW)
- .setBounds(token1, mSplitLayout.getBounds1())
- .setBounds(token2, mSplitLayout.getBounds2())
- // Moving the root task to top after the child tasks were repareted , or the root
- // task cannot be visible and focused.
- .reorder(mRootTaskInfo.token, true);
- mController.getTaskOrganizer().applyTransaction(wct);
- return true;
- }
-
- void unpair() {
- unpair(null /* toTopToken */);
- }
-
- private void unpair(@Nullable WindowContainerToken toTopToken) {
- final WindowContainerToken token1 = mTaskInfo1.token;
- final WindowContainerToken token2 = mTaskInfo2.token;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- // Reparent out of this container and reset windowing mode.
- wct.setHidden(mRootTaskInfo.token, true)
- .reorder(mRootTaskInfo.token, false)
- .reparent(token1, null, token1 == toTopToken /* onTop */)
- .reparent(token2, null, token2 == toTopToken /* onTop */)
- .setWindowingMode(token1, WINDOWING_MODE_UNDEFINED)
- .setWindowingMode(token2, WINDOWING_MODE_UNDEFINED);
- mController.getTaskOrganizer().applyTransaction(wct);
-
- mTaskInfo1 = null;
- mTaskInfo2 = null;
- mSplitLayout.release();
- mSplitLayout = null;
- }
-
- @Override
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mRootTaskInfo == null || taskInfo.taskId == mRootTaskInfo.taskId) {
- mRootTaskInfo = taskInfo;
- mRootTaskLeash = leash;
- } else if (taskInfo.taskId == getTaskId1()) {
- mTaskInfo1 = taskInfo;
- mTaskLeash1 = leash;
- mSyncQueue.runInSync(t -> mDimLayer1 =
- SurfaceUtils.makeDimLayer(t, mTaskLeash1, "Dim layer", mSurfaceSession));
- } else if (taskInfo.taskId == getTaskId2()) {
- mTaskInfo2 = taskInfo;
- mTaskLeash2 = leash;
- mSyncQueue.runInSync(t -> mDimLayer2 =
- SurfaceUtils.makeDimLayer(t, mTaskLeash2, "Dim layer", mSurfaceSession));
- } else {
- throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
- }
-
- if (mTaskLeash1 == null || mTaskLeash2 == null) return;
-
- mSplitLayout.init();
-
- mSyncQueue.runInSync(t -> t
- .show(mRootTaskLeash)
- .show(mTaskLeash1)
- .show(mTaskLeash2)
- .setPosition(mTaskLeash1,
- mTaskInfo1.positionInParent.x,
- mTaskInfo1.positionInParent.y)
- .setPosition(mTaskLeash2,
- mTaskInfo2.positionInParent.x,
- mTaskInfo2.positionInParent.y));
- }
-
- @Override
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.supportsMultiWindow) {
- // Dismiss AppPair if the task no longer supports multi window.
- mController.unpair(mRootTaskInfo.taskId);
- return;
- }
- if (taskInfo.taskId == getRootTaskId()) {
- if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
- mSyncQueue.runInSync(t -> {
- if (taskInfo.isVisible) {
- t.show(mRootTaskLeash);
- } else {
- t.hide(mRootTaskLeash);
- }
- });
- }
- mRootTaskInfo = taskInfo;
-
- if (mSplitLayout != null
- && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
- onLayoutSizeChanged(mSplitLayout);
- }
- } else if (taskInfo.taskId == getTaskId1()) {
- mTaskInfo1 = taskInfo;
- } else if (taskInfo.taskId == getTaskId2()) {
- mTaskInfo2 = taskInfo;
- } else {
- throw new IllegalStateException("Unknown task=" + taskInfo.taskId);
- }
- }
-
- @Override
- public int getSplitItemPosition(WindowContainerToken token) {
- if (token == null) {
- return SPLIT_POSITION_UNDEFINED;
- }
-
- if (token.equals(mTaskInfo1.getToken())) {
- return SPLIT_POSITION_TOP_OR_LEFT;
- } else if (token.equals(mTaskInfo2.getToken())) {
- return SPLIT_POSITION_BOTTOM_OR_RIGHT;
- }
-
- return SPLIT_POSITION_UNDEFINED;
- }
-
- @Override
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (taskInfo.taskId == getRootTaskId()) {
- // We don't want to release this object back to the pool since the root task went away.
- mController.unpair(mRootTaskInfo.taskId, false /* releaseToPool */);
- } else if (taskInfo.taskId == getTaskId1()) {
- mController.unpair(mRootTaskInfo.taskId);
- mSyncQueue.runInSync(t -> t.remove(mDimLayer1));
- } else if (taskInfo.taskId == getTaskId2()) {
- mController.unpair(mRootTaskInfo.taskId);
- mSyncQueue.runInSync(t -> t.remove(mDimLayer2));
- }
- }
-
- @Override
- public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- b.setParent(findTaskSurface(taskId));
- }
-
- @Override
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- t.reparent(sc, findTaskSurface(taskId));
- }
-
- private SurfaceControl findTaskSurface(int taskId) {
- if (getRootTaskId() == taskId) {
- return mRootTaskLeash;
- } else if (getTaskId1() == taskId) {
- return mTaskLeash1;
- } else if (getTaskId2() == taskId) {
- return mTaskLeash2;
- } else {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- if (mRootTaskInfo != null) {
- pw.println(innerPrefix + "Root taskId=" + mRootTaskInfo.taskId
- + " winMode=" + mRootTaskInfo.getWindowingMode());
- }
- if (mTaskInfo1 != null) {
- pw.println(innerPrefix + "1 taskId=" + mTaskInfo1.taskId
- + " winMode=" + mTaskInfo1.getWindowingMode());
- }
- if (mTaskInfo2 != null) {
- pw.println(innerPrefix + "2 taskId=" + mTaskInfo2.taskId
- + " winMode=" + mTaskInfo2.getWindowingMode());
- }
- }
-
- @Override
- public String toString() {
- return TAG + "#" + getRootTaskId();
- }
-
- @Override
- public void onSnappedToDismiss(boolean snappedToEnd) {
- unpair(snappedToEnd ? mTaskInfo1.token : mTaskInfo2.token /* toTopToken */);
- }
-
- @Override
- public void onLayoutPositionChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t ->
- layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2,
- true /* applyResizingOffset */));
- }
-
- @Override
- public void onLayoutSizeChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t ->
- layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2,
- true /* applyResizingOffset */));
- }
-
- @Override
- public void onLayoutSizeChanged(SplitLayout layout) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyTaskChanges(wct, mTaskInfo1, mTaskInfo2);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- layout.applySurfaceChanges(t, mTaskLeash1, mTaskLeash2, mDimLayer1, mDimLayer2,
- false /* applyResizingOffset */));
- }
-
- @Override
- public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, mTaskInfo1, mTaskInfo2);
- mController.getTaskOrganizer().applyTransaction(wct);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
deleted file mode 100644
index a9b1dbc3c23b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairs.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import android.app.ActivityManager;
-
-import androidx.annotation.NonNull;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
-
-/**
- * Interface to engage app pairs feature.
- */
-@ExternalThread
-public interface AppPairs {
- /** Pairs indicated tasks. */
- boolean pair(int task1, int task2);
- /** Pairs indicated tasks. */
- boolean pair(ActivityManager.RunningTaskInfo task1, ActivityManager.RunningTaskInfo task2);
- /** Unpairs any app-pair containing this task id. */
- void unpair(int taskId);
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
deleted file mode 100644
index 53234ab971d6..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsController.java
+++ /dev/null
@@ -1,213 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import android.app.ActivityManager;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
-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.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import java.io.PrintWriter;
-
-/**
- * Class manages app-pairs multitasking mode and implements the main interface {@link AppPairs}.
- */
-public class AppPairsController {
- private static final String TAG = AppPairsController.class.getSimpleName();
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final SyncTransactionQueue mSyncQueue;
- private final ShellExecutor mMainExecutor;
- private final AppPairsImpl mImpl = new AppPairsImpl();
-
- private AppPairsPool mPairsPool;
- // Active app-pairs mapped by root task id key.
- private final SparseArray<AppPair> mActiveAppPairs = new SparseArray<>();
- private final DisplayController mDisplayController;
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
-
- public AppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController, ShellExecutor mainExecutor,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController) {
- mTaskOrganizer = organizer;
- mSyncQueue = syncQueue;
- mDisplayController = displayController;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mMainExecutor = mainExecutor;
- }
-
- public AppPairs asAppPairs() {
- return mImpl;
- }
-
- public void onOrganizerRegistered() {
- if (mPairsPool == null) {
- setPairsPool(new AppPairsPool(this));
- }
- }
-
- @VisibleForTesting
- public void setPairsPool(AppPairsPool pool) {
- mPairsPool = pool;
- }
-
- public boolean pair(int taskId1, int taskId2) {
- final ActivityManager.RunningTaskInfo task1 = mTaskOrganizer.getRunningTaskInfo(taskId1);
- final ActivityManager.RunningTaskInfo task2 = mTaskOrganizer.getRunningTaskInfo(taskId2);
- if (task1 == null || task2 == null) {
- return false;
- }
- return pair(task1, task2);
- }
-
- public boolean pair(ActivityManager.RunningTaskInfo task1,
- ActivityManager.RunningTaskInfo task2) {
- return pairInner(task1, task2) != null;
- }
-
- @VisibleForTesting
- public AppPair pairInner(
- @NonNull ActivityManager.RunningTaskInfo task1,
- @NonNull ActivityManager.RunningTaskInfo task2) {
- final AppPair pair = mPairsPool.acquire();
- if (!pair.pair(task1, task2)) {
- mPairsPool.release(pair);
- return null;
- }
-
- mActiveAppPairs.put(pair.getRootTaskId(), pair);
- return pair;
- }
-
- public void unpair(int taskId) {
- unpair(taskId, true /* releaseToPool */);
- }
-
- public void unpair(int taskId, boolean releaseToPool) {
- AppPair pair = mActiveAppPairs.get(taskId);
- if (pair == null) {
- for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
- final AppPair candidate = mActiveAppPairs.valueAt(i);
- if (candidate.contains(taskId)) {
- pair = candidate;
- break;
- }
- }
- }
- if (pair == null) {
- ProtoLog.v(WM_SHELL_TASK_ORG, "taskId %d isn't isn't in an app-pair.", taskId);
- return;
- }
-
- ProtoLog.v(WM_SHELL_TASK_ORG, "unpair taskId=%d pair=%s", taskId, pair);
- mActiveAppPairs.remove(pair.getRootTaskId());
- pair.unpair();
- if (releaseToPool) {
- mPairsPool.release(pair);
- }
- }
-
- ShellTaskOrganizer getTaskOrganizer() {
- return mTaskOrganizer;
- }
-
- SyncTransactionQueue getSyncTransactionQueue() {
- return mSyncQueue;
- }
-
- DisplayController getDisplayController() {
- return mDisplayController;
- }
-
- DisplayImeController getDisplayImeController() {
- return mDisplayImeController;
- }
-
- DisplayInsetsController getDisplayInsetsController() {
- return mDisplayInsetsController;
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
-
- for (int i = mActiveAppPairs.size() - 1; i >= 0; --i) {
- mActiveAppPairs.valueAt(i).dump(pw, childPrefix);
- }
-
- if (mPairsPool != null) {
- mPairsPool.dump(pw, prefix);
- }
- }
-
- @Override
- public String toString() {
- return TAG + "#" + mActiveAppPairs.size();
- }
-
- private class AppPairsImpl implements AppPairs {
- @Override
- public boolean pair(int task1, int task2) {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = AppPairsController.this.pair(task1, task2);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to pair tasks: " + task1 + ", " + task2);
- }
- return result[0];
- }
-
- @Override
- public boolean pair(ActivityManager.RunningTaskInfo task1,
- ActivityManager.RunningTaskInfo task2) {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = AppPairsController.this.pair(task1, task2);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to pair tasks: " + task1 + ", " + task2);
- }
- return result[0];
- }
-
- @Override
- public void unpair(int taskId) {
- mMainExecutor.execute(() -> {
- AppPairsController.this.unpair(taskId);
- });
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java
deleted file mode 100644
index 5c6037ea0702..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/AppPairsPool.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.protolog.common.ProtoLog;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-/**
- * Class that manager pool of {@link AppPair} objects. Helps reduce the need to call system_server
- * to create a root task for the app-pair when needed since we always have one ready to go.
- */
-class AppPairsPool {
- private static final String TAG = AppPairsPool.class.getSimpleName();
-
- @VisibleForTesting
- final AppPairsController mController;
- // The pool
- private final ArrayList<AppPair> mPool = new ArrayList();
-
- AppPairsPool(AppPairsController controller) {
- mController = controller;
- incrementPool();
- }
-
- AppPair acquire() {
- final AppPair entry = mPool.remove(mPool.size() - 1);
- ProtoLog.v(WM_SHELL_TASK_ORG, "acquire entry.taskId=%s listener=%s size=%d",
- entry.getRootTaskId(), entry, mPool.size());
- if (mPool.size() == 0) {
- incrementPool();
- }
- return entry;
- }
-
- void release(AppPair entry) {
- mPool.add(entry);
- ProtoLog.v(WM_SHELL_TASK_ORG, "release entry.taskId=%s listener=%s size=%d",
- entry.getRootTaskId(), entry, mPool.size());
- }
-
- @VisibleForTesting
- void incrementPool() {
- ProtoLog.v(WM_SHELL_TASK_ORG, "incrementPool size=%d", mPool.size());
- final AppPair entry = new AppPair(mController);
- // TODO: multi-display...
- mController.getTaskOrganizer().createRootTask(
- DEFAULT_DISPLAY, WINDOWING_MODE_FULLSCREEN, entry);
- mPool.add(entry);
- }
-
- @VisibleForTesting
- int poolSize() {
- return mPool.size();
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- for (int i = mPool.size() - 1; i >= 0; --i) {
- mPool.get(i).dump(pw, childPrefix);
- }
- }
-
- @Override
- public String toString() {
- return TAG + "#" + mPool.size();
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS
deleted file mode 100644
index 4d9b520e3f0e..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/apppairs/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# WM shell sub-modules apppair owner
-chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
index 8c0affb0a432..86f9d5b534f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimation.java
@@ -29,6 +29,13 @@ import com.android.wm.shell.common.annotations.ExternalThread;
public interface BackAnimation {
/**
+ * Returns a binder that can be passed to an external process to update back animations.
+ */
+ default IBackAnimation createExternalInterface() {
+ return null;
+ }
+
+ /**
* Called when a {@link MotionEvent} is generated by a back gesture.
*
* @param touchX the X touch position of the {@link MotionEvent}.
@@ -47,13 +54,6 @@ public interface BackAnimation {
void setTriggerBack(boolean triggerBack);
/**
- * Returns a binder that can be passed to an external process to update back animations.
- */
- default IBackAnimation createExternalInterface() {
- return null;
- }
-
- /**
* Sets the threshold values that defining edge swipe behavior.
* @param triggerThreshold the min threshold to trigger back.
* @param progressThreshold the max threshold to keep progressing back animation.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index 0cf2b28921e1..d53a98cf5b07 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -403,7 +403,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private boolean shouldDispatchToLauncher(int backType) {
return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
&& mBackToLauncherCallback != null
- && mEnableAnimations.get();
+ && mEnableAnimations.get()
+ && mBackNavigationInfo != null
+ && mBackNavigationInfo.getDepartingAnimationTarget() != null;
}
private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
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 f427a2c4bc95..d7f1292cb717 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
@@ -25,6 +25,7 @@ import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.wm.shell.bubbles.BubblePositioner.TASKBAR_POSITION_BOTTOM;
@@ -79,18 +80,15 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
import android.view.WindowManager;
-import android.window.WindowContainerTransaction;
import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.UiEventLogger;
import com.android.internal.statusbar.IStatusBarService;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.common.DisplayChangeController;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.FloatingContentCoordinator;
import com.android.wm.shell.common.ShellExecutor;
@@ -103,6 +101,8 @@ import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.PinnedStackListenerForwarder;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -121,7 +121,7 @@ import java.util.function.IntConsumer;
*
* The controller manages addition, removal, and visible state of bubbles on screen.
*/
-public class BubbleController {
+public class BubbleController implements ConfigurationChangeListener {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
@@ -157,6 +157,7 @@ public class BubbleController {
private final DisplayController mDisplayController;
private final TaskViewTransitions mTaskViewTransitions;
private final SyncTransactionQueue mSyncQueue;
+ private final ShellController mShellController;
// Used to post to main UI thread
private final ShellExecutor mMainExecutor;
@@ -224,44 +225,9 @@ public class BubbleController {
/** Drag and drop controller to register listener for onDragStarted. */
private DragAndDropController mDragAndDropController;
- /**
- * Creates an instance of the BubbleController.
- */
- public static BubbleController create(Context context,
- @Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
- FloatingContentCoordinator floatingContentCoordinator,
- @Nullable IStatusBarService statusBarService,
- WindowManager windowManager,
- WindowManagerShellWrapper windowManagerShellWrapper,
- UserManager userManager,
- LauncherApps launcherApps,
- TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
- ShellTaskOrganizer organizer,
- DisplayController displayController,
- Optional<OneHandedController> oneHandedOptional,
- DragAndDropController dragAndDropController,
- @ShellMainThread ShellExecutor mainExecutor,
- @ShellMainThread Handler mainHandler,
- @ShellBackgroundThread ShellExecutor bgExecutor,
- TaskViewTransitions taskViewTransitions,
- SyncTransactionQueue syncQueue) {
- BubbleLogger logger = new BubbleLogger(uiEventLogger);
- BubblePositioner positioner = new BubblePositioner(context, windowManager);
- BubbleData data = new BubbleData(context, logger, positioner, mainExecutor);
- return new BubbleController(context, data, synchronizer, floatingContentCoordinator,
- new BubbleDataRepository(context, launcherApps, mainExecutor),
- statusBarService, windowManager, windowManagerShellWrapper, userManager,
- launcherApps, logger, taskStackListener, organizer, positioner, displayController,
- oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
- taskViewTransitions, syncQueue);
- }
-
- /**
- * Testing constructor.
- */
- @VisibleForTesting
- protected BubbleController(Context context,
+
+ public BubbleController(Context context,
+ ShellController shellController,
BubbleData data,
@Nullable BubbleStackView.SurfaceSynchronizer synchronizer,
FloatingContentCoordinator floatingContentCoordinator,
@@ -284,6 +250,7 @@ public class BubbleController {
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
mContext = context;
+ mShellController = shellController;
mLauncherApps = launcherApps;
mBarService = statusBarService == null
? IStatusBarService.Stub.asInterface(
@@ -435,17 +402,13 @@ public class BubbleController {
});
mDisplayController.addDisplayChangingController(
- new DisplayChangeController.OnDisplayChangingListener() {
- @Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction t) {
- // This is triggered right before the rotation is applied
- if (fromRotation != toRotation) {
- if (mStackView != null) {
- // Layout listener set on stackView will update the positioner
- // once the rotation is applied
- mStackView.onOrientationChanged();
- }
+ (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
+ // This is triggered right before the rotation is applied
+ if (fromRotation != toRotation) {
+ if (mStackView != null) {
+ // Layout listener set on stackView will update the positioner
+ // once the rotation is applied
+ mStackView.onOrientationChanged();
}
}
});
@@ -456,6 +419,8 @@ public class BubbleController {
// Clear out any persisted bubbles on disk that no longer have a valid user.
List<UserInfo> users = mUserManager.getAliveUsers();
mDataRepository.sanitizeBubbles(users);
+
+ mShellController.addConfigurationChangeListener(this);
}
@VisibleForTesting
@@ -840,7 +805,8 @@ public class BubbleController {
mSavedBubbleKeysPerUser.remove(userId);
}
- private void updateForThemeChanges() {
+ @Override
+ public void onThemeChanged() {
if (mStackView != null) {
mStackView.onThemeChanged();
}
@@ -860,7 +826,8 @@ public class BubbleController {
}
}
- private void onConfigChanged(Configuration newConfig) {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
if (mBubblePositioner != null) {
mBubblePositioner.update();
}
@@ -885,6 +852,19 @@ public class BubbleController {
}
}
+ private void onNotificationPanelExpandedChanged(boolean expanded) {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "onNotificationPanelExpandedChanged: expanded=" + expanded);
+ }
+ if (mStackView != null && mStackView.isExpanded()) {
+ if (expanded) {
+ mStackView.stopMonitoringSwipeUpGesture();
+ } else {
+ mStackView.startMonitoringSwipeUpGesture();
+ }
+ }
+ }
+
private void setSysuiProxy(Bubbles.SysuiProxy proxy) {
mSysuiProxy = proxy;
}
@@ -1342,14 +1322,18 @@ public class BubbleController {
mStackView.setBubbleSuppressed(update.unsuppressedBubble, false);
}
+ boolean collapseStack = update.expandedChanged && !update.expanded;
+
// At this point, the correct bubbles are inflated in the stack.
// Make sure the order in bubble data is reflected in bubble row.
if (update.orderChanged && mStackView != null) {
mDataRepository.addBubbles(mCurrentUserId, update.bubbles);
- mStackView.updateBubbleOrder(update.bubbles);
+ // if the stack is going to be collapsed, do not update pointer position
+ // after reordering
+ mStackView.updateBubbleOrder(update.bubbles, !collapseStack);
}
- if (update.expandedChanged && !update.expanded) {
+ if (collapseStack) {
mStackView.setExpanded(false);
mSysuiProxy.requestNotificationShadeTopUi(false, TAG);
}
@@ -1468,6 +1452,18 @@ public class BubbleController {
}
/**
+ * Check if notification panel is in an expanded state.
+ * Makes a call to System UI process and delivers the result via {@code callback} on the
+ * WM Shell main thread.
+ *
+ * @param callback callback that has the result of notification panel expanded state
+ */
+ public void isNotificationPanelExpanded(Consumer<Boolean> callback) {
+ mSysuiProxy.isNotificationPanelExpand(expanded ->
+ mMainExecutor.execute(() -> callback.accept(expanded)));
+ }
+
+ /**
* Description of current bubble state.
*/
private void dump(PrintWriter pw, String[] args) {
@@ -1546,7 +1542,7 @@ public class BubbleController {
public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
mBubblePositioner.setImeVisible(imeVisible, imeHeight);
if (mStackView != null) {
- mStackView.animateForIme(imeVisible);
+ mStackView.setImeVisible(imeVisible);
}
}
}
@@ -1691,13 +1687,6 @@ public class BubbleController {
}
@Override
- public void updateForThemeChanges() {
- mMainExecutor.execute(() -> {
- BubbleController.this.updateForThemeChanges();
- });
- }
-
- @Override
public void expandStackAndSelectBubble(BubbleEntry entry) {
mMainExecutor.execute(() -> {
BubbleController.this.expandStackAndSelectBubble(entry);
@@ -1836,10 +1825,9 @@ public class BubbleController {
}
@Override
- public void onConfigChanged(Configuration newConfig) {
- mMainExecutor.execute(() -> {
- BubbleController.this.onConfigChanged(newConfig);
- });
+ public void onNotificationPanelExpandedChanged(boolean expanded) {
+ mMainExecutor.execute(
+ () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded));
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index dc2ace949f0c..dce6b56261ff 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -46,6 +46,9 @@ public class BubbleDebugConfig {
static final boolean DEBUG_OVERFLOW = false;
static final boolean DEBUG_USER_EDUCATION = false;
static final boolean DEBUG_POSITIONER = false;
+ public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
+ static final boolean DEBUG_BUBBLE_GESTURE = false;
+ public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
private static final boolean FORCE_SHOW_USER_EDUCATION = false;
private static final String FORCE_SHOW_USER_EDUCATION_SETTING =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index b8bf1a8e497e..2666a0e186b3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -43,10 +43,13 @@ import android.graphics.CornerPathEffect;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Picture;
+import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
import android.util.AttributeSet;
+import android.util.FloatProperty;
+import android.util.IntProperty;
import android.util.Log;
import android.util.TypedValue;
import android.view.LayoutInflater;
@@ -75,6 +78,62 @@ import java.io.PrintWriter;
public class BubbleExpandedView extends LinearLayout {
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
+ /** {@link IntProperty} for updating bottom clip */
+ public static final IntProperty<BubbleExpandedView> BOTTOM_CLIP_PROPERTY =
+ new IntProperty<BubbleExpandedView>("bottomClip") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, int value) {
+ expandedView.setBottomClip(value);
+ }
+
+ @Override
+ public Integer get(BubbleExpandedView expandedView) {
+ return expandedView.mBottomClip;
+ }
+ };
+
+ /** {@link FloatProperty} for updating taskView or overflow alpha */
+ public static final FloatProperty<BubbleExpandedView> CONTENT_ALPHA =
+ new FloatProperty<BubbleExpandedView>("contentAlpha") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, float value) {
+ expandedView.setContentAlpha(value);
+ }
+
+ @Override
+ public Float get(BubbleExpandedView expandedView) {
+ return expandedView.getContentAlpha();
+ }
+ };
+
+ /** {@link FloatProperty} for updating background and pointer alpha */
+ public static final FloatProperty<BubbleExpandedView> BACKGROUND_ALPHA =
+ new FloatProperty<BubbleExpandedView>("backgroundAlpha") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, float value) {
+ expandedView.setBackgroundAlpha(value);
+ }
+
+ @Override
+ public Float get(BubbleExpandedView expandedView) {
+ return expandedView.getAlpha();
+ }
+ };
+
+ /** {@link FloatProperty} for updating manage button alpha */
+ public static final FloatProperty<BubbleExpandedView> MANAGE_BUTTON_ALPHA =
+ new FloatProperty<BubbleExpandedView>("manageButtonAlpha") {
+ @Override
+ public void setValue(BubbleExpandedView expandedView, float value) {
+ expandedView.mManageButton.setAlpha(value);
+ }
+
+ @Override
+ public Float get(BubbleExpandedView expandedView) {
+ return expandedView.mManageButton.getAlpha();
+ }
+ };
+
// The triangle pointing to the expanded view
private View mPointerView;
@Nullable private int[] mExpandedViewContainerLocation;
@@ -90,7 +149,7 @@ public class BubbleExpandedView extends LinearLayout {
/**
* Whether we want the {@code TaskView}'s content to be visible (alpha = 1f). If
- * {@link #mIsAlphaAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
+ * {@link #mIsAnimating} is true, this may not reflect the {@code TaskView}'s actual alpha
* value until the animation ends.
*/
private boolean mIsContentVisible = false;
@@ -99,12 +158,13 @@ public class BubbleExpandedView extends LinearLayout {
* Whether we're animating the {@code TaskView}'s alpha value. If so, we will hold off on
* applying alpha changes from {@link #setContentVisibility} until the animation ends.
*/
- private boolean mIsAlphaAnimating = false;
+ private boolean mIsAnimating = false;
private int mPointerWidth;
private int mPointerHeight;
private float mPointerRadius;
private float mPointerOverlap;
+ private final PointF mPointerPos = new PointF();
private CornerPathEffect mPointerEffect;
private ShapeDrawable mCurrentPointer;
private ShapeDrawable mTopPointer;
@@ -113,11 +173,13 @@ public class BubbleExpandedView extends LinearLayout {
private float mCornerRadius = 0f;
private int mBackgroundColorFloating;
private boolean mUsingMaxHeight;
-
+ private int mTopClip = 0;
+ private int mBottomClip = 0;
@Nullable private Bubble mBubble;
private PendingIntent mPendingIntent;
// TODO(b/170891664): Don't use a flag, set the BubbleOverflow object instead
private boolean mIsOverflow;
+ private boolean mIsClipping;
private BubbleController mController;
private BubbleStackView mStackView;
@@ -268,7 +330,8 @@ public class BubbleExpandedView extends LinearLayout {
mExpandedViewContainer.setOutlineProvider(new ViewOutlineProvider() {
@Override
public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), mCornerRadius);
+ Rect clip = new Rect(0, mTopClip, view.getWidth(), view.getHeight() - mBottomClip);
+ outline.setRoundRect(clip, mCornerRadius);
}
});
mExpandedViewContainer.setClipToOutline(true);
@@ -300,9 +363,9 @@ public class BubbleExpandedView extends LinearLayout {
// they should not collapse the stack (which all other touches on areas around the AV
// would do).
if (motionEvent.getRawY() >= avBounds.top
- && motionEvent.getRawY() <= avBounds.bottom
- && (motionEvent.getRawX() < avBounds.left
- || motionEvent.getRawX() > avBounds.right)) {
+ && motionEvent.getRawY() <= avBounds.bottom
+ && (motionEvent.getRawX() < avBounds.left
+ || motionEvent.getRawX() > avBounds.right)) {
return true;
}
@@ -384,7 +447,7 @@ public class BubbleExpandedView extends LinearLayout {
}
void applyThemeAttrs() {
- final TypedArray ta = mContext.obtainStyledAttributes(new int[] {
+ final TypedArray ta = mContext.obtainStyledAttributes(new int[]{
android.R.attr.dialogCornerRadius,
android.R.attr.colorBackgroundFloating});
boolean supportsRoundedCorners = ScreenDecorationsUtils.supportsRoundedCornersOnWindows(
@@ -429,7 +492,7 @@ public class BubbleExpandedView extends LinearLayout {
* ordering surfaces during animations. When content is drawn on top of the app (e.g. bubble
* being dragged out, the manage menu) this is set to false, otherwise it should be true.
*/
- void setSurfaceZOrderedOnTop(boolean onTop) {
+ public void setSurfaceZOrderedOnTop(boolean onTop) {
if (mTaskView == null) {
return;
}
@@ -510,12 +573,12 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Whether we are currently animating the {@code TaskView}'s alpha value. If this is set to
+ * Whether we are currently animating the {@code TaskView}. If this is set to
* true, calls to {@link #setContentVisibility} will not be applied until this is set to false
* again.
*/
- void setAlphaAnimating(boolean animating) {
- mIsAlphaAnimating = animating;
+ public void setAnimating(boolean animating) {
+ mIsAnimating = animating;
// If we're done animating, apply the correct
if (!animating) {
@@ -524,18 +587,139 @@ public class BubbleExpandedView extends LinearLayout {
}
/**
- * Sets the alpha of the underlying {@code TaskView}, since changing the expanded view's alpha
- * does not affect the {@code TaskView} since it uses a Surface.
+ * Get alpha from underlying {@code TaskView} if this view is for a bubble.
+ * Or get alpha for the overflow view if this view is for overflow.
+ *
+ * @return alpha for the content being shown
*/
- void setTaskViewAlpha(float alpha) {
+ public float getContentAlpha() {
+ if (mIsOverflow) {
+ return mOverflowView.getAlpha();
+ }
if (mTaskView != null) {
+ return mTaskView.getAlpha();
+ }
+ return 1f;
+ }
+
+ /**
+ * Set alpha of the underlying {@code TaskView} if this view is for a bubble.
+ * Or set alpha for the overflow view if this view is for overflow.
+ *
+ * Changing expanded view's alpha does not affect the {@code TaskView} since it uses a Surface.
+ */
+ public void setContentAlpha(float alpha) {
+ if (mIsOverflow) {
+ mOverflowView.setAlpha(alpha);
+ } else if (mTaskView != null) {
mTaskView.setAlpha(alpha);
}
+ }
+
+ /**
+ * Sets the alpha of the background and the pointer view.
+ */
+ public void setBackgroundAlpha(float alpha) {
mPointerView.setAlpha(alpha);
setAlpha(alpha);
}
/**
+ * Set translation Y for the expanded view content.
+ * Excludes manage button and pointer.
+ */
+ public void setContentTranslationY(float translationY) {
+ mExpandedViewContainer.setTranslationY(translationY);
+
+ // Left or right pointer can become detached when moving the view up
+ if (translationY <= 0 && (isShowingLeftPointer() || isShowingRightPointer())) {
+ // Y coordinate where the pointer would start to get detached from the expanded view.
+ // Takes into account bottom clipping and rounded corners
+ float detachPoint =
+ mExpandedViewContainer.getBottom() - mBottomClip - mCornerRadius + translationY;
+ float pointerBottom = mPointerPos.y + mPointerHeight;
+ // If pointer bottom is past detach point, move it in by that many pixels
+ float horizontalShift = 0;
+ if (pointerBottom > detachPoint) {
+ horizontalShift = pointerBottom - detachPoint;
+ }
+ if (isShowingLeftPointer()) {
+ // Move left pointer right
+ movePointerBy(horizontalShift, 0);
+ } else {
+ // Move right pointer left
+ movePointerBy(-horizontalShift, 0);
+ }
+ // Hide pointer if it is moved by entire width
+ mPointerView.setVisibility(
+ horizontalShift > mPointerWidth ? View.INVISIBLE : View.VISIBLE);
+ }
+ }
+
+ /**
+ * Update alpha value for the manage button
+ */
+ public void setManageButtonAlpha(float alpha) {
+ mManageButton.setAlpha(alpha);
+ }
+
+ /**
+ * Set {@link #setTranslationY(float) translationY} for the manage button
+ */
+ public void setManageButtonTranslationY(float translationY) {
+ mManageButton.setTranslationY(translationY);
+ }
+
+ /**
+ * Set top clipping for the view
+ */
+ public void setTopClip(int clip) {
+ mTopClip = clip;
+ onContainerClipUpdate();
+ }
+
+ /**
+ * Set bottom clipping for the view
+ */
+ public void setBottomClip(int clip) {
+ mBottomClip = clip;
+ onContainerClipUpdate();
+ }
+
+ private void onContainerClipUpdate() {
+ if (mTopClip == 0 && mBottomClip == 0) {
+ if (mIsClipping) {
+ mIsClipping = false;
+ if (mTaskView != null) {
+ mTaskView.setClipBounds(null);
+ mTaskView.setEnableSurfaceClipping(false);
+ }
+ mExpandedViewContainer.invalidateOutline();
+ }
+ } else {
+ if (!mIsClipping) {
+ mIsClipping = true;
+ if (mTaskView != null) {
+ mTaskView.setEnableSurfaceClipping(true);
+ }
+ }
+ mExpandedViewContainer.invalidateOutline();
+ if (mTaskView != null) {
+ mTaskView.setClipBounds(new Rect(0, mTopClip, mTaskView.getWidth(),
+ mTaskView.getHeight() - mBottomClip));
+ }
+ }
+ }
+
+ /**
+ * Move pointer from base position
+ */
+ public void movePointerBy(float x, float y) {
+ mPointerView.setTranslationX(mPointerPos.x + x);
+ mPointerView.setTranslationY(mPointerPos.y + y);
+ }
+
+ /**
* Set visibility of contents in the expanded state.
*
* @param visibility {@code true} if the contents should be visible on the screen.
@@ -543,13 +727,13 @@ public class BubbleExpandedView extends LinearLayout {
* Note that this contents visibility doesn't affect visibility at {@link android.view.View},
* and setting {@code false} actually means rendering the contents in transparent.
*/
- void setContentVisibility(boolean visibility) {
+ public void setContentVisibility(boolean visibility) {
if (DEBUG_BUBBLE_EXPANDED_VIEW) {
Log.d(TAG, "setContentVisibility: visibility=" + visibility
+ " bubble=" + getBubbleKey());
}
mIsContentVisible = visibility;
- if (mTaskView != null && !mIsAlphaAnimating) {
+ if (mTaskView != null && !mIsAnimating) {
mTaskView.setAlpha(visibility ? 1f : 0f);
mPointerView.setAlpha(visibility ? 1f : 0f);
}
@@ -560,6 +744,44 @@ public class BubbleExpandedView extends LinearLayout {
return mTaskView;
}
+ @VisibleForTesting
+ public BubbleOverflowContainerView getOverflow() {
+ return mOverflowView;
+ }
+
+
+ /**
+ * Return content height: taskView or overflow.
+ * Takes into account clippings set by {@link #setTopClip(int)} and {@link #setBottomClip(int)}
+ *
+ * @return if bubble is for overflow, return overflow height, otherwise return taskView height
+ */
+ public int getContentHeight() {
+ if (mIsOverflow) {
+ return mOverflowView.getHeight() - mTopClip - mBottomClip;
+ }
+ if (mTaskView != null) {
+ return mTaskView.getHeight() - mTopClip - mBottomClip;
+ }
+ return 0;
+ }
+
+ /**
+ * Return bottom position of the content on screen
+ *
+ * @return if bubble is for overflow, return value for overflow, otherwise taskView
+ */
+ public int getContentBottomOnScreen() {
+ Rect out = new Rect();
+ if (mIsOverflow) {
+ mOverflowView.getBoundsOnScreen(out);
+ }
+ if (mTaskView != null) {
+ mTaskView.getBoundsOnScreen(out);
+ }
+ return out.bottom;
+ }
+
int getTaskId() {
return mTaskId;
}
@@ -687,7 +909,9 @@ public class BubbleExpandedView extends LinearLayout {
mTaskView.onLocationChanged();
}
if (mIsOverflow) {
- mOverflowView.show();
+ post(() -> {
+ mOverflowView.show();
+ });
}
}
@@ -730,38 +954,59 @@ public class BubbleExpandedView extends LinearLayout {
post(() -> {
mCurrentPointer = showVertically ? onLeft ? mLeftPointer : mRightPointer : mTopPointer;
updatePointerView();
- float pointerY;
- float pointerX;
if (showVertically) {
- pointerY = bubbleCenter - (mPointerWidth / 2f);
+ mPointerPos.y = bubbleCenter - (mPointerWidth / 2f);
if (!isRtl) {
- pointerX = onLeft
+ mPointerPos.x = onLeft
? -mPointerHeight + mPointerOverlap
: getWidth() - mPaddingRight - mPointerOverlap;
} else {
- pointerX = onLeft
+ mPointerPos.x = onLeft
? -(getWidth() - mPaddingLeft - mPointerOverlap)
: mPointerHeight - mPointerOverlap;
}
} else {
- pointerY = mPointerOverlap;
+ mPointerPos.y = mPointerOverlap;
if (!isRtl) {
- pointerX = bubbleCenter - (mPointerWidth / 2f);
+ mPointerPos.x = bubbleCenter - (mPointerWidth / 2f);
} else {
- pointerX = -(getWidth() - mPaddingLeft - bubbleCenter) + (mPointerWidth / 2f);
+ mPointerPos.x = -(getWidth() - mPaddingLeft - bubbleCenter)
+ + (mPointerWidth / 2f);
}
}
if (animate) {
- mPointerView.animate().translationX(pointerX).translationY(pointerY).start();
+ mPointerView.animate().translationX(mPointerPos.x).translationY(
+ mPointerPos.y).start();
} else {
- mPointerView.setTranslationY(pointerY);
- mPointerView.setTranslationX(pointerX);
+ mPointerView.setTranslationY(mPointerPos.y);
+ mPointerView.setTranslationX(mPointerPos.x);
mPointerView.setVisibility(VISIBLE);
}
});
}
/**
+ * Return true if pointer is shown on the left
+ */
+ public boolean isShowingLeftPointer() {
+ return mCurrentPointer == mLeftPointer;
+ }
+
+ /**
+ * Return true if pointer is shown on the right
+ */
+ public boolean isShowingRightPointer() {
+ return mCurrentPointer == mRightPointer;
+ }
+
+ /**
+ * Return width of the current pointer
+ */
+ public int getPointerWidth() {
+ return mPointerWidth;
+ }
+
+ /**
* Position of the manage button displayed in the expanded view. Used for placing user
* education about the manage button.
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
index fcd0ed7308ef..9aa285fff19c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflowContainerView.java
@@ -167,7 +167,10 @@ public class BubbleOverflowContainerView extends LinearLayout {
void updateOverflow() {
Resources res = getResources();
- final int columns = res.getInteger(R.integer.bubbles_overflow_columns);
+ int columns = (int) Math.round(getWidth()
+ / (res.getDimension(R.dimen.bubble_name_width)));
+ columns = columns > 0 ? columns : res.getInteger(R.integer.bubbles_overflow_columns);
+
mRecyclerView.setLayoutManager(
new OverflowGridLayoutManager(getContext(), columns));
if (mRecyclerView.getItemDecorationCount() == 0) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
index e9729e45731b..dbad5df9cf56 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblePositioner.java
@@ -16,6 +16,8 @@
package com.android.wm.shell.bubbles;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
@@ -28,7 +30,6 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.util.Log;
import android.view.Surface;
-import android.view.View;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -366,6 +367,14 @@ public class BubblePositioner {
return mImeVisible ? mImeHeight : 0;
}
+ /** Return top position of the IME if it's visible */
+ public int getImeTop() {
+ if (mImeVisible) {
+ return getScreenRect().bottom - getImeHeight() - getInsets().bottom;
+ }
+ return 0;
+ }
+
/** Sets whether the IME is visible. **/
public void setImeVisible(boolean visible, int height) {
mImeVisible = visible;
@@ -557,16 +566,30 @@ public class BubblePositioner {
* @return the position of the bubble on-screen when the stack is expanded.
*/
public PointF getExpandedBubbleXY(int index, BubbleStackView.StackViewState state) {
- final float positionInRow = index * (mBubbleSize + mSpacingBetweenBubbles);
+ boolean showBubblesVertically = showBubblesVertically();
+ boolean isRtl = mContext.getResources().getConfiguration().getLayoutDirection()
+ == LAYOUT_DIRECTION_RTL;
+
+ int onScreenIndex;
+ if (showBubblesVertically || !isRtl) {
+ onScreenIndex = index;
+ } else {
+ // If bubbles are shown horizontally, check if RTL language is used.
+ // If RTL is active, position first bubble on the right and last on the left.
+ // Last bubble has screen index 0 and first bubble has max screen index value.
+ onScreenIndex = state.numberOfBubbles - 1 - index;
+ }
+
+ final float positionInRow = onScreenIndex * (mBubbleSize + mSpacingBetweenBubbles);
final float expandedStackSize = getExpandedStackSize(state.numberOfBubbles);
- final float centerPosition = showBubblesVertically()
+ final float centerPosition = showBubblesVertically
? mPositionRect.centerY()
: mPositionRect.centerX();
// alignment - centered on the edge
final float rowStart = centerPosition - (expandedStackSize / 2f);
float x;
float y;
- if (showBubblesVertically()) {
+ if (showBubblesVertically) {
int inset = mExpandedViewLargeScreenInsetClosestEdge;
y = rowStart + positionInRow;
int left = mIsLargeScreen
@@ -583,8 +606,8 @@ public class BubblePositioner {
x = rowStart + positionInRow;
}
- if (showBubblesVertically() && mImeVisible) {
- return new PointF(x, getExpandedBubbleYForIme(index, state));
+ if (showBubblesVertically && mImeVisible) {
+ return new PointF(x, getExpandedBubbleYForIme(onScreenIndex, state));
}
return new PointF(x, y);
}
@@ -693,7 +716,7 @@ public class BubblePositioner {
// Start on the left if we're in LTR, right otherwise.
final boolean startOnLeft =
mContext.getResources().getConfiguration().getLayoutDirection()
- != View.LAYOUT_DIRECTION_RTL;
+ != LAYOUT_DIRECTION_RTL;
final float startingVerticalOffset = mContext.getResources().getDimensionPixelOffset(
R.dimen.bubble_stack_starting_offset_y);
// TODO: placement bug here because mPositionRect doesn't handle the overhanging edge
@@ -749,4 +772,21 @@ public class BubblePositioner {
public void setPinnedLocation(PointF point) {
mPinLocation = point;
}
+
+ /**
+ * Navigation bar has an area where system gestures can be started from.
+ *
+ * @return {@link Rect} for system navigation bar gesture zone
+ */
+ public Rect getNavBarGestureZone() {
+ // Gesture zone height from the bottom
+ int gestureZoneHeight = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.navigation_bar_gesture_height);
+ Rect screen = getScreenRect();
+ return new Rect(
+ screen.left,
+ screen.bottom - gestureZoneHeight,
+ screen.right,
+ screen.bottom);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 0a334140d616..2d0be066beb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -21,6 +21,7 @@ import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_STACK_VIEW;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -44,6 +45,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.os.Bundle;
+import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Log;
import android.view.Choreographer;
@@ -56,6 +58,7 @@ import android.view.View;
import android.view.ViewGroup;
import android.view.ViewOutlineProvider;
import android.view.ViewTreeObserver;
+import android.view.WindowManagerPolicyConstants;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
@@ -75,8 +78,12 @@ import com.android.internal.util.FrameworkStatsLog;
import com.android.wm.shell.R;
import com.android.wm.shell.animation.Interpolators;
import com.android.wm.shell.animation.PhysicsAnimator;
+import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
+import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationController;
+import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerImpl;
+import com.android.wm.shell.bubbles.animation.ExpandedViewAnimationControllerStub;
import com.android.wm.shell.bubbles.animation.PhysicsAnimationLayout;
import com.android.wm.shell.bubbles.animation.StackAnimationController;
import com.android.wm.shell.common.FloatingContentCoordinator;
@@ -89,6 +96,7 @@ import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Objects;
import java.util.function.Consumer;
import java.util.stream.Collectors;
@@ -97,6 +105,12 @@ import java.util.stream.Collectors;
*/
public class BubbleStackView extends FrameLayout
implements ViewTreeObserver.OnComputeInternalInsetsListener {
+ /**
+ * Set to {@code true} to enable home gesture handling in bubbles
+ */
+ public static final boolean HOME_GESTURE_ENABLED =
+ SystemProperties.getBoolean("persist.wm.debug.bubbles_home_gesture", true);
+
private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleStackView" : TAG_BUBBLES;
/** How far the flyout needs to be dragged before it's dismissed regardless of velocity. */
@@ -123,6 +137,9 @@ public class BubbleStackView extends FrameLayout
private static final float SCRIM_ALPHA = 0.6f;
+ /** Minimum alpha value for scrim when alpha is being changed via drag */
+ private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f;
+
/**
* How long to wait to animate the stack temporarily invisible after a drag/flyout hide
* animation ends, if we are in fact temporarily invisible.
@@ -148,7 +165,7 @@ public class BubbleStackView extends FrameLayout
* Handler to use for all delayed animations - this way, we can easily cancel them before
* starting a new animation.
*/
- private final ShellExecutor mDelayedAnimationExecutor;
+ private final ShellExecutor mMainExecutor;
private Runnable mDelayedAnimation;
/**
@@ -197,8 +214,10 @@ public class BubbleStackView extends FrameLayout
private PhysicsAnimationLayout mBubbleContainer;
private StackAnimationController mStackAnimationController;
private ExpandedAnimationController mExpandedAnimationController;
+ private ExpandedViewAnimationController mExpandedViewAnimationController;
private View mScrim;
+ private boolean mScrimAnimating;
private View mManageMenuScrim;
private FrameLayout mExpandedViewContainer;
@@ -276,6 +295,9 @@ public class BubbleStackView extends FrameLayout
*/
private int mPointerIndexDown = -1;
+ @Nullable
+ private BubblesNavBarGestureTracker mBubblesNavBarGestureTracker;
+
/** Description of current animation controller state. */
public void dump(PrintWriter pw, String[] args) {
pw.println("Stack view state:");
@@ -693,6 +715,90 @@ public class BubbleStackView extends FrameLayout
}
};
+ /** Touch listener set on the whole view that forwards event to the swipe up listener. */
+ private final RelativeTouchListener mContainerSwipeListener = new RelativeTouchListener() {
+ @Override
+ public boolean onDown(@NonNull View v, @NonNull MotionEvent ev) {
+ // Pass move event on to swipe listener
+ mSwipeUpListener.onDown(ev.getX(), ev.getY());
+ return true;
+ }
+
+ @Override
+ public void onMove(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy) {
+ // Pass move event on to swipe listener
+ mSwipeUpListener.onMove(dx, dy);
+ }
+
+ @Override
+ public void onUp(@NonNull View v, @NonNull MotionEvent ev, float viewInitialX,
+ float viewInitialY, float dx, float dy, float velX, float velY) {
+ // Pass up even on to swipe listener
+ mSwipeUpListener.onUp(velX, velY);
+ }
+ };
+
+ /** MotionEventListener that listens from home gesture swipe event. */
+ private final MotionEventListener mSwipeUpListener = new MotionEventListener() {
+ @Override
+ public void onDown(float x, float y) {}
+
+ @Override
+ public void onMove(float dx, float dy) {
+ if ((mManageEduView != null && mManageEduView.getVisibility() == VISIBLE)
+ || isStackEduShowing()) {
+ return;
+ }
+
+ if (mShowingManage) {
+ showManageMenu(false /* show */);
+ }
+ // Only allow up, normalize for up direction
+ float collapsed = -Math.min(dy, 0);
+ mExpandedViewAnimationController.updateDrag((int) collapsed);
+
+ // Update scrim
+ if (!mScrimAnimating) {
+ mScrim.setAlpha(getScrimAlphaForDrag(collapsed));
+ }
+ }
+
+ @Override
+ public void onCancel() {
+ mExpandedViewAnimationController.animateBackToExpanded();
+ }
+
+ @Override
+ public void onUp(float velX, float velY) {
+ mExpandedViewAnimationController.setSwipeVelocity(velY);
+ if (mExpandedViewAnimationController.shouldCollapse()) {
+ // Update data first and start the animation when we are processing change
+ mBubbleData.setExpanded(false);
+ } else {
+ mExpandedViewAnimationController.animateBackToExpanded();
+
+ // Update scrim
+ if (!mScrimAnimating) {
+ showScrim(true);
+ }
+ }
+ }
+
+ private float getScrimAlphaForDrag(float dragAmount) {
+ // dragAmount should be negative as we allow scroll up only
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
+
+ int dragMax = mExpandedBubble.getExpandedView().getContentHeight();
+ float dragFraction = dragAmount / dragMax;
+
+ return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG);
+ }
+ return SCRIM_ALPHA;
+ }
+ };
+
/** Click listener set on the flyout, which expands the stack when the flyout is tapped. */
private OnClickListener mFlyoutClickListener = new OnClickListener() {
@Override
@@ -766,7 +872,7 @@ public class BubbleStackView extends FrameLayout
ShellExecutor mainExecutor) {
super(context);
- mDelayedAnimationExecutor = mainExecutor;
+ mMainExecutor = mainExecutor;
mBubbleController = bubbleController;
mBubbleData = data;
@@ -796,6 +902,14 @@ public class BubbleStackView extends FrameLayout
mExpandedAnimationController = new ExpandedAnimationController(mPositioner,
onBubbleAnimatedOut, this);
+
+ if (HOME_GESTURE_ENABLED) {
+ mExpandedViewAnimationController =
+ new ExpandedViewAnimationControllerImpl(context, mPositioner);
+ } else {
+ mExpandedViewAnimationController = new ExpandedViewAnimationControllerStub();
+ }
+
mSurfaceSynchronizer = synchronizer != null ? synchronizer : DEFAULT_SURFACE_SYNCHRONIZER;
// Force LTR by default since most of the Bubbles UI is positioned manually by the user, or
@@ -971,7 +1085,7 @@ public class BubbleStackView extends FrameLayout
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
// We need to be Z ordered on top in order for alpha animations to work.
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(true);
- mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+ mExpandedBubble.getExpandedView().setAnimating(true);
}
}
@@ -985,14 +1099,15 @@ public class BubbleStackView extends FrameLayout
// = 0f remains in effect.
&& !mExpandedViewTemporarilyHidden) {
mExpandedBubble.getExpandedView().setSurfaceZOrderedOnTop(false);
- mExpandedBubble.getExpandedView().setAlphaAnimating(false);
+ mExpandedBubble.getExpandedView().setAnimating(false);
}
}
});
mExpandedViewAlphaAnimator.addUpdateListener(valueAnimator -> {
if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setTaskViewAlpha(
- (float) valueAnimator.getAnimatedValue());
+ float alpha = (float) valueAnimator.getAnimatedValue();
+ mExpandedBubble.getExpandedView().setContentAlpha(alpha);
+ mExpandedBubble.getExpandedView().setBackgroundAlpha(alpha);
}
});
@@ -1708,7 +1823,7 @@ public class BubbleStackView extends FrameLayout
/**
* Update bubble order and pointer position.
*/
- public void updateBubbleOrder(List<Bubble> bubbles) {
+ public void updateBubbleOrder(List<Bubble> bubbles, boolean updatePointerPositoion) {
final Runnable reorder = () -> {
for (int i = 0; i < bubbles.size(); i++) {
Bubble bubble = bubbles.get(i);
@@ -1724,7 +1839,10 @@ public class BubbleStackView extends FrameLayout
.map(b -> b.getIconView()).collect(Collectors.toList());
mStackAnimationController.animateReorder(bubbleViews, reorder);
}
- updatePointerPosition(false /* forIme */);
+
+ if (updatePointerPositoion) {
+ updatePointerPosition(false /* forIme */);
+ }
}
/**
@@ -1795,6 +1913,7 @@ public class BubbleStackView extends FrameLayout
private void showNewlySelectedBubble(BubbleViewProvider bubbleToSelect) {
final BubbleViewProvider previouslySelected = mExpandedBubble;
mExpandedBubble = bubbleToSelect;
+ mExpandedViewAnimationController.setExpandedView(mExpandedBubble.getExpandedView());
if (mIsExpanded) {
hideCurrentInputMethod();
@@ -1843,12 +1962,19 @@ public class BubbleStackView extends FrameLayout
return;
}
+ boolean wasExpanded = mIsExpanded;
+
hideCurrentInputMethod();
mBubbleController.getSysuiProxy().onStackExpandChanged(shouldExpand);
- if (mIsExpanded) {
- animateCollapse();
+ if (wasExpanded) {
+ stopMonitoringSwipeUpGesture();
+ if (HOME_GESTURE_ENABLED) {
+ animateCollapse();
+ } else {
+ animateCollapseWithScale();
+ }
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
} else {
animateExpansion();
@@ -1856,11 +1982,58 @@ public class BubbleStackView extends FrameLayout
logBubbleEvent(mExpandedBubble, FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
logBubbleEvent(mExpandedBubble,
FrameworkStatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
+ if (HOME_GESTURE_ENABLED) {
+ mBubbleController.isNotificationPanelExpanded(notifPanelExpanded -> {
+ if (!notifPanelExpanded && mIsExpanded) {
+ startMonitoringSwipeUpGesture();
+ }
+ });
+ }
}
notifyExpansionChanged(mExpandedBubble, mIsExpanded);
}
/**
+ * Monitor for swipe up gesture that is used to collapse expanded view
+ */
+ void startMonitoringSwipeUpGesture() {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "startMonitoringSwipeUpGesture");
+ }
+ stopMonitoringSwipeUpGestureInternal();
+
+ if (isGestureNavEnabled()) {
+ mBubblesNavBarGestureTracker = new BubblesNavBarGestureTracker(mContext, mPositioner);
+ mBubblesNavBarGestureTracker.start(mSwipeUpListener);
+ setOnTouchListener(mContainerSwipeListener);
+ }
+ }
+
+ private boolean isGestureNavEnabled() {
+ return mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_navBarInteractionMode)
+ == WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
+ }
+
+ /**
+ * Stop monitoring for swipe up gesture
+ */
+ void stopMonitoringSwipeUpGesture() {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "stopMonitoringSwipeUpGesture");
+ }
+ stopMonitoringSwipeUpGestureInternal();
+ }
+
+ private void stopMonitoringSwipeUpGestureInternal() {
+ if (mBubblesNavBarGestureTracker != null) {
+ mBubblesNavBarGestureTracker.stop();
+ mBubblesNavBarGestureTracker = null;
+ setOnTouchListener(null);
+ }
+ }
+
+ /**
* Called when back press occurs while bubbles are expanded.
*/
public void onBackPressed() {
@@ -1982,15 +2155,28 @@ public class BubbleStackView extends FrameLayout
}
private void showScrim(boolean show) {
+ AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mScrimAnimating = true;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mScrimAnimating = false;
+ }
+ };
if (show) {
mScrim.animate()
.setInterpolator(ALPHA_IN)
.alpha(SCRIM_ALPHA)
+ .setListener(listener)
.start();
} else {
mScrim.animate()
.alpha(0f)
.setInterpolator(ALPHA_OUT)
+ .setListener(listener)
.start();
}
}
@@ -2072,11 +2258,12 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
if (mExpandedBubble.getExpandedView() != null) {
- mExpandedBubble.getExpandedView().setTaskViewAlpha(0f);
+ mExpandedBubble.getExpandedView().setContentAlpha(0f);
+ mExpandedBubble.getExpandedView().setBackgroundAlpha(0f);
// We'll be starting the alpha animation after a slight delay, so set this flag early
// here.
- mExpandedBubble.getExpandedView().setAlphaAnimating(true);
+ mExpandedBubble.getExpandedView().setAnimating(true);
}
mDelayedAnimation = () -> {
@@ -2114,10 +2301,10 @@ public class BubbleStackView extends FrameLayout
})
.start();
};
- mDelayedAnimationExecutor.executeDelayed(mDelayedAnimation, startDelay);
+ mMainExecutor.executeDelayed(mDelayedAnimation, startDelay);
}
- private void animateCollapse() {
+ private void animateCollapseWithScale() {
cancelDelayedExpandCollapseSwitchAnimations();
if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
@@ -2217,6 +2404,68 @@ public class BubbleStackView extends FrameLayout
.start();
}
+ private void animateCollapse() {
+ cancelDelayedExpandCollapseSwitchAnimations();
+
+ if (mManageEduView != null && mManageEduView.getVisibility() == VISIBLE) {
+ mManageEduView.hide();
+ }
+
+ mIsExpanded = false;
+ mIsExpansionAnimating = true;
+
+ showScrim(false);
+
+ mBubbleContainer.cancelAllAnimations();
+
+ // If we were in the middle of swapping, the animating-out surface would have been scaling
+ // to zero - finish it off.
+ PhysicsAnimator.getInstance(mAnimatingOutSurfaceContainer).cancel();
+ mAnimatingOutSurfaceContainer.setScaleX(0f);
+ mAnimatingOutSurfaceContainer.setScaleY(0f);
+
+ // Let the expanded animation controller know that it shouldn't animate child adds/reorders
+ // since we're about to animate collapsed.
+ mExpandedAnimationController.notifyPreparingToCollapse();
+
+ final Runnable collapseBackToStack = () -> mExpandedAnimationController.collapseBackToStack(
+ mStackAnimationController
+ .getStackPositionAlongNearestHorizontalEdge()
+ /* collapseTo */,
+ () -> mBubbleContainer.setActiveController(mStackAnimationController));
+
+ final Runnable after = () -> {
+ final BubbleViewProvider previouslySelected = mExpandedBubble;
+ // TODO(b/231350255): investigate why this call is needed here
+ beforeExpandedViewAnimation();
+ if (mManageEduView != null) {
+ mManageEduView.hide();
+ }
+
+ if (DEBUG_BUBBLE_STACK_VIEW) {
+ Log.d(TAG, "animateCollapse");
+ Log.d(TAG, BubbleDebugConfig.formatBubblesString(getBubblesOnScreen(),
+ mExpandedBubble));
+ }
+ updateOverflowVisibility();
+ updateZOrder();
+ updateBadges(true /* setBadgeForCollapsedStack */);
+ afterExpandedViewAnimation();
+ if (previouslySelected != null) {
+ previouslySelected.setTaskViewVisibility(false);
+ }
+ mExpandedViewAnimationController.reset();
+ };
+ mExpandedViewAnimationController.animateCollapse(collapseBackToStack, after);
+ if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ // When the animation completes, we should no longer be showing the content.
+ // This won't actually update content visibility immediately, if we are currently
+ // animating. But updates the internal state for the content to be hidden after
+ // animation completes.
+ mExpandedBubble.getExpandedView().setContentVisibility(false);
+ }
+ }
+
private void animateSwitchBubbles() {
// If we're no longer expanded, this is meaningless.
if (!mIsExpanded) {
@@ -2277,7 +2526,7 @@ public class BubbleStackView extends FrameLayout
mExpandedViewContainer.setAnimationMatrix(mExpandedViewContainerMatrix);
- mDelayedAnimationExecutor.executeDelayed(() -> {
+ mMainExecutor.executeDelayed(() -> {
if (!mIsExpanded) {
mIsBubbleSwitchAnimating = false;
return;
@@ -2308,7 +2557,7 @@ public class BubbleStackView extends FrameLayout
* animating flags for those animations.
*/
private void cancelDelayedExpandCollapseSwitchAnimations() {
- mDelayedAnimationExecutor.removeCallbacks(mDelayedAnimation);
+ mMainExecutor.removeCallbacks(mDelayedAnimation);
mIsExpansionAnimating = false;
mIsBubbleSwitchAnimating = false;
@@ -2332,9 +2581,18 @@ public class BubbleStackView extends FrameLayout
/**
* Updates the stack based for IME changes. When collapsed it'll move the stack if it
* overlaps where they IME would be. When expanded it'll shift the expanded bubbles
- * if they might overlap with the IME (this only happens for large screens).
+ * if they might overlap with the IME (this only happens for large screens)
+ * and clip the expanded view.
*/
- public void animateForIme(boolean visible) {
+ public void setImeVisible(boolean visible) {
+ if (HOME_GESTURE_ENABLED) {
+ setImeVisibleInternal(visible);
+ } else {
+ setImeVisibleWithoutClipping(visible);
+ }
+ }
+
+ private void setImeVisibleWithoutClipping(boolean visible) {
if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
// This will update the animation so the bubbles move to position for the IME
mExpandedAnimationController.expandFromStack(() -> {
@@ -2385,6 +2643,62 @@ public class BubbleStackView extends FrameLayout
}
}
+ private void setImeVisibleInternal(boolean visible) {
+ if ((mIsExpansionAnimating || mIsBubbleSwitchAnimating) && mIsExpanded) {
+ // This will update the animation so the bubbles move to position for the IME
+ mExpandedAnimationController.expandFromStack(() -> {
+ updatePointerPosition(false /* forIme */);
+ afterExpandedViewAnimation();
+ mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
+ } /* after */);
+ return;
+ }
+
+ if (!mIsExpanded && getBubbleCount() > 0) {
+ final float stackDestinationY =
+ mStackAnimationController.animateForImeVisibility(visible);
+
+ // How far the stack is animating due to IME, we'll just animate the flyout by that
+ // much too.
+ final float stackDy =
+ stackDestinationY - mStackAnimationController.getStackPosition().y;
+
+ // If the flyout is visible, translate it along with the bubble stack.
+ if (mFlyout.getVisibility() == VISIBLE) {
+ PhysicsAnimator.getInstance(mFlyout)
+ .spring(DynamicAnimation.TRANSLATION_Y,
+ mFlyout.getTranslationY() + stackDy,
+ FLYOUT_IME_ANIMATION_SPRING_CONFIG)
+ .start();
+ }
+ }
+
+ if (mIsExpanded) {
+ mExpandedViewAnimationController.animateForImeVisibilityChange(visible);
+ if (mPositioner.showBubblesVertically()
+ && mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
+ float selectedY = mPositioner.getExpandedBubbleXY(getState().selectedIndex,
+ getState()).y;
+ float newExpandedViewTop = mPositioner.getExpandedViewY(mExpandedBubble, selectedY);
+ mExpandedBubble.getExpandedView().setImeVisible(visible);
+ if (!mExpandedBubble.getExpandedView().isUsingMaxHeight()) {
+ mExpandedViewContainer.animate().translationY(newExpandedViewTop);
+ }
+ List<Animator> animList = new ArrayList();
+ for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
+ View child = mBubbleContainer.getChildAt(i);
+ float transY = mPositioner.getExpandedBubbleXY(i, getState()).y;
+ ObjectAnimator anim = ObjectAnimator.ofFloat(child, TRANSLATION_Y, transY);
+ animList.add(anim);
+ }
+ updatePointerPosition(true /* forIme */);
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animList);
+ set.start();
+ }
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() != MotionEvent.ACTION_DOWN && ev.getActionIndex() != mPointerIndexDown) {
@@ -2473,8 +2787,10 @@ public class BubbleStackView extends FrameLayout
private void dismissBubbleIfExists(@Nullable BubbleViewProvider bubble) {
if (bubble != null && mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) {
- if (mIsExpanded && mBubbleData.getBubbles().size() > 1) {
- // If we have more than 1 bubble we will perform the switch animation
+ if (mIsExpanded && mBubbleData.getBubbles().size() > 1
+ && Objects.equals(bubble, mExpandedBubble)) {
+ // If we have more than 1 bubble and it's the current bubble being dismissed,
+ // we will perform the switch animation
mIsBubbleSwitchAnimating = true;
}
mBubbleData.dismissBubbleWithKey(bubble.getKey(), Bubbles.DISMISS_USER_GESTURE);
@@ -2820,7 +3136,7 @@ public class BubbleStackView extends FrameLayout
&& mExpandedBubble.getExpandedView() != null) {
BubbleExpandedView bev = mExpandedBubble.getExpandedView();
bev.setContentVisibility(false);
- bev.setAlphaAnimating(!mIsExpansionAnimating);
+ bev.setAnimating(!mIsExpansionAnimating);
mExpandedViewContainerMatrix.setScaleX(0f);
mExpandedViewContainerMatrix.setScaleY(0f);
mExpandedViewContainerMatrix.setTranslate(0f, 0f);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
index 8a0db0a12711..f8ccf2364b4c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java
@@ -107,9 +107,6 @@ public interface Bubbles {
/** Tell the stack of bubbles to collapse. */
void collapseStack();
- /** Tell the controller need update its UI to fit theme. */
- void updateForThemeChanges();
-
/**
* Request the stack expand if needed, then select the specified Bubble as current.
* If no bubble exists for this entry, one is created.
@@ -214,6 +211,11 @@ public interface Bubbles {
int modificationType);
/**
+ * Called when notification panel is expanded or collapsed
+ */
+ void onNotificationPanelExpandedChanged(boolean expanded);
+
+ /**
* Called when the status bar has become visible or invisible (either permanently or
* temporarily).
*/
@@ -250,13 +252,6 @@ public interface Bubbles {
*/
void onUserRemoved(int removedUserId);
- /**
- * Called when config changed.
- *
- * @param newConfig the new config.
- */
- void onConfigChanged(Configuration newConfig);
-
/** Description of current bubble state. */
void dump(PrintWriter pw, String[] args);
@@ -285,7 +280,7 @@ public interface Bubbles {
/** Callback to tell SysUi components execute some methods. */
interface SysuiProxy {
- void isNotificationShadeExpand(Consumer<Boolean> callback);
+ void isNotificationPanelExpand(Consumer<Boolean> callback);
void getPendingOrActiveEntry(String key, Consumer<BubbleEntry> callback);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
new file mode 100644
index 000000000000..e7beeeb06534
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarGestureTracker.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.content.Context;
+import android.hardware.input.InputManager;
+import android.util.Log;
+import android.view.Choreographer;
+import android.view.InputChannel;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
+
+/**
+ * Set up tracking bubbles gestures that begin in navigation bar
+ */
+class BubblesNavBarGestureTracker {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "BubblesGestureTracker" : TAG_BUBBLES;
+
+ private static final String GESTURE_MONITOR = "bubbles-gesture";
+ private final Context mContext;
+ private final BubblePositioner mPositioner;
+
+ @Nullable
+ private InputMonitor mInputMonitor;
+ @Nullable
+ private InputEventReceiver mInputEventReceiver;
+
+ BubblesNavBarGestureTracker(Context context, BubblePositioner positioner) {
+ mContext = context;
+ mPositioner = positioner;
+ }
+
+ /**
+ * Start tracking gestures
+ *
+ * @param listener listener that is notified of touch events
+ */
+ void start(MotionEventListener listener) {
+ if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "start monitoring bubbles swipe up gesture");
+ }
+
+ stopInternal();
+
+ mInputMonitor = InputManager.getInstance().monitorGestureInput(GESTURE_MONITOR,
+ mContext.getDisplayId());
+ InputChannel inputChannel = mInputMonitor.getInputChannel();
+
+ BubblesNavBarMotionEventHandler motionEventHandler =
+ new BubblesNavBarMotionEventHandler(mContext, mPositioner,
+ this::onInterceptTouch, listener);
+ mInputEventReceiver = new BubblesNavBarInputEventReceiver(inputChannel,
+ Choreographer.getInstance(), motionEventHandler);
+ }
+
+ void stop() {
+ if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "stop monitoring bubbles swipe up gesture");
+ }
+ stopInternal();
+ }
+
+ private void stopInternal() {
+ if (mInputEventReceiver != null) {
+ mInputEventReceiver.dispose();
+ mInputEventReceiver = null;
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.dispose();
+ mInputMonitor = null;
+ }
+ }
+
+ private void onInterceptTouch() {
+ if (BubbleDebugConfig.DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "intercept touch event");
+ }
+ if (mInputMonitor != null) {
+ mInputMonitor.pilferPointers();
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java
new file mode 100644
index 000000000000..45037b87830f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarInputEventReceiver.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import android.os.Looper;
+import android.view.BatchedInputEventReceiver;
+import android.view.Choreographer;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.MotionEvent;
+
+/**
+ * Bubbles {@link BatchedInputEventReceiver} for monitoring touches from navbar gesture area
+ */
+class BubblesNavBarInputEventReceiver extends BatchedInputEventReceiver {
+
+ private final BubblesNavBarMotionEventHandler mMotionEventHandler;
+
+ BubblesNavBarInputEventReceiver(InputChannel inputChannel,
+ Choreographer choreographer, BubblesNavBarMotionEventHandler motionEventHandler) {
+ super(inputChannel, Looper.myLooper(), choreographer);
+ mMotionEventHandler = motionEventHandler;
+ }
+
+ @Override
+ public void onInputEvent(InputEvent event) {
+ boolean handled = false;
+ try {
+ if (!(event instanceof MotionEvent)) {
+ return;
+ }
+ handled = mMotionEventHandler.onMotionEvent((MotionEvent) event);
+ } finally {
+ finishInputEvent(event, handled);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
new file mode 100644
index 000000000000..844526ca0f35
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandler.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.content.Context;
+import android.graphics.PointF;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Handles {@link MotionEvent}s for bubbles that begin in the nav bar area
+ */
+class BubblesNavBarMotionEventHandler {
+ private static final String TAG =
+ TAG_WITH_CLASS_NAME ? "BubblesNavBarMotionEventHandler" : TAG_BUBBLES;
+ private static final int VELOCITY_UNITS = 1000;
+
+ private final Runnable mOnInterceptTouch;
+ private final MotionEventListener mMotionEventListener;
+ private final int mTouchSlop;
+ private final BubblePositioner mPositioner;
+ private final PointF mTouchDown = new PointF();
+ private boolean mTrackingTouches;
+ private boolean mInterceptingTouches;
+ @Nullable
+ private VelocityTracker mVelocityTracker;
+
+ BubblesNavBarMotionEventHandler(Context context, BubblePositioner positioner,
+ Runnable onInterceptTouch, MotionEventListener motionEventListener) {
+ mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+ mPositioner = positioner;
+ mOnInterceptTouch = onInterceptTouch;
+ mMotionEventListener = motionEventListener;
+ }
+
+ /**
+ * Handle {@link MotionEvent} and forward it to {@code motionEventListener} defined in
+ * constructor
+ *
+ * @return {@code true} if this {@link MotionEvent} is handled (it started in the gesture area)
+ */
+ public boolean onMotionEvent(MotionEvent motionEvent) {
+ float dx = motionEvent.getX() - mTouchDown.x;
+ float dy = motionEvent.getY() - mTouchDown.y;
+
+ switch (motionEvent.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (isInGestureRegion(motionEvent)) {
+ mTouchDown.set(motionEvent.getX(), motionEvent.getY());
+ mMotionEventListener.onDown(motionEvent.getX(), motionEvent.getY());
+ mTrackingTouches = true;
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_MOVE:
+ if (mTrackingTouches) {
+ if (!mInterceptingTouches && Math.hypot(dx, dy) > mTouchSlop) {
+ mInterceptingTouches = true;
+ mOnInterceptTouch.run();
+ }
+ if (mInterceptingTouches) {
+ getVelocityTracker().addMovement(motionEvent);
+ mMotionEventListener.onMove(dx, dy);
+ }
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ if (mTrackingTouches) {
+ mMotionEventListener.onCancel();
+ finishTracking();
+ return true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ if (mTrackingTouches) {
+ if (mInterceptingTouches) {
+ getVelocityTracker().computeCurrentVelocity(VELOCITY_UNITS);
+ mMotionEventListener.onUp(getVelocityTracker().getXVelocity(),
+ getVelocityTracker().getYVelocity());
+ }
+ finishTracking();
+ return true;
+ }
+ break;
+ }
+ return false;
+ }
+
+ private boolean isInGestureRegion(MotionEvent ev) {
+ // Only handles touch events beginning in navigation bar system gesture zone
+ if (mPositioner.getNavBarGestureZone().contains((int) ev.getX(), (int) ev.getY())) {
+ if (DEBUG_BUBBLE_GESTURE) {
+ Log.d(TAG, "handling touch y=" + ev.getY()
+ + " navBarGestureZone=" + mPositioner.getNavBarGestureZone());
+ }
+ return true;
+ }
+ return false;
+ }
+
+ private VelocityTracker getVelocityTracker() {
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ return mVelocityTracker;
+ }
+
+ private void finishTracking() {
+ mTouchDown.set(0, 0);
+ mTrackingTouches = false;
+ mInterceptingTouches = false;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ /**
+ * Callback for receiving {@link MotionEvent} updates
+ */
+ interface MotionEventListener {
+ /**
+ * Touch down action.
+ *
+ * @param x x coordinate
+ * @param y y coordinate
+ */
+ void onDown(float x, float y);
+
+ /**
+ * Move action.
+ * Reports distance from point reported in {@link #onDown(float, float)}
+ *
+ * @param dx distance moved on x-axis from starting point, in pixels
+ * @param dy distance moved on y-axis from starting point, in pixels
+ */
+ void onMove(float dx, float dy);
+
+ /**
+ * Touch up action.
+ *
+ * @param velX velocity of the move action on x axis
+ * @param velY velocity of the move actin on y axis
+ */
+ void onUp(float velX, float velY);
+
+ /**
+ * Motion action was cancelled.
+ */
+ void onCancel();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
index cf0cefec401a..ea9d065d5f53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/RelativeTouchListener.kt
@@ -17,8 +17,6 @@
package com.android.wm.shell.bubbles
import android.graphics.PointF
-import android.os.Handler
-import android.os.Looper
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
@@ -146,6 +144,12 @@ abstract class RelativeTouchListener : View.OnTouchListener {
velocityTracker.clear()
movedEnough = false
}
+
+ MotionEvent.ACTION_CANCEL -> {
+ v.handler.removeCallbacksAndMessages(null)
+ velocityTracker.clear()
+ movedEnough = false
+ }
}
return true
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index 573f42468512..b521cb6a3d38 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -16,12 +16,16 @@
package com.android.wm.shell.bubbles.animation;
+import static android.view.View.LAYOUT_DIRECTION_RTL;
+
import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.bubbles.BubbleStackView.HOME_GESTURE_ENABLED;
import android.content.res.Resources;
import android.graphics.Path;
import android.graphics.PointF;
import android.view.View;
+import android.view.animation.Interpolator;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -61,7 +65,10 @@ public class ExpandedAnimationController
private static final float DAMPING_RATIO_MEDIUM_LOW_BOUNCY = 0.65f;
/** Stiffness for the expand/collapse path-following animation. */
- private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 1000;
+ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS = 400;
+
+ /** Stiffness for the expand/collapse animation when home gesture handling is off */
+ private static final int EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE = 1000;
/**
* Velocity required to dismiss an individual bubble without dragging it into the dismiss
@@ -73,6 +80,11 @@ public class ExpandedAnimationController
new PhysicsAnimator.SpringConfig(
EXPAND_COLLAPSE_ANIM_STIFFNESS, SpringForce.DAMPING_RATIO_NO_BOUNCY);
+ private final PhysicsAnimator.SpringConfig mAnimateOutSpringConfigWithoutHomeGesture =
+ new PhysicsAnimator.SpringConfig(
+ EXPAND_COLLAPSE_ANIM_STIFFNESS_WITHOUT_HOME_GESTURE,
+ SpringForce.DAMPING_RATIO_NO_BOUNCY);
+
/** Horizontal offset between bubbles, which we need to know to re-stack them. */
private float mStackOffsetPx;
/** Size of each bubble. */
@@ -233,6 +245,11 @@ public class ExpandedAnimationController
};
}
+ boolean showBubblesVertically = mPositioner.showBubblesVertically();
+ final boolean isRtl =
+ mLayout.getContext().getResources().getConfiguration().getLayoutDirection()
+ == LAYOUT_DIRECTION_RTL;
+
// Animate each bubble individually, since each path will end in a different spot.
animationsForChildrenFromIndex(0, (index, animation) -> {
final View bubble = mLayout.getChildAt(index);
@@ -267,9 +284,20 @@ public class ExpandedAnimationController
// right side, the first bubble is traveling to the top left, so it leads. During
// collapse to the left, the first bubble has the shortest travel time back to the stack
// position, so it leads (and vice versa).
- final boolean firstBubbleLeads =
- (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+ final boolean firstBubbleLeads;
+ if (showBubblesVertically || !isRtl) {
+ firstBubbleLeads =
+ (expanding && !mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
|| (!expanding && mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+ } else {
+ // For RTL languages, when showing bubbles horizontally, it is reversed. The bubbles
+ // are positioned right to left. This means that when expanding from left, the top
+ // bubble will lead as it will be positioned on the right. And when expanding from
+ // right, the top bubble will have the least travel distance.
+ firstBubbleLeads =
+ (expanding && mLayout.isFirstChildXLeftOfCenter(bubble.getTranslationX()))
+ || (!expanding && !mLayout.isFirstChildXLeftOfCenter(mCollapsePoint.x));
+ }
final int startDelay = firstBubbleLeads
? (index * 10)
: ((mLayout.getChildCount() - index) * 10);
@@ -278,11 +306,20 @@ public class ExpandedAnimationController
(firstBubbleLeads && index == 0)
|| (!firstBubbleLeads && index == mLayout.getChildCount() - 1);
+ Interpolator interpolator;
+ if (HOME_GESTURE_ENABLED) {
+ // When home gesture is enabled, we use a different animation timing for collapse
+ interpolator = expanding
+ ? Interpolators.EMPHASIZED_ACCELERATE : Interpolators.EMPHASIZED_DECELERATE;
+ } else {
+ interpolator = Interpolators.LINEAR;
+ }
+
animation
.followAnimatedTargetAlongPath(
path,
EXPAND_COLLAPSE_TARGET_ANIM_DURATION /* targetAnimDuration */,
- Interpolators.LINEAR /* targetAnimInterpolator */,
+ interpolator /* targetAnimInterpolator */,
isLeadBubble ? mLeadBubbleEndAction : null /* endAction */,
() -> mLeadBubbleEndAction = null /* endAction */)
.withStartDelay(startDelay)
@@ -525,10 +562,16 @@ public class ExpandedAnimationController
finishRemoval.run();
mOnBubbleAnimatedOutAction.run();
} else {
+ PhysicsAnimator.SpringConfig springConfig;
+ if (HOME_GESTURE_ENABLED) {
+ springConfig = mAnimateOutSpringConfig;
+ } else {
+ springConfig = mAnimateOutSpringConfigWithoutHomeGesture;
+ }
PhysicsAnimator.getInstance(child)
.spring(DynamicAnimation.ALPHA, 0f)
- .spring(DynamicAnimation.SCALE_X, 0f, mAnimateOutSpringConfig)
- .spring(DynamicAnimation.SCALE_Y, 0f, mAnimateOutSpringConfig)
+ .spring(DynamicAnimation.SCALE_X, 0f, springConfig)
+ .spring(DynamicAnimation.SCALE_Y, 0f, springConfig)
.withEndActions(finishRemoval, mOnBubbleAnimatedOutAction)
.start();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
new file mode 100644
index 000000000000..8a33780bc8d5
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationController.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+
+/**
+ * Animation controller for bubble expanded view collapsing
+ */
+public interface ExpandedViewAnimationController {
+ /**
+ * Set expanded view that this controller is working with.
+ */
+ void setExpandedView(BubbleExpandedView expandedView);
+
+ /**
+ * Set current collapse value, in pixels.
+ *
+ * @param distance pixels that user dragged the view by
+ */
+ void updateDrag(float distance);
+
+ /**
+ * Set current swipe velocity.
+ * Velocity is directional:
+ * <ul>
+ * <li>velocity < 0 means swipe direction is up</li>
+ * <li>velocity > 0 means swipe direction is down</li>
+ * </ul>
+ */
+ void setSwipeVelocity(float velocity);
+
+ /**
+ * Check if view is dragged past collapse threshold or swipe up velocity exceeds min velocity
+ * required to collapse the view
+ */
+ boolean shouldCollapse();
+
+ /**
+ * Animate view to collapsed state
+ *
+ * @param startStackCollapse runnable that is triggered when bubbles can start moving back to
+ * their collapsed location
+ * @param after runnable to run after animation is complete
+ */
+ void animateCollapse(Runnable startStackCollapse, Runnable after);
+
+ /**
+ * Animate the view back to fully expanded state.
+ */
+ void animateBackToExpanded();
+
+ /**
+ * Animate view for IME visibility change
+ */
+ void animateForImeVisibilityChange(boolean visible);
+
+ /**
+ * Reset the view to fully expanded state
+ */
+ void reset();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
new file mode 100644
index 000000000000..845dca34b41f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -0,0 +1,431 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_COLLAPSE_ANIMATOR;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_EXPANDED_VIEW_DRAGGING;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.BACKGROUND_ALPHA;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.BOTTOM_CLIP_PROPERTY;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.CONTENT_ALPHA;
+import static com.android.wm.shell.bubbles.BubbleExpandedView.MANAGE_BUTTON_ALPHA;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.ValueAnimator;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+import android.view.HapticFeedbackConstants;
+import android.view.ViewConfiguration;
+
+import androidx.annotation.Nullable;
+import androidx.dynamicanimation.animation.DynamicAnimation;
+import androidx.dynamicanimation.animation.FloatPropertyCompat;
+import androidx.dynamicanimation.animation.SpringAnimation;
+import androidx.dynamicanimation.animation.SpringForce;
+
+import com.android.wm.shell.animation.FlingAnimationUtils;
+import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+import com.android.wm.shell.bubbles.BubblePositioner;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Implementation of {@link ExpandedViewAnimationController} that uses a collapse animation to
+ * hide the {@link BubbleExpandedView}
+ */
+public class ExpandedViewAnimationControllerImpl implements ExpandedViewAnimationController {
+
+ private static final String TAG = TAG_WITH_CLASS_NAME ? "ExpandedViewAnimCtrl" : TAG_BUBBLES;
+
+ private static final float COLLAPSE_THRESHOLD = 0.02f;
+
+ private static final int COLLAPSE_DURATION_MS = 250;
+
+ private static final int MANAGE_BUTTON_ANIM_DURATION_MS = 78;
+
+ private static final int CONTENT_OPACITY_ANIM_DELAY_MS = 93;
+ private static final int CONTENT_OPACITY_ANIM_DURATION_MS = 78;
+
+ private static final int BACKGROUND_OPACITY_ANIM_DELAY_MS = 172;
+ private static final int BACKGROUND_OPACITY_ANIM_DURATION_MS = 78;
+
+ /** Animation fraction threshold for content alpha animation when stack collapse should begin */
+ private static final float STACK_COLLAPSE_THRESHOLD = 0.5f;
+
+ private static final FloatPropertyCompat<ExpandedViewAnimationControllerImpl>
+ COLLAPSE_HEIGHT_PROPERTY =
+ new FloatPropertyCompat<ExpandedViewAnimationControllerImpl>("CollapseSpring") {
+ @Override
+ public float getValue(ExpandedViewAnimationControllerImpl controller) {
+ return controller.getCollapsedAmount();
+ }
+
+ @Override
+ public void setValue(ExpandedViewAnimationControllerImpl controller,
+ float value) {
+ controller.setCollapsedAmount(value);
+ }
+ };
+
+ private final int mMinFlingVelocity;
+ private float mSwipeUpVelocity;
+ private float mSwipeDownVelocity;
+ private final BubblePositioner mPositioner;
+ private final FlingAnimationUtils mFlingAnimationUtils;
+ private int mDraggedAmount;
+ private float mCollapsedAmount;
+ @Nullable
+ private BubbleExpandedView mExpandedView;
+ @Nullable
+ private AnimatorSet mCollapseAnimation;
+ private boolean mNotifiedAboutThreshold;
+ private SpringAnimation mBackToExpandedAnimation;
+ @Nullable
+ private ObjectAnimator mBottomClipAnim;
+
+ public ExpandedViewAnimationControllerImpl(Context context, BubblePositioner positioner) {
+ mFlingAnimationUtils = new FlingAnimationUtils(context.getResources().getDisplayMetrics(),
+ COLLAPSE_DURATION_MS / 1000f);
+ mMinFlingVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
+ mPositioner = positioner;
+ }
+
+ private static void adjustAnimatorSetDuration(AnimatorSet animatorSet,
+ float durationAdjustment) {
+ for (Animator animator : animatorSet.getChildAnimations()) {
+ animator.setStartDelay((long) (animator.getStartDelay() * durationAdjustment));
+ animator.setDuration((long) (animator.getDuration() * durationAdjustment));
+ }
+ }
+
+ @Override
+ public void setExpandedView(BubbleExpandedView expandedView) {
+ if (mExpandedView != null) {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "updating expandedView, resetting previous");
+ }
+ if (mCollapseAnimation != null) {
+ mCollapseAnimation.cancel();
+ }
+ if (mBackToExpandedAnimation != null) {
+ mBackToExpandedAnimation.cancel();
+ }
+ reset();
+ }
+ mExpandedView = expandedView;
+ }
+
+ @Override
+ public void updateDrag(float distance) {
+ if (mExpandedView != null) {
+ mDraggedAmount = OverScroll.dampedScroll(distance, mExpandedView.getContentHeight());
+
+ if (DEBUG_COLLAPSE_ANIMATOR && DEBUG_EXPANDED_VIEW_DRAGGING) {
+ Log.d(TAG, "updateDrag: distance=" + distance + " dragged=" + mDraggedAmount);
+ }
+
+ setCollapsedAmount(mDraggedAmount);
+
+ if (!mNotifiedAboutThreshold && isPastCollapseThreshold()) {
+ mNotifiedAboutThreshold = true;
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "notifying over collapse threshold");
+ }
+ vibrateIfEnabled();
+ }
+ }
+ }
+
+ @Override
+ public void setSwipeVelocity(float velocity) {
+ if (velocity < 0) {
+ mSwipeUpVelocity = Math.abs(velocity);
+ mSwipeDownVelocity = 0;
+ } else {
+ mSwipeUpVelocity = 0;
+ mSwipeDownVelocity = velocity;
+ }
+ }
+
+ @Override
+ public boolean shouldCollapse() {
+ if (mSwipeDownVelocity > mMinFlingVelocity) {
+ // Swipe velocity is positive and over fling velocity.
+ // This is a swipe down, always reset to expanded state, regardless of dragged amount.
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG,
+ "not collapsing expanded view, swipe down velocity: " + mSwipeDownVelocity
+ + " minV: " + mMinFlingVelocity);
+ }
+ return false;
+ }
+
+ if (mSwipeUpVelocity > mMinFlingVelocity) {
+ // Swiping up and over fling velocity, collapse the view.
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG,
+ "collapse expanded view, swipe up velocity: " + mSwipeUpVelocity + " minV: "
+ + mMinFlingVelocity);
+ }
+ return true;
+ }
+
+ if (isPastCollapseThreshold()) {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "collapse expanded view, past threshold, dragged: " + mDraggedAmount);
+ }
+ return true;
+ }
+
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "not collapsing expanded view");
+ }
+
+ return false;
+ }
+
+ @Override
+ public void animateCollapse(Runnable startStackCollapse, Runnable after) {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG,
+ "expandedView animate collapse swipeVel=" + mSwipeUpVelocity + " minFlingVel="
+ + mMinFlingVelocity);
+ }
+ if (mExpandedView != null) {
+ // Mark it as animating immediately to avoid updates to the view before animation starts
+ mExpandedView.setAnimating(true);
+
+ if (mCollapseAnimation != null) {
+ mCollapseAnimation.cancel();
+ }
+ mCollapseAnimation = createCollapseAnimation(mExpandedView, startStackCollapse, after);
+
+ if (mSwipeUpVelocity >= mMinFlingVelocity) {
+ int contentHeight = mExpandedView.getContentHeight();
+
+ // Use a temp animator to get adjusted duration value for swipe.
+ // This new value will be used to adjust animation times proportionally in the
+ // animator set. If we adjust animator set duration directly, all child animations
+ // will get the same animation time.
+ ValueAnimator tempAnimator = new ValueAnimator();
+ mFlingAnimationUtils.applyDismissing(tempAnimator, mCollapsedAmount, contentHeight,
+ mSwipeUpVelocity, (contentHeight - mCollapsedAmount));
+
+ float durationAdjustment =
+ (float) tempAnimator.getDuration() / COLLAPSE_DURATION_MS;
+
+ adjustAnimatorSetDuration(mCollapseAnimation, durationAdjustment);
+ mCollapseAnimation.setInterpolator(tempAnimator.getInterpolator());
+ }
+ mCollapseAnimation.start();
+ }
+ }
+
+ @Override
+ public void animateBackToExpanded() {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "expandedView animate back to expanded");
+ }
+ BubbleExpandedView expandedView = mExpandedView;
+ if (expandedView == null) {
+ return;
+ }
+
+ expandedView.setAnimating(true);
+
+ mBackToExpandedAnimation = new SpringAnimation(this, COLLAPSE_HEIGHT_PROPERTY);
+ mBackToExpandedAnimation.setSpring(new SpringForce()
+ .setStiffness(SpringForce.STIFFNESS_LOW)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_LOW_BOUNCY)
+ );
+ mBackToExpandedAnimation.addEndListener(new OneTimeEndListener() {
+ @Override
+ public void onAnimationEnd(DynamicAnimation animation, boolean canceled, float value,
+ float velocity) {
+ super.onAnimationEnd(animation, canceled, value, velocity);
+ mNotifiedAboutThreshold = false;
+ mBackToExpandedAnimation = null;
+ expandedView.setAnimating(false);
+ }
+ });
+ mBackToExpandedAnimation.setStartValue(mCollapsedAmount);
+ mBackToExpandedAnimation.animateToFinalPosition(0);
+ }
+
+ @Override
+ public void animateForImeVisibilityChange(boolean visible) {
+ if (mExpandedView != null) {
+ if (mBottomClipAnim != null) {
+ mBottomClipAnim.cancel();
+ }
+ int clip = 0;
+ if (visible) {
+ // Clip the expanded view at the top of the IME view
+ clip = mExpandedView.getContentBottomOnScreen() - mPositioner.getImeTop();
+ // Don't allow negative clip value
+ clip = Math.max(clip, 0);
+ }
+ mBottomClipAnim = ObjectAnimator.ofInt(mExpandedView, BOTTOM_CLIP_PROPERTY, clip);
+ mBottomClipAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mBottomClipAnim = null;
+ }
+ });
+ mBottomClipAnim.start();
+ }
+ }
+
+ @Override
+ public void reset() {
+ if (DEBUG_COLLAPSE_ANIMATOR) {
+ Log.d(TAG, "reset expandedView collapsed state");
+ }
+ if (mExpandedView == null) {
+ return;
+ }
+ mExpandedView.setAnimating(false);
+
+ if (mCollapseAnimation != null) {
+ mCollapseAnimation.cancel();
+ }
+ if (mBackToExpandedAnimation != null) {
+ mBackToExpandedAnimation.cancel();
+ }
+ mExpandedView.setContentAlpha(1);
+ mExpandedView.setBackgroundAlpha(1);
+ mExpandedView.setManageButtonAlpha(1);
+ setCollapsedAmount(0);
+ mExpandedView.setBottomClip(0);
+ mExpandedView.movePointerBy(0, 0);
+ mCollapsedAmount = 0;
+ mDraggedAmount = 0;
+ mSwipeUpVelocity = 0;
+ mSwipeDownVelocity = 0;
+ mNotifiedAboutThreshold = false;
+ }
+
+ private float getCollapsedAmount() {
+ return mCollapsedAmount;
+ }
+
+ private void setCollapsedAmount(float collapsed) {
+ if (mCollapsedAmount != collapsed) {
+ float previous = mCollapsedAmount;
+ mCollapsedAmount = collapsed;
+
+ if (mExpandedView != null) {
+ if (previous == 0) {
+ // View was not collapsed before. Apply z order change
+ mExpandedView.setSurfaceZOrderedOnTop(true);
+ mExpandedView.setAnimating(true);
+ }
+
+ mExpandedView.setTopClip((int) mCollapsedAmount);
+ // Move up with translationY. Use negative collapsed value
+ mExpandedView.setContentTranslationY(-mCollapsedAmount);
+ mExpandedView.setManageButtonTranslationY(-mCollapsedAmount);
+
+ if (mCollapsedAmount == 0) {
+ // View is no longer collapsed. Revert z order change
+ mExpandedView.setSurfaceZOrderedOnTop(false);
+ mExpandedView.setAnimating(false);
+ }
+ }
+ }
+ }
+
+ private boolean isPastCollapseThreshold() {
+ if (mExpandedView != null) {
+ return mDraggedAmount > mExpandedView.getContentHeight() * COLLAPSE_THRESHOLD;
+ }
+ return false;
+ }
+
+ private AnimatorSet createCollapseAnimation(BubbleExpandedView expandedView,
+ Runnable startStackCollapse, Runnable after) {
+ List<Animator> animatorList = new ArrayList<>();
+ animatorList.add(createHeightAnimation(expandedView));
+ animatorList.add(createManageButtonAnimation());
+ ObjectAnimator contentAlphaAnimation = createContentAlphaAnimation();
+ final boolean[] notified = {false};
+ contentAlphaAnimation.addUpdateListener(animation -> {
+ if (!notified[0] && animation.getAnimatedFraction() > STACK_COLLAPSE_THRESHOLD) {
+ notified[0] = true;
+ // Notify bubbles that they can start moving back to the collapsed position
+ startStackCollapse.run();
+ }
+ });
+ animatorList.add(contentAlphaAnimation);
+ animatorList.add(createBackgroundAlphaAnimation());
+
+ AnimatorSet animatorSet = new AnimatorSet();
+ animatorSet.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ after.run();
+ }
+ });
+ animatorSet.playTogether(animatorList);
+ return animatorSet;
+ }
+
+ private ValueAnimator createHeightAnimation(BubbleExpandedView expandedView) {
+ ValueAnimator animator = ValueAnimator.ofInt((int) mCollapsedAmount,
+ expandedView.getContentHeight());
+ animator.setInterpolator(Interpolators.EMPHASIZED_ACCELERATE);
+ animator.setDuration(COLLAPSE_DURATION_MS);
+ animator.addUpdateListener(anim -> setCollapsedAmount((int) anim.getAnimatedValue()));
+ return animator;
+ }
+
+ private ObjectAnimator createManageButtonAnimation() {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, MANAGE_BUTTON_ALPHA, 0f);
+ animator.setDuration(MANAGE_BUTTON_ANIM_DURATION_MS);
+ animator.setInterpolator(Interpolators.LINEAR);
+ return animator;
+ }
+
+ private ObjectAnimator createContentAlphaAnimation() {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, CONTENT_ALPHA, 0f);
+ animator.setDuration(CONTENT_OPACITY_ANIM_DURATION_MS);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.setStartDelay(CONTENT_OPACITY_ANIM_DELAY_MS);
+ return animator;
+ }
+
+ private ObjectAnimator createBackgroundAlphaAnimation() {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mExpandedView, BACKGROUND_ALPHA, 0f);
+ animator.setDuration(BACKGROUND_OPACITY_ANIM_DURATION_MS);
+ animator.setInterpolator(Interpolators.LINEAR);
+ animator.setStartDelay(BACKGROUND_OPACITY_ANIM_DELAY_MS);
+ return animator;
+ }
+
+ @SuppressLint("MissingPermission")
+ private void vibrateIfEnabled() {
+ if (mExpandedView != null) {
+ mExpandedView.performHapticFeedback(HapticFeedbackConstants.DRAG_CROSSING);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
new file mode 100644
index 000000000000..bb8a3aaaf551
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerStub.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+
+/**
+ * Stub implementation {@link ExpandedViewAnimationController} that does not animate the
+ * {@link BubbleExpandedView}
+ */
+public class ExpandedViewAnimationControllerStub implements ExpandedViewAnimationController {
+ @Override
+ public void setExpandedView(BubbleExpandedView expandedView) {
+ }
+
+ @Override
+ public void updateDrag(float distance) {
+ }
+
+ @Override
+ public void setSwipeVelocity(float velocity) {
+ }
+
+ @Override
+ public boolean shouldCollapse() {
+ return false;
+ }
+
+ @Override
+ public void animateCollapse(Runnable startStackCollapse, Runnable after) {
+ }
+
+ @Override
+ public void animateBackToExpanded() {
+ }
+
+ @Override
+ public void animateForImeVisibilityChange(boolean visible) {
+ }
+
+ @Override
+ public void reset() {
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java
new file mode 100644
index 000000000000..d4e76ed0282e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/OverScroll.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+/**
+ * Utility methods for overscroll damping and related effect.
+ *
+ * Copied from packages/apps/Launcher3/src/com/android/launcher3/touch/OverScroll.java
+ */
+public class OverScroll {
+
+ private static final float OVERSCROLL_DAMP_FACTOR = 0.07f;
+
+ /**
+ * This curve determines how the effect of scrolling over the limits of the page diminishes
+ * as the user pulls further and further from the bounds
+ *
+ * @param f The percentage of how much the user has overscrolled.
+ * @return A transformed percentage based on the influence curve.
+ */
+ private static float overScrollInfluenceCurve(float f) {
+ f -= 1.0f;
+ return f * f * f + 1.0f;
+ }
+
+ /**
+ * @param amount The original amount overscrolled.
+ * @param max The maximum amount that the View can overscroll.
+ * @return The dampened overscroll amount.
+ */
+ public static int dampedScroll(float amount, int max) {
+ if (Float.compare(amount, 0) == 0) return 0;
+
+ float f = amount / max;
+ f = f / (Math.abs(f)) * (overScrollInfluenceCurve(Math.abs(f)));
+
+ // Clamp this factor, f, to -1 < f < 1
+ if (Math.abs(f) >= 1) {
+ f /= Math.abs(f);
+ }
+
+ return Math.round(OVERSCROLL_DAMP_FACTOR * f * max);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
index c32733d4f73c..28c7367662a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayChangeController.java
@@ -16,11 +16,13 @@
package com.android.wm.shell.common;
+import android.annotation.Nullable;
import android.os.RemoteException;
import android.util.Slog;
-import android.view.IDisplayWindowRotationCallback;
-import android.view.IDisplayWindowRotationController;
+import android.view.IDisplayChangeWindowCallback;
+import android.view.IDisplayChangeWindowController;
import android.view.IWindowManager;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
@@ -40,17 +42,17 @@ public class DisplayChangeController {
private final ShellExecutor mMainExecutor;
private final IWindowManager mWmService;
- private final IDisplayWindowRotationController mControllerImpl;
+ private final IDisplayChangeWindowController mControllerImpl;
- private final CopyOnWriteArrayList<OnDisplayChangingListener> mRotationListener =
+ private final CopyOnWriteArrayList<OnDisplayChangingListener> mDisplayChangeListener =
new CopyOnWriteArrayList<>();
public DisplayChangeController(IWindowManager wmService, ShellExecutor mainExecutor) {
mMainExecutor = mainExecutor;
mWmService = wmService;
- mControllerImpl = new DisplayWindowRotationControllerImpl();
+ mControllerImpl = new DisplayChangeWindowControllerImpl();
try {
- mWmService.setDisplayWindowRotationController(mControllerImpl);
+ mWmService.setDisplayChangeWindowController(mControllerImpl);
} catch (RemoteException e) {
throw new RuntimeException("Unable to register rotation controller");
}
@@ -59,63 +61,64 @@ public class DisplayChangeController {
/**
* Adds a display rotation controller.
*/
- public void addRotationListener(OnDisplayChangingListener listener) {
- mRotationListener.add(listener);
+ public void addDisplayChangeListener(OnDisplayChangingListener listener) {
+ mDisplayChangeListener.add(listener);
}
/**
* Removes a display rotation controller.
*/
- public void removeRotationListener(OnDisplayChangingListener listener) {
- mRotationListener.remove(listener);
+ public void removeDisplayChangeListener(OnDisplayChangingListener listener) {
+ mDisplayChangeListener.remove(listener);
}
- /** Query all listeners for changes that should happen on rotation. */
- public void dispatchOnRotateDisplay(WindowContainerTransaction outWct, int displayId,
- final int fromRotation, final int toRotation) {
- for (OnDisplayChangingListener c : mRotationListener) {
- c.onRotateDisplay(displayId, fromRotation, toRotation, outWct);
+ /** Query all listeners for changes that should happen on display change. */
+ public void dispatchOnDisplayChange(WindowContainerTransaction outWct, int displayId,
+ int fromRotation, int toRotation, DisplayAreaInfo newDisplayAreaInfo) {
+ for (OnDisplayChangingListener c : mDisplayChangeListener) {
+ c.onDisplayChange(displayId, fromRotation, toRotation, newDisplayAreaInfo, outWct);
}
}
- private void onRotateDisplay(int displayId, final int fromRotation, final int toRotation,
- IDisplayWindowRotationCallback callback) {
+ private void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
WindowContainerTransaction t = new WindowContainerTransaction();
- dispatchOnRotateDisplay(t, displayId, fromRotation, toRotation);
+ dispatchOnDisplayChange(t, displayId, fromRotation, toRotation, newDisplayAreaInfo);
try {
- callback.continueRotateDisplay(toRotation, t);
+ callback.continueDisplayChange(t);
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to continue rotation", e);
+ Slog.e(TAG, "Failed to continue handling display change", e);
}
}
@BinderThread
- private class DisplayWindowRotationControllerImpl
- extends IDisplayWindowRotationController.Stub {
+ private class DisplayChangeWindowControllerImpl
+ extends IDisplayChangeWindowController.Stub {
@Override
- public void onRotateDisplay(int displayId, final int fromRotation,
- final int toRotation, IDisplayWindowRotationCallback callback) {
- mMainExecutor.execute(() -> {
- DisplayChangeController.this.onRotateDisplay(displayId, fromRotation, toRotation,
- callback);
- });
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo, IDisplayChangeWindowCallback callback) {
+ mMainExecutor.execute(() -> DisplayChangeController.this
+ .onDisplayChange(displayId, fromRotation, toRotation,
+ newDisplayAreaInfo, callback));
}
}
/**
* Give a listener a chance to queue up configuration changes to execute as part of a
- * display rotation. The contents of {@link #onRotateDisplay} must run synchronously.
+ * display rotation. The contents of {@link #onDisplayChange} must run synchronously.
*/
@ShellMainThread
public interface OnDisplayChangingListener {
/**
- * Called before the display is rotated. Contents of this method must run synchronously.
- * @param displayId Id of display that is rotating.
- * @param fromRotation starting rotation of the display.
- * @param toRotation target rotation of the display (after rotating).
+ * Called before the display size has changed.
+ * Contents of this method must run synchronously.
+ * @param displayId display id of the display that is under the change
+ * @param fromRotation rotation before the change
+ * @param toRotation rotation after the change
+ * @param newDisplayAreaInfo display area info after applying the update
* @param t A task transaction to populate.
*/
- void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction t);
+ void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index 4ba32e93fb3d..764936cceb01 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -156,14 +156,14 @@ public class DisplayController {
* Adds a display rotation controller.
*/
public void addDisplayChangingController(OnDisplayChangingListener controller) {
- mChangeController.addRotationListener(controller);
+ mChangeController.addDisplayChangeListener(controller);
}
/**
* Removes a display rotation controller.
*/
public void removeDisplayChangingController(OnDisplayChangingListener controller) {
- mChangeController.removeRotationListener(controller);
+ mChangeController.removeDisplayChangeListener(controller);
}
private void onDisplayAdded(int displayId) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 6a2acf438302..b3f62477077c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -20,6 +20,7 @@ import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.IntDef;
+import android.content.ComponentName;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Point;
@@ -324,7 +325,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged
}
@Override
- public void topFocusedWindowChanged(String packageName,
+ public void topFocusedWindowChanged(ComponentName component,
InsetsVisibilities requestedVisibilities) {
// Do nothing
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
index b6705446674a..f546f110e87c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java
@@ -16,6 +16,7 @@
package com.android.wm.shell.common;
+import android.content.ComponentName;
import android.os.RemoteException;
import android.util.Slog;
import android.util.SparseArray;
@@ -171,14 +172,14 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
}
}
- private void topFocusedWindowChanged(String packageName,
+ private void topFocusedWindowChanged(ComponentName component,
InsetsVisibilities requestedVisibilities) {
CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId);
if (listeners == null) {
return;
}
for (OnInsetsChangedListener listener : listeners) {
- listener.topFocusedWindowChanged(packageName, requestedVisibilities);
+ listener.topFocusedWindowChanged(component, requestedVisibilities);
}
}
@@ -186,10 +187,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
private class DisplayWindowInsetsControllerImpl
extends IDisplayWindowInsetsController.Stub {
@Override
- public void topFocusedWindowChanged(String packageName,
+ public void topFocusedWindowChanged(ComponentName component,
InsetsVisibilities requestedVisibilities) throws RemoteException {
mMainExecutor.execute(() -> {
- PerDisplay.this.topFocusedWindowChanged(packageName, requestedVisibilities);
+ PerDisplay.this.topFocusedWindowChanged(component, requestedVisibilities);
});
}
@@ -234,10 +235,10 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan
/**
* Called when top focused window changes to determine whether or not to take over insets
* control. Won't be called if config_remoteInsetsControllerControlsSystemBars is false.
- * @param packageName The name of the package that is open in the top focussed window.
+ * @param component The application component that is open in the top focussed window.
* @param requestedVisibilities The insets visibilities requested by the focussed window.
*/
- default void topFocusedWindowChanged(String packageName,
+ default void topFocusedWindowChanged(ComponentName component,
InsetsVisibilities requestedVisibilities) {}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
index fd3aa05cfc06..ec344d345139 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java
@@ -18,7 +18,9 @@ package com.android.wm.shell.common;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.Context;
import android.text.TextUtils;
+import android.view.SurfaceControl;
import android.view.View;
import com.android.internal.jank.InteractionJankMonitor;
@@ -44,6 +46,24 @@ public class InteractionJankMonitorUtils {
}
/**
+ * Begin a trace session.
+ *
+ * @param cujType the specific {@link InteractionJankMonitor.CujType}.
+ * @param context the context
+ * @param surface the surface to trace
+ * @param tag the tag to distinguish different flow of same type CUJ.
+ */
+ public static void beginTracing(@InteractionJankMonitor.CujType int cujType,
+ @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) {
+ final InteractionJankMonitor.Configuration.Builder builder =
+ InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface);
+ if (!TextUtils.isEmpty(tag)) {
+ builder.setTag(tag);
+ }
+ InteractionJankMonitor.getInstance().begin(builder);
+ }
+
+ /**
* End a trace session.
*
* @param cujType the specific {@link InteractionJankMonitor.CujType}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 6305959bb6ac..8bc16bcc9d9d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -206,12 +206,12 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
mSplitLayout = layout;
mSplitWindowManager = splitWindowManager;
mViewHost = viewHost;
- mDividerBounds.set(layout.getDividerBounds());
+ layout.getDividerBounds(mDividerBounds);
onInsetsChanged(insetsState, false /* animate */);
}
void onInsetsChanged(InsetsState insetsState, boolean animate) {
- mTempRect.set(mSplitLayout.getDividerBounds());
+ mSplitLayout.getDividerBounds(mTempRect);
final InsetsSource taskBarInsetsSource =
insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
// Only insets the divider bar with task bar when it's expanded so that the rounded corners
@@ -286,6 +286,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
setTouching();
mStartPos = touchPos;
mMoving = false;
+ mSplitLayout.onStartDragging();
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
@@ -301,7 +302,10 @@ public class DividerView extends FrameLayout implements View.OnTouchListener {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
releaseTouching();
- if (!mMoving) break;
+ if (!mMoving) {
+ mSplitLayout.onDraggingCancelled();
+ break;
+ }
mVelocityTracker.addMovement(event);
mVelocityTracker.computeCurrentVelocity(1000 /* units */);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index 484294ab295b..74f8bf9ac863 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -50,12 +50,15 @@ import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.R;
import com.android.wm.shell.common.SurfaceUtils;
+import java.util.function.Consumer;
+
/**
* Handles split decor like showing resizing hint for a specific split.
*/
public class SplitDecorManager extends WindowlessWindowManager {
private static final String TAG = SplitDecorManager.class.getSimpleName();
private static final String RESIZING_BACKGROUND_SURFACE_NAME = "ResizingBackground";
+ private static final String GAP_BACKGROUND_SURFACE_NAME = "GapBackground";
private static final long FADE_DURATION = 133;
private final IconProvider mIconProvider;
@@ -67,6 +70,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
private SurfaceControl mHostLeash;
private SurfaceControl mIconLeash;
private SurfaceControl mBackgroundLeash;
+ private SurfaceControl mGapBackgroundLeash;
private boolean mShown;
private boolean mIsResizing;
@@ -141,6 +145,10 @@ public class SplitDecorManager extends WindowlessWindowManager {
t.remove(mBackgroundLeash);
mBackgroundLeash = null;
}
+ if (mGapBackgroundLeash != null) {
+ t.remove(mGapBackgroundLeash);
+ mGapBackgroundLeash = null;
+ }
mHostLeash = null;
mIcon = null;
mResizingIconView = null;
@@ -150,7 +158,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
/** Showing resizing hint. */
public void onResizing(ActivityManager.RunningTaskInfo resizingTask, Rect newBounds,
- SurfaceControl.Transaction t) {
+ Rect sideBounds, SurfaceControl.Transaction t) {
if (mResizingIconView == null) {
return;
}
@@ -176,6 +184,19 @@ public class SplitDecorManager extends WindowlessWindowManager {
.setLayer(mBackgroundLeash, Integer.MAX_VALUE - 1);
}
+ if (mGapBackgroundLeash == null) {
+ final boolean isLandscape = newBounds.height() == sideBounds.height();
+ final int left = isLandscape ? mBounds.width() : 0;
+ final int top = isLandscape ? 0 : mBounds.height();
+ mGapBackgroundLeash = SurfaceUtils.makeColorLayer(mHostLeash,
+ GAP_BACKGROUND_SURFACE_NAME, mSurfaceSession);
+ // Fill up another side bounds area.
+ t.setColor(mGapBackgroundLeash, getResizingBackgroundColor(resizingTask))
+ .setLayer(mGapBackgroundLeash, Integer.MAX_VALUE - 2)
+ .setPosition(mGapBackgroundLeash, left, top)
+ .setWindowCrop(mGapBackgroundLeash, sideBounds.width(), sideBounds.height());
+ }
+
if (mIcon == null && resizingTask.topActivityInfo != null) {
mIcon = mIconProvider.getIcon(resizingTask.topActivityInfo);
mResizingIconView.setImageDrawable(mIcon);
@@ -193,7 +214,7 @@ public class SplitDecorManager extends WindowlessWindowManager {
newBounds.height() / 2 - mIconSize / 2);
if (animate) {
- startFadeAnimation(show, false /* isResized */);
+ startFadeAnimation(show, null /* finishedConsumer */);
mShown = show;
}
}
@@ -224,14 +245,29 @@ public class SplitDecorManager extends WindowlessWindowManager {
mFadeAnimator.cancel();
}
if (mShown) {
- startFadeAnimation(false /* show */, true /* isResized */);
+ fadeOutDecor(null /* finishedCallback */);
} else {
// Decor surface is hidden so release it directly.
releaseDecor(t);
}
}
- private void startFadeAnimation(boolean show, boolean isResized) {
+ /** Fade-out decor surface with animation end callback, if decor is hidden, run the callback
+ * directly. */
+ public void fadeOutDecor(Runnable finishedCallback) {
+ if (mShown) {
+ startFadeAnimation(false /* show */, transaction -> {
+ releaseDecor(transaction);
+ if (finishedCallback != null) finishedCallback.run();
+ });
+ mShown = false;
+ } else {
+ if (finishedCallback != null) finishedCallback.run();
+ }
+ }
+
+ private void startFadeAnimation(boolean show,
+ Consumer<SurfaceControl.Transaction> finishedConsumer) {
final SurfaceControl.Transaction animT = new SurfaceControl.Transaction();
mFadeAnimator = ValueAnimator.ofFloat(0f, 1f);
mFadeAnimator.setDuration(FADE_DURATION);
@@ -249,7 +285,9 @@ public class SplitDecorManager extends WindowlessWindowManager {
@Override
public void onAnimationStart(@NonNull Animator animation) {
if (show) {
- animT.show(mBackgroundLeash).show(mIconLeash).apply();
+ animT.show(mBackgroundLeash).show(mIconLeash).show(mGapBackgroundLeash).apply();
+ } else {
+ animT.hide(mGapBackgroundLeash).apply();
}
}
@@ -263,8 +301,8 @@ public class SplitDecorManager extends WindowlessWindowManager {
animT.hide(mIconLeash);
}
}
- if (isResized) {
- releaseDecor(animT);
+ if (finishedConsumer != null) {
+ finishedConsumer.accept(animT);
}
animT.apply();
animT.close();
@@ -280,6 +318,11 @@ public class SplitDecorManager extends WindowlessWindowManager {
mBackgroundLeash = null;
}
+ if (mGapBackgroundLeash != null) {
+ t.remove(mGapBackgroundLeash);
+ mGapBackgroundLeash = null;
+ }
+
if (mIcon != null) {
mResizingIconView.setVisibility(View.GONE);
mResizingIconView.setImageDrawable(null);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index bae23575297f..40cf9a32d7a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -24,6 +24,7 @@ import static android.view.WindowManager.DOCKED_LEFT;
import static android.view.WindowManager.DOCKED_RIGHT;
import static android.view.WindowManager.DOCKED_TOP;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
import static com.android.internal.policy.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
@@ -31,9 +32,11 @@ import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.annotation.NonNull;
import android.app.ActivityManager;
@@ -55,7 +58,6 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.policy.DividerSnapAlgorithm;
import com.android.internal.policy.DockedDividerUtils;
import com.android.wm.shell.R;
@@ -78,6 +80,8 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public static final int PARALLAX_DISMISSING = 1;
public static final int PARALLAX_ALIGN_CENTER = 2;
+ private static final int FLING_ANIMATION_DURATION = 250;
+
private final int mDividerWindowWidth;
private final int mDividerInsets;
private final int mDividerSize;
@@ -85,7 +89,9 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private final Rect mTempRect = new Rect();
private final Rect mRootBounds = new Rect();
private final Rect mDividerBounds = new Rect();
+ // Bounds1 final position should be always at top or left
private final Rect mBounds1 = new Rect();
+ // Bounds2 final position should be always at bottom or right
private final Rect mBounds2 = new Rect();
private final Rect mWinBounds1 = new Rect();
private final Rect mWinBounds2 = new Rect();
@@ -178,6 +184,11 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return outBounds;
}
+ /** Gets root bounds of the whole split layout */
+ public Rect getRootBounds() {
+ return new Rect(mRootBounds);
+ }
+
/** Gets bounds of divider window with screen based coordinate. */
public Rect getDividerBounds() {
return new Rect(mDividerBounds);
@@ -190,6 +201,44 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
return outBounds;
}
+ /** Gets bounds of the primary split with screen based coordinate on the param Rect. */
+ public void getBounds1(Rect rect) {
+ rect.set(mBounds1);
+ }
+
+ /** Gets bounds of the primary split with parent based coordinate on the param Rect. */
+ public void getRefBounds1(Rect rect) {
+ getBounds1(rect);
+ rect.offset(-mRootBounds.left, -mRootBounds.top);
+ }
+
+ /** Gets bounds of the secondary split with screen based coordinate on the param Rect. */
+ public void getBounds2(Rect rect) {
+ rect.set(mBounds2);
+ }
+
+ /** Gets bounds of the secondary split with parent based coordinate on the param Rect. */
+ public void getRefBounds2(Rect rect) {
+ getBounds2(rect);
+ rect.offset(-mRootBounds.left, -mRootBounds.top);
+ }
+
+ /** Gets root bounds of the whole split layout on the param Rect. */
+ public void getRootBounds(Rect rect) {
+ rect.set(mRootBounds);
+ }
+
+ /** Gets bounds of divider window with screen based coordinate on the param Rect. */
+ public void getDividerBounds(Rect rect) {
+ rect.set(mDividerBounds);
+ }
+
+ /** Gets bounds of divider window with parent based coordinate on the param Rect. */
+ public void getRefDividerBounds(Rect rect) {
+ getDividerBounds(rect);
+ rect.offset(-mRootBounds.left, -mRootBounds.top);
+ }
+
/** Returns leash of the current divider bar. */
@Nullable
public SurfaceControl getDividerLeash() {
@@ -270,28 +319,35 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
updateBounds(mDividePosition);
}
- /** Updates recording bounds of divider window and both of the splits. */
private void updateBounds(int position) {
- mDividerBounds.set(mRootBounds);
- mBounds1.set(mRootBounds);
- mBounds2.set(mRootBounds);
+ updateBounds(position, mBounds1, mBounds2, mDividerBounds, true /* setEffectBounds */);
+ }
+
+ /** Updates recording bounds of divider window and both of the splits. */
+ private void updateBounds(int position, Rect bounds1, Rect bounds2, Rect dividerBounds,
+ boolean setEffectBounds) {
+ dividerBounds.set(mRootBounds);
+ bounds1.set(mRootBounds);
+ bounds2.set(mRootBounds);
final boolean isLandscape = isLandscape(mRootBounds);
if (isLandscape) {
position += mRootBounds.left;
- mDividerBounds.left = position - mDividerInsets;
- mDividerBounds.right = mDividerBounds.left + mDividerWindowWidth;
- mBounds1.right = position;
- mBounds2.left = mBounds1.right + mDividerSize;
+ dividerBounds.left = position - mDividerInsets;
+ dividerBounds.right = dividerBounds.left + mDividerWindowWidth;
+ bounds1.right = position;
+ bounds2.left = bounds1.right + mDividerSize;
} else {
position += mRootBounds.top;
- mDividerBounds.top = position - mDividerInsets;
- mDividerBounds.bottom = mDividerBounds.top + mDividerWindowWidth;
- mBounds1.bottom = position;
- mBounds2.top = mBounds1.bottom + mDividerSize;
+ dividerBounds.top = position - mDividerInsets;
+ dividerBounds.bottom = dividerBounds.top + mDividerWindowWidth;
+ bounds1.bottom = position;
+ bounds2.top = bounds1.bottom + mDividerSize;
+ }
+ DockedDividerUtils.sanitizeStackBounds(bounds1, true /** topLeft */);
+ DockedDividerUtils.sanitizeStackBounds(bounds2, false /** topLeft */);
+ if (setEffectBounds) {
+ mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
}
- DockedDividerUtils.sanitizeStackBounds(mBounds1, true /** topLeft */);
- DockedDividerUtils.sanitizeStackBounds(mBounds2, false /** topLeft */);
- mSurfaceEffectPolicy.applyDividerPosition(position, isLandscape);
}
/** Inflates {@link DividerView} on the root surface. */
@@ -394,11 +450,13 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
switch (snapTarget.flag) {
case FLAG_DISMISS_START:
flingDividePosition(currentPosition, snapTarget.position,
- () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */));
+ () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
+ EXIT_REASON_DRAG_DIVIDER));
break;
case FLAG_DISMISS_END:
flingDividePosition(currentPosition, snapTarget.position,
- () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */));
+ () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
+ EXIT_REASON_DRAG_DIVIDER));
break;
default:
flingDividePosition(currentPosition, snapTarget.position,
@@ -407,6 +465,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
}
}
+ void onStartDragging() {
+ InteractionJankMonitorUtils.beginTracing(CUJ_SPLIT_SCREEN_RESIZE, mContext,
+ getDividerLeash(), null /* tag */);
+ }
+
+ void onDraggingCancelled() {
+ InteractionJankMonitorUtils.cancelTracing(CUJ_SPLIT_SCREEN_RESIZE);
+ }
+
void onDoubleTappedDivider() {
mSplitLayoutHandler.onDoubleTappedDivider();
}
@@ -423,28 +490,48 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
private DividerSnapAlgorithm getSnapAlgorithm(Context context, Rect rootBounds,
@Nullable Rect stableInsets) {
final boolean isLandscape = isLandscape(rootBounds);
+ final Rect insets = stableInsets != null ? stableInsets : getDisplayInsets(context);
+
+ // Make split axis insets value same as the larger one to avoid bounds1 and bounds2
+ // have difference after split switching for solving issues on non-resizable app case.
+ if (isLandscape) {
+ final int largerInsets = Math.max(insets.left, insets.right);
+ insets.set(largerInsets, insets.top, largerInsets, insets.bottom);
+ } else {
+ final int largerInsets = Math.max(insets.top, insets.bottom);
+ insets.set(insets.left, largerInsets, insets.right, largerInsets);
+ }
+
return new DividerSnapAlgorithm(
context.getResources(),
rootBounds.width(),
rootBounds.height(),
mDividerSize,
!isLandscape,
- stableInsets != null ? stableInsets : getDisplayInsets(context),
+ insets,
isLandscape ? DOCKED_LEFT : DOCKED_TOP /* dockSide */);
}
+ /** Fling divider from current position to end or start position then exit */
+ public void flingDividerToDismiss(boolean toEnd, int reason) {
+ final int target = toEnd ? mDividerSnapAlgorithm.getDismissEndTarget().position
+ : mDividerSnapAlgorithm.getDismissStartTarget().position;
+ flingDividePosition(getDividePosition(), target,
+ () -> mSplitLayoutHandler.onSnappedToDismiss(toEnd, reason));
+ }
+
@VisibleForTesting
void flingDividePosition(int from, int to, @Nullable Runnable flingFinishedCallback) {
if (from == to) {
// No animation run, still callback to stop resizing.
mSplitLayoutHandler.onLayoutSizeChanged(this);
+ InteractionJankMonitorUtils.endTracing(
+ CUJ_SPLIT_SCREEN_RESIZE);
return;
}
- InteractionJankMonitorUtils.beginTracing(InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE,
- mSplitWindowManager.getDividerView(), "Divider fling");
ValueAnimator animator = ValueAnimator
.ofInt(from, to)
- .setDuration(250);
+ .setDuration(FLING_ANIMATION_DURATION);
animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
animator.addUpdateListener(
animation -> updateDivideBounds((int) animation.getAnimatedValue()));
@@ -455,7 +542,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
flingFinishedCallback.run();
}
InteractionJankMonitorUtils.endTracing(
- InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE);
+ CUJ_SPLIT_SCREEN_RESIZE);
}
@Override
@@ -466,6 +553,86 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
animator.start();
}
+ /** Swich both surface position with animation. */
+ public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
+ SurfaceControl leash2, Runnable finishCallback) {
+ final boolean isLandscape = isLandscape();
+ final Rect insets = getDisplayInsets(mContext);
+ insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
+ isLandscape ? insets.right : 0, isLandscape ? 0 : insets.bottom);
+
+ final int dividerPos = mDividerSnapAlgorithm.calculateNonDismissingSnapTarget(
+ isLandscape ? mBounds2.width() : mBounds2.height()).position;
+ final Rect distBounds1 = new Rect();
+ final Rect distBounds2 = new Rect();
+ final Rect distDividerBounds = new Rect();
+ // Compute dist bounds.
+ updateBounds(dividerPos, distBounds2, distBounds1, distDividerBounds,
+ false /* setEffectBounds */);
+ // Offset to real position under root container.
+ distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
+ distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
+ distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
+ // DO NOT move to insets area for smooth animation.
+ distBounds1.set(distBounds1.left, distBounds1.top,
+ distBounds1.right - insets.right, distBounds1.bottom - insets.bottom);
+ distBounds2.set(distBounds2.left + insets.left, distBounds2.top + insets.top,
+ distBounds2.right, distBounds2.bottom);
+
+ ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
+ false /* alignStart */);
+ ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
+ true /* alignStart */);
+ ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
+ distDividerBounds, true /* alignStart */);
+
+ AnimatorSet set = new AnimatorSet();
+ set.playTogether(animator1, animator2, animator3);
+ set.setDuration(FLING_ANIMATION_DURATION);
+ set.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mDividePosition = dividerPos;
+ updateBounds(mDividePosition);
+ finishCallback.run();
+ }
+ });
+ set.start();
+ }
+
+ private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
+ Rect start, Rect end, boolean alignStart) {
+ Rect tempStart = new Rect(start);
+ Rect tempEnd = new Rect(end);
+ final float diffX = tempEnd.left - tempStart.left;
+ final float diffY = tempEnd.top - tempStart.top;
+ final float diffWidth = tempEnd.width() - tempStart.width();
+ final float diffHeight = tempEnd.height() - tempStart.height();
+ ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+ animator.addUpdateListener(animation -> {
+ if (leash == null) return;
+
+ final float scale = (float) animation.getAnimatedValue();
+ final float distX = tempStart.left + scale * diffX;
+ final float distY = tempStart.top + scale * diffY;
+ final int width = (int) (tempStart.width() + scale * diffWidth);
+ final int height = (int) (tempStart.height() + scale * diffHeight);
+ if (alignStart) {
+ t.setPosition(leash, distX, distY);
+ t.setWindowCrop(leash, width, height);
+ } else {
+ final int offsetX = width - tempStart.width();
+ final int offsetY = height - tempStart.height();
+ t.setPosition(leash, distX + offsetX, distY + offsetY);
+ mTempRect.set(0, 0, width, height);
+ mTempRect.offsetTo(-offsetX, -offsetY);
+ t.setCrop(leash, mTempRect);
+ }
+ t.apply();
+ });
+ return animator;
+ }
+
private static Rect getDisplayInsets(Context context) {
return context.getSystemService(WindowManager.class)
.getMaximumWindowMetrics()
@@ -504,15 +671,15 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
boolean applyResizingOffset) {
final SurfaceControl dividerLeash = getDividerLeash();
if (dividerLeash != null) {
- mTempRect.set(getRefDividerBounds());
+ getRefDividerBounds(mTempRect);
t.setPosition(dividerLeash, mTempRect.left, mTempRect.top);
// Resets layer of divider bar to make sure it is always on top.
t.setLayer(dividerLeash, Integer.MAX_VALUE);
}
- mTempRect.set(getRefBounds1());
+ getRefBounds1(mTempRect);
t.setPosition(leash1, mTempRect.left, mTempRect.top)
.setWindowCrop(leash1, mTempRect.width(), mTempRect.height());
- mTempRect.set(getRefBounds2());
+ getRefBounds2(mTempRect);
t.setPosition(leash2, mTempRect.left, mTempRect.top)
.setWindowCrop(leash2, mTempRect.width(), mTempRect.height());
@@ -560,31 +727,23 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
ActivityManager.RunningTaskInfo taskInfo1, ActivityManager.RunningTaskInfo taskInfo2) {
if (offsetX == 0 && offsetY == 0) {
wct.setBounds(taskInfo1.token, mBounds1);
- wct.setAppBounds(taskInfo1.token, null);
wct.setScreenSizeDp(taskInfo1.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
wct.setBounds(taskInfo2.token, mBounds2);
- wct.setAppBounds(taskInfo2.token, null);
wct.setScreenSizeDp(taskInfo2.token,
SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
} else {
- mTempRect.set(taskInfo1.configuration.windowConfiguration.getBounds());
+ getBounds1(mTempRect);
mTempRect.offset(offsetX, offsetY);
wct.setBounds(taskInfo1.token, mTempRect);
- mTempRect.set(taskInfo1.configuration.windowConfiguration.getAppBounds());
- mTempRect.offset(offsetX, offsetY);
- wct.setAppBounds(taskInfo1.token, mTempRect);
wct.setScreenSizeDp(taskInfo1.token,
taskInfo1.configuration.screenWidthDp,
taskInfo1.configuration.screenHeightDp);
- mTempRect.set(taskInfo2.configuration.windowConfiguration.getBounds());
+ getBounds2(mTempRect);
mTempRect.offset(offsetX, offsetY);
wct.setBounds(taskInfo2.token, mTempRect);
- mTempRect.set(taskInfo2.configuration.windowConfiguration.getAppBounds());
- mTempRect.offset(offsetX, offsetY);
- wct.setAppBounds(taskInfo2.token, mTempRect);
wct.setScreenSizeDp(taskInfo2.token,
taskInfo2.configuration.screenWidthDp,
taskInfo2.configuration.screenHeightDp);
@@ -602,7 +761,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange
public interface SplitLayoutHandler {
/** Calls when dismissing split. */
- void onSnappedToDismiss(boolean snappedToEnd);
+ void onSnappedToDismiss(boolean snappedToEnd, int reason);
/**
* Calls when resizing the split bounds.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index 9b614875119b..afc706ee9c8e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -15,6 +15,11 @@
*/
package com.android.wm.shell.common.split;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
import android.annotation.IntDef;
/** Helper utility class of methods and constants that are available to be imported in Launcher. */
@@ -44,4 +49,10 @@ public class SplitScreenConstants {
})
public @interface SplitPosition {
}
+
+ public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
+ public static final int[] CONTROLLED_WINDOWING_MODES =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
+ public static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
+ {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
index 99b32a677abe..db8d9d423aca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUIController.java
@@ -39,9 +39,10 @@ import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListen
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.compatui.CompatUIWindowManager.CompatUIHintsState;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import java.lang.ref.WeakReference;
@@ -58,7 +59,7 @@ import dagger.Lazy;
* activities are in compatibility mode.
*/
public class CompatUIController implements OnDisplaysChangedListener,
- DisplayImeController.ImePositionProcessor {
+ DisplayImeController.ImePositionProcessor, KeyguardChangeListener {
/** Callback for compat UI interaction. */
public interface CompatUICallback {
@@ -100,13 +101,13 @@ public class CompatUIController implements OnDisplaysChangedListener,
private final SparseArray<WeakReference<Context>> mDisplayContextCache = new SparseArray<>(0);
private final Context mContext;
+ private final ShellController mShellController;
private final DisplayController mDisplayController;
private final DisplayInsetsController mDisplayInsetsController;
private final DisplayImeController mImeController;
private final SyncTransactionQueue mSyncQueue;
private final ShellExecutor mMainExecutor;
private final Lazy<Transitions> mTransitionsLazy;
- private final CompatUIImpl mImpl = new CompatUIImpl();
private CompatUICallback mCallback;
@@ -118,6 +119,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
private boolean mKeyguardShowing;
public CompatUIController(Context context,
+ ShellController shellController,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
DisplayImeController imeController,
@@ -125,6 +127,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
ShellExecutor mainExecutor,
Lazy<Transitions> transitionsLazy) {
mContext = context;
+ mShellController = shellController;
mDisplayController = displayController;
mDisplayInsetsController = displayInsetsController;
mImeController = imeController;
@@ -134,11 +137,7 @@ public class CompatUIController implements OnDisplaysChangedListener,
mDisplayController.addDisplayWindowListener(this);
mImeController.addPositionProcessor(this);
mCompatUIHintsState = new CompatUIHintsState();
- }
-
- /** Returns implementation of {@link CompatUI}. */
- public CompatUI asCompatUI() {
- return mImpl;
+ shellController.addKeyguardChangeListener(this);
}
/** Sets the callback for UI interactions. */
@@ -223,9 +222,10 @@ public class CompatUIController implements OnDisplaysChangedListener,
layout -> layout.updateVisibility(showOnDisplay(displayId)));
}
- @VisibleForTesting
- void onKeyguardShowingChanged(boolean showing) {
- mKeyguardShowing = showing;
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mKeyguardShowing = visible;
// Hide the compat UIs when keyguard is showing.
forAllLayouts(layout -> layout.updateVisibility(showOnDisplay(layout.getDisplayId())));
}
@@ -373,19 +373,6 @@ public class CompatUIController implements OnDisplaysChangedListener,
}
}
- /**
- * The interface for calls from outside the Shell, within the host process.
- */
- @ExternalThread
- private class CompatUIImpl implements CompatUI {
- @Override
- public void onKeyguardShowingChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- CompatUIController.this.onKeyguardShowingChanged(showing);
- });
- }
- }
-
/** An implementation of {@link OnInsetsChangedListener} for a given display id. */
private class PerDisplayOnInsetsChangedListener implements OnInsetsChangedListener {
final int mDisplayId;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/README.txt b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/README.txt
deleted file mode 100644
index 1cd69edf7cd2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/README.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-The dagger modules in this directory can be included by the host SysUI using the Shell library for
-explicity injection of Shell components. Apps using this library are not required to use these
-dagger modules for setup, but it is recommended for them to include them as needed.
-
-The modules are currently inherited as such:
-
-+- WMShellBaseModule (common shell features across SysUI)
- |
- +- WMShellModule (handheld)
- |
- +- TvPipModule (tv pip)
- |
- +- TvWMShellModule (tv) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
index 1ea5e21a2c1e..81904e291ad1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java
@@ -48,6 +48,7 @@ import com.android.wm.shell.pip.tv.TvPipNotificationController;
import com.android.wm.shell.pip.tv.TvPipTaskOrganizer;
import com.android.wm.shell.pip.tv.TvPipTransition;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
@@ -64,6 +65,7 @@ public abstract class TvPipModule {
@Provides
static Optional<Pip> providePip(
Context context,
+ ShellController shellController,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
@@ -81,6 +83,7 @@ public abstract class TvPipModule {
return Optional.of(
TvPipController.create(
context,
+ shellController,
tvPipBoundsState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
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 db6131a17114..5dd51499ef19 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
@@ -29,17 +29,14 @@ import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootDisplayAreaOrganizer;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.ShellCommandHandlerImpl;
-import com.android.wm.shell.ShellInit;
-import com.android.wm.shell.ShellInitImpl;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewFactory;
import com.android.wm.shell.TaskViewFactoryController;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairs;
-import com.android.wm.shell.apppairs.AppPairsController;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.back.BackAnimationController;
import com.android.wm.shell.bubbles.BubbleController;
@@ -58,20 +55,14 @@ import com.android.wm.shell.common.annotations.ShellAnimationThread;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
-import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.compatui.CompatUIController;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
import com.android.wm.shell.displayareahelper.DisplayAreaHelperController;
-import com.android.wm.shell.draganddrop.DragAndDrop;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
@@ -87,11 +78,12 @@ import com.android.wm.shell.startingsurface.StartingSurface;
import com.android.wm.shell.startingsurface.StartingWindowController;
import com.android.wm.shell.startingsurface.StartingWindowTypeAlgorithm;
import com.android.wm.shell.startingsurface.phone.PhoneStartingWindowTypeAlgorithm;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
import java.util.Optional;
@@ -163,16 +155,13 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
static DragAndDropController provideDragAndDropController(Context context,
- DisplayController displayController, UiEventLogger uiEventLogger,
- IconProvider iconProvider, @ShellMainThread ShellExecutor mainExecutor) {
- return new DragAndDropController(context, displayController, uiEventLogger, iconProvider,
- mainExecutor);
- }
-
- @WMSingleton
- @Provides
- static Optional<DragAndDrop> provideDragAndDrop(DragAndDropController dragAndDropController) {
- return Optional.of(dragAndDropController.asDragAndDrop());
+ ShellController shellController,
+ DisplayController displayController,
+ UiEventLogger uiEventLogger,
+ IconProvider iconProvider,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new DragAndDropController(context, shellController, displayController, uiEventLogger,
+ iconProvider, mainExecutor);
}
@WMSingleton
@@ -180,9 +169,11 @@ public abstract class WMShellBaseModule {
static ShellTaskOrganizer provideShellTaskOrganizer(@ShellMainThread ShellExecutor mainExecutor,
Context context,
CompatUIController compatUI,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional
) {
- return new ShellTaskOrganizer(mainExecutor, context, compatUI, recentTasksOptional);
+ return new ShellTaskOrganizer(mainExecutor, context, compatUI, unfoldAnimationController,
+ recentTasksOptional);
}
@WMSingleton
@@ -194,25 +185,23 @@ public abstract class WMShellBaseModule {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasksOptional
) {
return new KidsModeTaskOrganizer(mainExecutor, mainHandler, context, syncTransactionQueue,
- displayController, displayInsetsController, recentTasksOptional);
- }
-
- @WMSingleton
- @Provides static Optional<CompatUI> provideCompatUI(CompatUIController compatUIController) {
- return Optional.of(compatUIController.asCompatUI());
+ displayController, displayInsetsController, unfoldAnimationController,
+ recentTasksOptional);
}
@WMSingleton
@Provides
static CompatUIController provideCompatUIController(Context context,
+ ShellController shellController,
DisplayController displayController, DisplayInsetsController displayInsetsController,
DisplayImeController imeController, SyncTransactionQueue syncQueue,
@ShellMainThread ShellExecutor mainExecutor, Lazy<Transitions> transitionsLazy) {
- return new CompatUIController(context, displayController, displayInsetsController,
- imeController, syncQueue, mainExecutor, transitionsLazy);
+ return new CompatUIController(context, shellController, displayController,
+ displayInsetsController, imeController, syncQueue, mainExecutor, transitionsLazy);
}
@WMSingleton
@@ -294,13 +283,11 @@ public abstract class WMShellBaseModule {
static FullscreenTaskListener provideFullscreenTaskListener(
@DynamicOverride Optional<FullscreenTaskListener> fullscreenTaskListener,
SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> optionalFullscreenUnfoldController,
Optional<RecentTasksController> recentTasksOptional) {
if (fullscreenTaskListener.isPresent()) {
return fullscreenTaskListener.get();
} else {
- return new FullscreenTaskListener(syncQueue, optionalFullscreenUnfoldController,
- recentTasksOptional);
+ return new FullscreenTaskListener(syncQueue, recentTasksOptional);
}
}
@@ -314,31 +301,33 @@ public abstract class WMShellBaseModule {
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FullscreenUnfoldController optionalFullscreenUnfoldController();
+ abstract UnfoldAnimationController optionalUnfoldController();
@WMSingleton
@Provides
- static Optional<FullscreenUnfoldController> provideFullscreenUnfoldController(
- @DynamicOverride Optional<FullscreenUnfoldController> fullscreenUnfoldController,
+ static Optional<UnfoldAnimationController> provideUnfoldController(
+ @DynamicOverride Lazy<Optional<UnfoldAnimationController>>
+ fullscreenUnfoldController,
Optional<ShellUnfoldProgressProvider> progressProvider) {
if (progressProvider.isPresent()
&& progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
- return fullscreenUnfoldController;
+ return fullscreenUnfoldController.get();
}
return Optional.empty();
}
+ @BindsOptionalOf
+ @DynamicOverride
+ abstract UnfoldTransitionHandler optionalUnfoldTransitionHandler();
+
@WMSingleton
@Provides
static Optional<UnfoldTransitionHandler> provideUnfoldTransitionHandler(
Optional<ShellUnfoldProgressProvider> progressProvider,
- TransactionPool transactionPool,
- Transitions transitions,
- @ShellMainThread ShellExecutor executor) {
- if (progressProvider.isPresent()) {
- return Optional.of(
- new UnfoldTransitionHandler(progressProvider.get(), transactionPool, executor,
- transitions));
+ @DynamicOverride Lazy<Optional<UnfoldTransitionHandler>> handler) {
+ if (progressProvider.isPresent()
+ && progressProvider.get() != ShellUnfoldProgressProvider.NO_PROVIDER) {
+ return handler.get();
}
return Optional.empty();
}
@@ -350,12 +339,12 @@ public abstract class WMShellBaseModule {
// Workaround for dynamic overriding with a default implementation, see {@link DynamicOverride}
@BindsOptionalOf
@DynamicOverride
- abstract FreeformTaskListener optionalFreeformTaskListener();
+ abstract FreeformTaskListener<?> optionalFreeformTaskListener();
@WMSingleton
@Provides
- static Optional<FreeformTaskListener> provideFreeformTaskListener(
- @DynamicOverride Optional<FreeformTaskListener> freeformTaskListener,
+ static Optional<FreeformTaskListener<?>> provideFreeformTaskListener(
+ @DynamicOverride Optional<FreeformTaskListener<?>> freeformTaskListener,
Context context) {
if (FreeformTaskListener.isFreeformEnabled(context)) {
return freeformTaskListener;
@@ -369,17 +358,12 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static Optional<HideDisplayCutout> provideHideDisplayCutout(
- Optional<HideDisplayCutoutController> hideDisplayCutoutController) {
- return hideDisplayCutoutController.map((controller) -> controller.asHideDisplayCutout());
- }
-
- @WMSingleton
- @Provides
static Optional<HideDisplayCutoutController> provideHideDisplayCutoutController(Context context,
- DisplayController displayController, @ShellMainThread ShellExecutor mainExecutor) {
+ ShellController shellController, DisplayController displayController,
+ @ShellMainThread ShellExecutor mainExecutor) {
return Optional.ofNullable(
- HideDisplayCutoutController.create(context, displayController, mainExecutor));
+ HideDisplayCutoutController.create(context, shellController, displayController,
+ mainExecutor));
}
//
@@ -408,23 +392,6 @@ public abstract class WMShellBaseModule {
}
//
- // Task to Surface communication
- //
-
- @WMSingleton
- @Provides
- static Optional<TaskSurfaceHelper> provideTaskSurfaceHelper(
- Optional<TaskSurfaceHelperController> taskSurfaceController) {
- return taskSurfaceController.map((controller) -> controller.asTaskSurfaceHelper());
- }
-
- @Provides
- static Optional<TaskSurfaceHelperController> provideTaskSurfaceHelperController(
- ShellTaskOrganizer taskOrganizer, @ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(new TaskSurfaceHelperController(taskOrganizer, mainExecutor));
- }
-
- //
// Pip (optional feature)
//
@@ -444,8 +411,8 @@ public abstract class WMShellBaseModule {
@WMSingleton
@Provides
- static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper() {
- return new PipSurfaceTransactionHelper();
+ static PipSurfaceTransactionHelper providePipSurfaceTransactionHelper(Context context) {
+ return new PipSurfaceTransactionHelper(context);
}
@WMSingleton
@@ -561,29 +528,6 @@ public abstract class WMShellBaseModule {
return Optional.empty();
}
- // Legacy split (optional feature)
-
- @WMSingleton
- @Provides
- static Optional<LegacySplitScreen> provideLegacySplitScreen(
- Optional<LegacySplitScreenController> splitScreenController) {
- return splitScreenController.map((controller) -> controller.asLegacySplitScreen());
- }
-
- @BindsOptionalOf
- abstract LegacySplitScreenController optionalLegacySplitScreenController();
-
- // App Pairs (optional feature)
-
- @WMSingleton
- @Provides
- static Optional<AppPairs> provideAppPairs(Optional<AppPairsController> appPairsController) {
- return appPairsController.map((controller) -> controller.asAppPairs());
- }
-
- @BindsOptionalOf
- abstract AppPairsController optionalAppPairs();
-
//
// Starting window
//
@@ -644,19 +588,43 @@ public abstract class WMShellBaseModule {
taskViewTransitions);
}
+
//
- // Misc
+ // ActivityEmbedding
+ //
+
+ @WMSingleton
+ @Provides
+ static Optional<ActivityEmbeddingController> provideActivityEmbeddingController(
+ Context context, Transitions transitions) {
+ return Optional.of(new ActivityEmbeddingController(context, transitions));
+ }
+
+ //
+ // SysUI -> Shell interface
//
@WMSingleton
@Provides
- static ShellInit provideShellInit(ShellInitImpl impl) {
- return impl.asShellInit();
+ static ShellInterface provideShellSysuiCallbacks(ShellController shellController) {
+ return shellController.asShell();
}
@WMSingleton
@Provides
- static ShellInitImpl provideShellInitImpl(DisplayController displayController,
+ static ShellController provideShellController(@ShellMainThread ShellExecutor mainExecutor) {
+ return new ShellController(mainExecutor);
+ }
+
+ //
+ // Misc
+ //
+
+ @WMSingleton
+ @Provides
+ static ShellInit provideShellInitImpl(
+ ShellController shellController,
+ DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
DragAndDropController dragAndDropController,
@@ -664,17 +632,18 @@ public abstract class WMShellBaseModule {
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
- Optional<FullscreenUnfoldController> appUnfoldTransitionController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformTaskListener> freeformTaskListener,
+ Optional<FreeformTaskListener<?>> freeformTaskListener,
Optional<RecentTasksController> recentTasksOptional,
+ Optional<ActivityEmbeddingController> activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellInitImpl(displayController,
+ return new ShellInit(shellController,
+ displayController,
displayImeController,
displayInsetsController,
dragAndDropController,
@@ -682,44 +651,33 @@ public abstract class WMShellBaseModule {
kidsModeTaskOrganizer,
bubblesOptional,
splitScreenOptional,
- appPairsOptional,
pipTouchHandlerOptional,
fullscreenTaskListener,
- appUnfoldTransitionController,
+ unfoldAnimationController,
unfoldTransitionHandler,
freeformTaskListener,
recentTasksOptional,
+ activityEmbeddingOptional,
transitions,
startingWindow,
mainExecutor);
}
- /**
- * Note, this is only optional because we currently pass this to the SysUI component scope and
- * for non-primary users, we may inject a null-optional for that dependency.
- */
- @WMSingleton
- @Provides
- static Optional<ShellCommandHandler> provideShellCommandHandler(ShellCommandHandlerImpl impl) {
- return Optional.of(impl.asShellCommandHandler());
- }
-
@WMSingleton
@Provides
- static ShellCommandHandlerImpl provideShellCommandHandlerImpl(
+ static ShellCommandHandler provideShellCommandHandlerImpl(
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHandedController> oneHandedOptional,
Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<AppPairsController> appPairsOptional,
Optional<RecentTasksController> recentTasksOptional,
@ShellMainThread ShellExecutor mainExecutor) {
- return new ShellCommandHandlerImpl(shellTaskOrganizer, kidsModeTaskOrganizer,
- legacySplitScreenOptional, splitScreenOptional, pipOptional, oneHandedOptional,
- hideDisplayCutout, appPairsOptional, recentTasksOptional, mainExecutor);
+ return new ShellCommandHandler(shellController, shellTaskOrganizer,
+ kidsModeTaskOrganizer, splitScreenOptional, pipOptional, oneHandedOptional,
+ hideDisplayCutout, recentTasksOptional, mainExecutor);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
index cc741d3896a2..35a309a8352c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java
@@ -27,6 +27,7 @@ import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Trace;
+import android.view.Choreographer;
import androidx.annotation.Nullable;
@@ -144,6 +145,25 @@ public abstract class WMShellConcurrencyModule {
}
/**
+ * Provide a Shell main-thread {@link Choreographer} with the app vsync.
+ *
+ * @param executor the executor of the shell main thread
+ */
+ @WMSingleton
+ @Provides
+ @ShellMainThread
+ public static Choreographer provideShellMainChoreographer(
+ @ShellMainThread ShellExecutor executor) {
+ try {
+ final Choreographer[] choreographer = new Choreographer[1];
+ executor.executeBlocking(() -> choreographer[0] = Choreographer.getInstance());
+ return choreographer[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to obtain main Choreographer.", e);
+ }
+ }
+
+ /**
* Provide a Shell animation-thread Executor.
*/
@WMSingleton
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 b3799e2cf8d9..0dc293b0265f 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
@@ -16,11 +16,11 @@
package com.android.wm.shell.dagger;
-import android.animation.AnimationHandler;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.os.Handler;
import android.os.UserManager;
+import android.view.Choreographer;
import android.view.WindowManager;
import com.android.internal.jank.InteractionJankMonitor;
@@ -31,8 +31,11 @@ import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.apppairs.AppPairsController;
import com.android.wm.shell.bubbles.BubbleController;
+import com.android.wm.shell.bubbles.BubbleData;
+import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleLogger;
+import com.android.wm.shell.bubbles.BubblePositioner;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
import com.android.wm.shell.common.DisplayInsetsController;
@@ -43,13 +46,10 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.SystemWindows;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ChoreographerSfVsync;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.pip.PipAnimationController;
@@ -67,19 +67,30 @@ import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.pip.phone.PhonePipMenuController;
import com.android.wm.shell.pip.phone.PipController;
+import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm;
import com.android.wm.shell.pip.phone.PipMotionHelper;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
-import com.android.wm.shell.splitscreen.StageTaskUnfoldController;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
-
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+import com.android.wm.shell.unfold.qualifier.UnfoldShellTransition;
+import com.android.wm.shell.unfold.qualifier.UnfoldTransition;
+import com.android.wm.shell.windowdecor.CaptionWindowDecorViewModel;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
+
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
-import javax.inject.Provider;
-
+import dagger.Binds;
import dagger.Lazy;
import dagger.Module;
import dagger.Provides;
@@ -93,16 +104,40 @@ import dagger.Provides;
* dependencies should go into {@link WMShellBaseModule}.
*/
@Module(includes = WMShellBaseModule.class)
-public class WMShellModule {
+public abstract class WMShellModule {
//
// Bubbles
//
+ @WMSingleton
+ @Provides
+ static BubbleLogger provideBubbleLogger(UiEventLogger uiEventLogger) {
+ return new BubbleLogger(uiEventLogger);
+ }
+
+ @WMSingleton
+ @Provides
+ static BubblePositioner provideBubblePositioner(Context context,
+ WindowManager windowManager) {
+ return new BubblePositioner(context, windowManager);
+ }
+
+ @WMSingleton
+ @Provides
+ static BubbleData provideBubbleData(Context context,
+ BubbleLogger logger,
+ BubblePositioner positioner,
+ @ShellMainThread ShellExecutor mainExecutor) {
+ return new BubbleData(context, logger, positioner, mainExecutor);
+ }
+
// Note: Handler needed for LauncherApps.register
@WMSingleton
@Provides
static BubbleController provideBubbleController(Context context,
+ ShellController shellController,
+ BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
IStatusBarService statusBarService,
WindowManager windowManager,
@@ -110,8 +145,9 @@ public class WMShellModule {
UserManager userManager,
LauncherApps launcherApps,
TaskStackListenerImpl taskStackListener,
- UiEventLogger uiEventLogger,
+ BubbleLogger logger,
ShellTaskOrganizer organizer,
+ BubblePositioner positioner,
DisplayController displayController,
@DynamicOverride Optional<OneHandedController> oneHandedOptional,
DragAndDropController dragAndDropController,
@@ -120,24 +156,48 @@ public class WMShellModule {
@ShellBackgroundThread ShellExecutor bgExecutor,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- return BubbleController.create(context, null /* synchronizer */,
- floatingContentCoordinator, statusBarService, windowManager,
- windowManagerShellWrapper, userManager, launcherApps, taskStackListener,
- uiEventLogger, organizer, displayController, oneHandedOptional,
- dragAndDropController, mainExecutor, mainHandler, bgExecutor,
+ return new BubbleController(context, shellController, data, null /* synchronizer */,
+ floatingContentCoordinator,
+ new BubbleDataRepository(context, launcherApps, mainExecutor),
+ statusBarService, windowManager, windowManagerShellWrapper, userManager,
+ launcherApps, logger, taskStackListener, organizer, positioner, displayController,
+ oneHandedOptional, dragAndDropController, mainExecutor, mainHandler, bgExecutor,
taskViewTransitions, syncQueue);
}
//
+ // Window decoration
+ //
+
+ @WMSingleton
+ @Provides
+ static WindowDecorViewModel<?> provideWindowDecorViewModel(
+ Context context,
+ @ShellMainThread Handler mainHandler,
+ @ShellMainThread Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue) {
+ return new CaptionWindowDecorViewModel(
+ context,
+ mainHandler,
+ mainChoreographer,
+ taskOrganizer,
+ displayController,
+ syncQueue);
+ }
+
+ //
// Freeform
//
@WMSingleton
@Provides
@DynamicOverride
- static FreeformTaskListener provideFreeformTaskListener(
+ static FreeformTaskListener<?> provideFreeformTaskListener(
+ WindowDecorViewModel<?> windowDecorViewModel,
SyncTransactionQueue syncQueue) {
- return new FreeformTaskListener(syncQueue);
+ return new FreeformTaskListener<>(windowDecorViewModel, syncQueue);
}
//
@@ -150,12 +210,14 @@ public class WMShellModule {
@Provides
@DynamicOverride
static OneHandedController provideOneHandedController(Context context,
+ ShellController shellController,
WindowManager windowManager, DisplayController displayController,
DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
UiEventLogger uiEventLogger, InteractionJankMonitor jankMonitor,
@ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler) {
- return OneHandedController.create(context, windowManager, displayController, displayLayout,
- taskStackListener, jankMonitor, uiEventLogger, mainExecutor, mainHandler);
+ return OneHandedController.create(context, shellController, windowManager,
+ displayController, displayLayout, taskStackListener, jankMonitor, uiEventLogger,
+ mainExecutor, mainHandler);
}
//
@@ -166,6 +228,7 @@ public class WMShellModule {
@Provides
@DynamicOverride
static SplitScreenController provideSplitScreenController(
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer,
@@ -174,37 +237,11 @@ public class WMShellModule {
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> stageTaskUnfoldControllerProvider) {
- return new SplitScreenController(shellTaskOrganizer, syncQueue, context,
+ Optional<RecentTasksController> recentTasks) {
+ return new SplitScreenController(shellController, shellTaskOrganizer, syncQueue, context,
rootTaskDisplayAreaOrganizer, mainExecutor, displayController, displayImeController,
displayInsetsController, transitions, transactionPool, iconProvider,
- recentTasks, stageTaskUnfoldControllerProvider);
- }
-
- @WMSingleton
- @Provides
- static LegacySplitScreenController provideLegacySplitScreen(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController displayImeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- @ShellMainThread ShellExecutor mainExecutor,
- @ChoreographerSfVsync AnimationHandler sfVsyncAnimationHandler) {
- return new LegacySplitScreenController(context, displayController, systemWindows,
- displayImeController, transactionPool, shellTaskOrganizer, syncQueue,
- taskStackListener, transitions, mainExecutor, sfVsyncAnimationHandler);
- }
-
- @WMSingleton
- @Provides
- static AppPairsController provideAppPairs(ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, DisplayController displayController,
- @ShellMainThread ShellExecutor mainExecutor,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController) {
- return new AppPairsController(shellTaskOrganizer, syncQueue, displayController,
- mainExecutor, displayImeController, displayInsetsController);
+ recentTasks);
}
//
@@ -213,19 +250,23 @@ public class WMShellModule {
@WMSingleton
@Provides
- static Optional<Pip> providePip(Context context, DisplayController displayController,
+ static Optional<Pip> providePip(Context context,
+ ShellController shellController, DisplayController displayController,
PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper, PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
Optional<OneHandedController> oneHandedController,
@ShellMainThread ShellExecutor mainExecutor) {
- return Optional.ofNullable(PipController.create(context, displayController,
- pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState,
- pipMediaController, phonePipMenuController, pipTaskOrganizer,
+ return Optional.ofNullable(PipController.create(context, shellController, displayController,
+ pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState,
+ pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
pipTouchHandler, pipTransitionController, windowManagerShellWrapper,
taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor));
}
@@ -244,6 +285,12 @@ public class WMShellModule {
@WMSingleton
@Provides
+ static PipKeepClearAlgorithm providePipKeepClearAlgorithm() {
+ return new PipKeepClearAlgorithm();
+ }
+
+ @WMSingleton
+ @Provides
static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context,
PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) {
return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm);
@@ -351,40 +398,77 @@ public class WMShellModule {
//
// Unfold transition
//
-
@WMSingleton
@Provides
@DynamicOverride
- static FullscreenUnfoldController provideFullscreenUnfoldController(
- Context context,
+ static UnfoldAnimationController provideUnfoldAnimationController(
Optional<ShellUnfoldProgressProvider> progressProvider,
- Lazy<UnfoldBackgroundController> unfoldBackgroundController,
- DisplayInsetsController displayInsetsController,
+ TransactionPool transactionPool,
+ @UnfoldTransition SplitTaskUnfoldAnimator splitAnimator,
+ FullscreenUnfoldTaskAnimator fullscreenAnimator,
+ Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
@ShellMainThread ShellExecutor mainExecutor
) {
- return new FullscreenUnfoldController(context, mainExecutor,
- unfoldBackgroundController.get(), progressProvider.get(),
+ final List<UnfoldTaskAnimator> animators = new ArrayList<>();
+ animators.add(splitAnimator);
+ animators.add(fullscreenAnimator);
+
+ return new UnfoldAnimationController(
+ transactionPool,
+ progressProvider.get(),
+ animators,
+ unfoldTransitionHandler,
+ mainExecutor
+ );
+ }
+
+
+ @Provides
+ static FullscreenUnfoldTaskAnimator provideFullscreenUnfoldTaskAnimator(
+ Context context,
+ UnfoldBackgroundController unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController
+ ) {
+ return new FullscreenUnfoldTaskAnimator(context, unfoldBackgroundController,
displayInsetsController);
}
@Provides
- static Optional<StageTaskUnfoldController> provideStageTaskUnfoldController(
- Optional<ShellUnfoldProgressProvider> progressProvider,
+ static SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimatorBase(
Context context,
- TransactionPool transactionPool,
- Lazy<UnfoldBackgroundController> unfoldBackgroundController,
- DisplayInsetsController displayInsetsController,
- @ShellMainThread ShellExecutor mainExecutor
+ UnfoldBackgroundController backgroundController,
+ @ShellMainThread ShellExecutor executor,
+ Lazy<Optional<SplitScreenController>> splitScreenOptional,
+ DisplayInsetsController displayInsetsController
) {
- return progressProvider.map(shellUnfoldTransitionProgressProvider ->
- new StageTaskUnfoldController(
- context,
- transactionPool,
- shellUnfoldTransitionProgressProvider,
- displayInsetsController,
- unfoldBackgroundController.get(),
- mainExecutor
- ));
+ return new SplitTaskUnfoldAnimator(context, executor, splitScreenOptional,
+ backgroundController, displayInsetsController);
+ }
+
+ @WMSingleton
+ @UnfoldShellTransition
+ @Binds
+ abstract SplitTaskUnfoldAnimator provideShellSplitTaskUnfoldAnimator(
+ SplitTaskUnfoldAnimator splitTaskUnfoldAnimator);
+
+ @WMSingleton
+ @UnfoldTransition
+ @Binds
+ abstract SplitTaskUnfoldAnimator provideSplitTaskUnfoldAnimator(
+ SplitTaskUnfoldAnimator splitTaskUnfoldAnimator);
+
+ @WMSingleton
+ @Provides
+ @DynamicOverride
+ static UnfoldTransitionHandler provideUnfoldTransitionHandler(
+ Optional<ShellUnfoldProgressProvider> progressProvider,
+ FullscreenUnfoldTaskAnimator animator,
+ @UnfoldShellTransition SplitTaskUnfoldAnimator unfoldAnimator,
+ TransactionPool transactionPool,
+ Transitions transitions,
+ @ShellMainThread ShellExecutor executor) {
+ return new UnfoldTransitionHandler(progressProvider.get(), animator,
+ unfoldAnimator, transactionPool, executor, transitions);
}
@WMSingleton
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
new file mode 100644
index 000000000000..73a7348d5aca
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/README.md
@@ -0,0 +1,18 @@
+# Window Manager Shell Readme
+
+The following docs present more detail about the implementation of the WMShell library (in no
+particular order):
+
+1) [What is the Shell](overview.md)
+2) [Integration with SystemUI & Launcher](sysui.md)
+3) [Usage of Dagger](dagger.md)
+4) [Threading model in the Shell](threading.md)
+5) [Making changes in the Shell](changes.md)
+6) [Extending the Shell for Products/OEMs](extending.md)
+7) [Debugging in the Shell](debugging.md)
+8) [Testing in the Shell](testing.md)
+
+Todo
+- Per-feature docs
+- Feature flagging
+- Best practices \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
new file mode 100644
index 000000000000..f4e2f20f4637
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/changes.md
@@ -0,0 +1,73 @@
+# Making changes in the Shell
+
+---
+
+## Code reviews
+
+In addition to the individual reviewers who are most familiar with the changes you are making,
+please also add [wm-code-reviewers@google.com](http://g/wm-code-reviewers) to keep other WM folks
+in the loop.
+
+## Adding new code
+
+### Internal Shell utility classes
+If the new component is used only within the WMShell library, then there are no special
+considerations, go ahead and add it (in the `com.android.wm.shell.common` package for example)
+and make sure the appropriate [unit tests](testing.md) are added.
+
+### Internal Shell components
+If the new component is to be used by other components/features within the Shell library, then
+you can create an appropriate package for this component to add your new code. The current
+pattern is to have a single `<Component name>Controller` that handles the initialization of the
+component.
+
+As mentioned in the [Dagger usage](dagger.md) docs, you need to determine whether it should go into:
+- `WMShellBaseModule` for components that other base & product components will depend on
+- or `WMShellModule`, `TvWmShellModule`, etc. for product specific components that no base
+ components depend on
+
+### SysUI accessible components
+In addition to doing the above, you will also need to provide an interface for calling to SysUI
+from the Shell and vice versa. The current pattern is to have a parallel `Optional<Component name>`
+interface that the `<Component name>Controller` implements and handles on the main Shell thread.
+
+In addition, because components accessible to SysUI injection are explicitly listed, you'll have to
+add an appropriate method in `WMComponent` to get the interface and update the `Builder` in
+`SysUIComponent` to take the interface so it can be injected in SysUI code. The binding between
+the two is done in `SystemUIFactory#init()` which will need to be updated as well.
+
+### Launcher accessible components
+Because Launcher is not a part of SystemUI and is a separate process, exposing controllers to
+Launcher requires a new AIDL interface to be created and implemented by the controller. The
+implementation of the stub interface in the controller otherwise behaves similar to the interface
+to SysUI where it posts the work to the main Shell thread.
+
+### Component initialization
+To initialize the component:
+- On the Shell side, update `ShellInitImpl` to get a signal to initialize when the SysUI is started
+- On the SysUI side, update `WMShell` to setup any bindings for the component that depend on
+ SysUI code
+
+### General Do's & Dont's
+Do:
+- Do add unit tests for all new components
+- Do keep controllers simple and break them down as needed
+
+Don't:
+- **Don't** do initialization in the constructor, only do initialization in the init callbacks.
+ Otherwise it complicates the building of the dependency graph.
+- **Don't** create dependencies from base-module components on specific features (the base module
+ is intended for use with all products)
+ - Try adding a mechanism to register and listen for changes from the base module component instead
+- **Don't** add blocking synchronous calls in the SysUI interface between Shell & SysUI
+ - Try adding a push-mechanism to share data, or an async callback to request data
+
+### Exposing shared code for use in Launcher
+Launcher doesn't currently build against the Shell library, but needs to have access to some shared
+AIDL interfaces and constants. Currently, all AIDL files, and classes under the
+`com.android.wm.shell.util` package are automatically built into the `SystemUISharedLib` that
+Launcher uses.
+
+If the new code doesn't fall into those categories, they can be added explicitly in the Shell's
+[Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp) file under the
+`wm_shell_util-sources` filegroup. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
new file mode 100644
index 000000000000..6c01d962adc9
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/dagger.md
@@ -0,0 +1,50 @@
+# Usage of Dagger in the Shell library
+
+---
+
+## Dependencies
+
+Dagger is not required to use the Shell library, but it has a lot of obvious benefits:
+
+- Not having to worry about how to instantiate all the dependencies of a class, especially as
+ dependencies evolve (ie. product controller depends on base controller)
+- Can create boundaries within the same app to encourage better code modularity
+
+As such, the Shell also tries to provide some reasonable out-of-the-box modules for use with Dagger.
+
+## Modules
+
+All the Dagger related code in the Shell can be found in the `com.android.wm.shell.dagger` package,
+this is intentional as it keeps the "magic" in a single location. The explicit nature of how
+components in the shell are provided is as a result a bit more verbose, but it makes it easy for
+developers to jump into a few select files and understand how different components are provided
+(especially as products override components).
+
+The module dependency tree looks a bit like:
+- [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java)
+ (provides threading-related components)
+ - [WMShellBaseModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java)
+ (provides components that are likely common to all products, ie. DisplayController,
+ Transactions, etc.)
+ - [WMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java)
+ (phone/tablet specific components only)
+ - [TvPipModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java)
+ (PIP specific components for TV)
+ - [TvWMShellModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java)
+ (TV specific components only)
+ - etc.
+
+Ideally features could be abstracted out into their own modules and included as needed by each
+product.
+
+## Overriding base components
+
+In some rare cases, there are base components that can change behavior depending on which
+product it runs on. If there are hooks that can be added to the component, that is the
+preferable approach.
+
+The alternative is to use the [@DynamicOverride](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/DynamicOverride.java)
+annotation to allow the product module to provide an implementation that the base module can
+reference. This is most useful if the existence of the entire component is controlled by the
+product and the override implementation is optional (there is a default implementation). More
+details can be found in the class's javadoc. \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
new file mode 100644
index 000000000000..52f0c4222b64
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/debugging.md
@@ -0,0 +1,69 @@
+# Debugging in the Shell
+
+---
+
+## Logging & ProtoLogs
+
+The interactions in the Shell can be pretty complicated, so having good logging is crucial to
+debugging problems that arise (especially in dogfood). The Shell uses the same efficient Protolog
+mechanism as WM Core, which can be enabled at runtime on debug devices.
+
+**TLDR**&nbsp; Don’t use Logs or Slogs except for error cases, Protologs are much more flexible,
+easy to add and easy to use
+
+### Adding a new ProtoLog
+Update `ShellProtoLogGroup` to include a new log group (ie. NEW_FEATURE) for the content you want to
+log. ProtoLog log calls mirror Log.v/d/e(), and take a format message and arguments:
+```java
+ProtoLog.v(NEW_FEATURE, "Test log w/ params: %d %s", 1, “a”)
+```
+This code itself will not compile by itself, but the `protologtool` will preprocess the file when
+building to check the log state (is enabled) before printing the print format style log.
+
+**Notes**
+- ProtoLogs currently only work from soong builds (ie. via make/mp). We need to reimplement the
+ tool for use with SysUI-studio
+- Non-text ProtoLogs are not currently supported with the Shell library (you can't view them with
+ traces in Winscope)
+
+### Enabling ProtoLog command line logging
+Run these commands to enable protologs for both WM Core and WM Shell to print to logcat.
+```shell
+adb shell wm logging enable-text NEW_FEATURE
+adb shell wm logging disable-text NEW_FEATURE
+```
+
+## Winscope Tracing
+
+The Winscope tool is extremely useful in determining what is happening on-screen in both
+WindowManager and SurfaceFlinger. Follow [go/winscope](http://go/winscope-help) to learn how to
+use the tool.
+
+In addition, there is limited preliminary support for Winscope tracing componetns in the Shell,
+which involves adding trace fields to [wm_shell_trace.proto](frameworks/base/libs/WindowManager/Shell/proto/wm_shell_trace.proto)
+file and ensure it is updated as a part of `WMShell#writeToProto`.
+
+Tracing can be started via the shell command (to be added to the Winscope tool as needed):
+```shell
+adb shell cmd statusbar tracing start
+adb shell cmd statusbar tracing stop
+```
+
+## Dumps
+
+Because the Shell library is built as a part of SystemUI, dumping the state is currently done as a
+part of dumping the SystemUI service. Dumping the Shell specific data can be done by specifying the
+WMShell SysUI service:
+
+```shell
+adb shell dumpsys activity service SystemUIService WMShell
+```
+
+If information should be added to the dump, make updates to:
+- `WMShell` if you are dumping SysUI state
+- `ShellCommandHandler` if you are dumping Shell state
+
+## Debugging in Android Studio
+
+If you are using the [go/sysui-studio](http://go/sysui-studio) project, then you can debug Shell
+code directly from Android Studio like any other app.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md
new file mode 100644
index 000000000000..061ae00e2b25
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/extending.md
@@ -0,0 +1,13 @@
+# Extending the Shell for Products/OEMs
+
+---
+
+## General Do's & Dont's
+
+Do:
+- &nbsp;
+
+Don't
+- **Don't** override classes provided by WMShellBaseModule, it makes it difficult to make
+ simple changes to the Shell library base modules which are shared by all products
+ - If possible add mechanisms to modify the base class behavior \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
new file mode 100644
index 000000000000..a88ef6aea2ec
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/overview.md
@@ -0,0 +1,58 @@
+# What is the WindowManager Shell
+
+---
+
+## Motivation
+
+The primary motivation for the WindowManager Shell (WMShell) library is to effectively scale
+WindowManager by making it easy&trade; and safe to create windowing features to fit the needs of
+various Android products and form factors.
+
+To achieve this, WindowManager separates the policy of managing windows (WMCore) from the
+presentation of surfaces (WMShell) and provides a minimal interface boundary for the two to
+communicate.
+
+## Who is using the library?
+
+Currently, the WMShell library is used to drive the windowing experience on handheld
+(phones & tablets), TV, Auto, Arc++, and Wear to varying degrees.
+
+## Where does the code live
+
+The core WMShell library code is currently located in the [frameworks/base/libs/WindowManager/Shell](frameworks/base/libs/WindowManager/Shell)
+directory and is included as a part dependency of the host SystemUI apk.
+
+## How do I build the Shell library
+
+The library can be built directly by running (using [go/makepush](http://go/makepush)):
+```shell
+mp :WindowManager-Shell
+```
+But this is mainly useful for inspecting the contents of the library or verifying it builds. The
+various targets can be found in the Shell library's [Android.bp](frameworks/base/libs/WindowManager/Shell/Android.bp)
+file.
+
+Normally, you would build it as a part of the host SystemUI, for example via commandline:
+```shell
+# Phone SystemUI variant
+mp sysuig
+# Building Shell & SysUI changes along w/ framework changes
+mp core services sysuig
+```
+
+Or preferably, if you are making WMShell/SysUI only changes (no other framework changes), then
+building via [go/sysui-studio](http://go/sysui-studio) allows for very quick iteration (one click
+build and push of SysUI in < 30s).
+
+If you are making framework changes and are using `aidegen` to set up your platform IDE, make sure
+to include the appropriate directories to build, for example:
+```shell
+# frameworks/base will include base/libs/WindowManager/Shell and base/packages/SystemUI
+aidegen frameworks/base \
+ vendor/<oem>/packages/SystemUI \
+ ...
+```
+
+## Other useful links
+- [go/o-o-summit-20](go/o-o-summit-20) (Video presentations from the WM team)
+- [go/o-o-summit-21](go/o-o-summit-21) \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
new file mode 100644
index 000000000000..0dd50b1bee68
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/sysui.md
@@ -0,0 +1,65 @@
+# Shell & SystemUI
+
+---
+
+## Setup
+
+The SystemUI of various products depend on and build against the WM Shell library. To ensure
+that we don't inadvertently build dependencies between the Shell library and one particular
+product (ie. handheld SysUI), we deliberately separate the initialization of the WM Shell
+component from the SysUI component when set up through Dagger.
+
+**TLDR**&nbsp; Initialize everything as needed in the WM component scope and export only well
+defined interfaces to SysUI.
+
+## Initialization
+
+There are more details in the Dagger docs, but the general overview of the SysUI/Shell
+initialization flow is such:
+
+1) SysUI Global scope is initialize (see `GlobalModule` and its included modules)
+2) WM Shell scope is initialized, for example
+ 1) On phones: `WMComponent` includes `WMShellModule` which includes `WMShellBaseModule`
+ (common to all SysUI)
+ 2) On TVs: `TvWMComponent` includes `TvWMShellModule` which includes `WMShellBaseModule`
+ 3) etc.
+3) SysUI explicitly passes interfaces provided from the `WMComponent` to `SysUIComponent` via
+ the `SysUIComponent#Builder`, then builds the SysUI scoped components
+4) `WMShell` is the SystemUI “service” (in the SysUI scope) that initializes with the app after the
+SystemUI part of the dependency graph has been created. It contains the binding code between the
+interfaces provided by the Shell and the rest of SystemUI.
+5) SysUI can inject the interfaces into its own components
+
+More detail can be found in [go/wm-sysui-dagger](http://go/wm-sysui-dagger).
+
+## Interfaces to Shell components
+
+Within the same process, the WM Shell components can be running on a different thread than the main
+SysUI thread (disabled on certain products). This introduces challenges where we have to be
+careful about how SysUI calls into the Shell and vice versa.
+
+As a result, we enforce explicit interfaces between SysUI and Shell components, and the
+implementations of the interfaces on each side need to post to the right thread before it calls
+into other code.
+
+For example, you might have:
+1) (Shell) ShellFeature interface to be used from SysUI
+2) (Shell) ShellFeatureController handles logic, implements ShellFeature interface and posts to
+ main Shell thread
+3) SysUI application init injects Optional<ShellFeature> as an interface to SysUI to call
+4) (SysUI) SysUIFeature depends on ShellFeature interface
+5) (SysUI) SysUIFeature injects Optional<ShellFeature>, and sets up a callback for the Shell to
+ call, and the callback posts to the main SysUI thread
+
+Adding an interface to a Shell component may seem like a lot of boiler plate, but is currently
+necessary to maintain proper threading and logic isolation.
+
+## Configuration changes & other SysUI events
+
+Aside from direct calls into Shell controllers for exposed features, the Shell also receives
+common event callbacks from SysUI via the `ShellController`. This includes things like:
+
+- Configuration changes
+- TODO: Shell init
+- TODO: Shell command
+- TODO: Keyguard events \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
new file mode 100644
index 000000000000..8a80333facc4
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/testing.md
@@ -0,0 +1,49 @@
+# Testing
+
+---
+
+## Unit tests
+
+New WM Shell unit tests can be added to the
+[Shell/tests/unittest](frameworks/base/libs/WindowManager/Shell/tests/unittest) directory, and can
+be run via command line using `atest`:
+```shell
+atest WMShellUnitTests
+```
+
+If you use the SysUI Studio project, you can run and debug tests directly in the source files
+(click on the little arrows next to the test class or test method).
+
+These unit tests are run as a part of WindowManager presubmit, and the dashboards for these unit
+tests tests can be found at [go/wm-tests](http://go/wm-tests).
+
+This [GCL file](http://go/wm-unit-tests-gcl) configures the tests being run on the server.
+
+## Flicker tests
+
+Flicker tests are tests that perform actions and make assertions on the state in Window Manager
+and SurfaceFlinger traces captured during the run.
+
+New WM Shell Flicker tests can be added to the
+[Shell/tests/flicker](frameworks/base/libs/WindowManager/Shell/tests/flicker) directory, and can
+be run via command line using `atest`:
+```shell
+atest WMShellFlickerTests
+```
+
+**Note**: Currently Flicker tests can only be run from the commandline and not via SysUI Studio
+
+A subset of the flicker tests tests are run as a part of WindowManager presubmit, and the
+dashboards for these tests tests can be found at [go/wm-tests-flicker](http://go/wm-tests-flicker).
+
+## CTS tests
+
+Some windowing features also have CTS tests to ensure consistent behavior across OEMs. For example:
+- Picture-in-Picture:
+ [PinnedStackTests](cts/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java)
+- etc.
+
+These can also be run via commandline only using `atest`, for example:
+```shell
+atest PinnedStackTests
+``` \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
new file mode 100644
index 000000000000..eac748894432
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/docs/threading.md
@@ -0,0 +1,83 @@
+# Threading
+
+---
+
+## Boundaries
+
+```text
+ Thread boundary
+ |
+ WM Shell | SystemUI
+ |
+ |
+FeatureController <-> FeatureInterface <--|--> WMShell <-> SysUI
+ | (^post to shell thread) | (^post to main thread)
+ ... |
+ | |
+ OtherControllers |
+```
+
+## Threads
+
+We currently have multiple threads in use in the Shell library depending on the configuration by
+the product.
+- SysUI main thread (standard main thread)
+- `ShellMainThread` (only used if the resource `config_enableShellMainThread` is set true
+ (ie. phones))
+ - This falls back to the SysUI main thread otherwise
+ - **Note**:
+ - This thread runs with `THREAD_PRIORITY_DISPLAY` priority since so many windowing-critical
+ components depend on it
+ - This is also the UI thread for almost all UI created by the Shell
+ - The Shell main thread Handler (and the Executor that wraps it) is async, so
+ messages/runnables used via this Handler are handled immediately if there is no sync
+ messages prior to it in the queue.
+- `ShellBackgroundThread` (for longer running tasks where we don't want to block the shell main
+ thread)
+ - This is always another thread even if config_enableShellMainThread is not set true
+ - **Note**:
+ - This thread runs with `THREAD_PRIORITY_BACKGROUND` priority
+- `ShellAnimationThread` (currently only used for Transitions and Splitscreen, but potentially all
+ animations could be offloaded here)
+- `ShellSplashScreenThread` (only for use with splashscreens)
+
+## Dagger setup
+
+The threading-related components are provided by the [WMShellConcurrencyModule](frameworks/base/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellConcurrencyModule.java),
+for example, the Executors and Handlers for the various threads that are used. You can request
+an executor of the necessary type by using the appropriate annotation for each of the threads (ie.
+`@ShellMainThread Executor`) when injecting into your Shell component.
+
+To get the SysUI main thread, you can use the `@Main` annotation.
+
+## Best practices
+
+### Components
+- Don't do initialization in the Shell component constructors
+ - If the host SysUI is not careful, it may construct the WMComponent dependencies on the main
+ thread, and this reduces the likelihood that components will intiailize on the wrong thread
+ in such cases
+- Be careful of using CountDownLatch and other blocking synchronization mechanisms in Shell code
+ - If the Shell main thread is not a separate thread, this will cause a deadlock
+- Callbacks, Observers, Listeners to any non-shell component should post onto main Shell thread
+ - This includes Binder calls, SysUI calls, BroadcastReceivers, etc. Basically any API that
+ takes a runnable should either be registered with the right Executor/Handler or posted to
+ the main Shell thread manually
+- Since everything in the Shell runs on the main Shell thread, you do **not** need to explicitly
+ `synchronize` your code (unless you are trying to prevent reentrantcy, but that can also be
+ done in other ways)
+
+### Handlers/Executors
+- You generally **never** need to create Handlers explicitly, instead inject `@ShellMainThread
+ ShellExecutor` instead
+ - This is a common pattern to defer logic in UI code, but the Handler created wraps the Looper
+ that is currently running, which can be wrong (see above for initialization vs construction)
+- That said, sometimes Handlers are necessary because Framework API only takes Handlers or you
+ want to dedupe multiple messages
+ - In such cases inject `@ShellMainThread Handler` or use view.getHandler() which should be OK
+ assuming that the view root was initialized on the main Shell thread
+- **Never use Looper.getMainLooper()**
+ - It's likely going to be wrong, you can inject `@Main ShellExecutor` to get the SysUI main thread
+
+### Testing
+- You can use a `TestShellExecutor` to control the processing of messages \ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index 95de2dc61a43..c5df53b6dbc8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -60,6 +60,8 @@ import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import java.util.ArrayList;
import java.util.Optional;
@@ -68,21 +70,20 @@ import java.util.Optional;
* Handles the global drag and drop handling for the Shell.
*/
public class DragAndDropController implements DisplayController.OnDisplaysChangedListener,
- View.OnDragListener {
+ View.OnDragListener, ConfigurationChangeListener {
private static final String TAG = DragAndDropController.class.getSimpleName();
private final Context mContext;
+ private final ShellController mShellController;
private final DisplayController mDisplayController;
private final DragAndDropEventLogger mLogger;
private final IconProvider mIconProvider;
private SplitScreenController mSplitScreen;
private ShellExecutor mMainExecutor;
- private DragAndDropImpl mImpl;
private ArrayList<DragAndDropListener> mListeners = new ArrayList<>();
private final SparseArray<PerDisplay> mDisplayDropTargets = new SparseArray<>();
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
/**
* Listener called during drag events, currently just onDragStarted.
@@ -92,23 +93,24 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
void onDragStarted();
}
- public DragAndDropController(Context context, DisplayController displayController,
- UiEventLogger uiEventLogger, IconProvider iconProvider, ShellExecutor mainExecutor) {
+ public DragAndDropController(Context context,
+ ShellController shellController,
+ DisplayController displayController,
+ UiEventLogger uiEventLogger,
+ IconProvider iconProvider,
+ ShellExecutor mainExecutor) {
mContext = context;
+ mShellController = shellController;
mDisplayController = displayController;
mLogger = new DragAndDropEventLogger(uiEventLogger);
mIconProvider = iconProvider;
mMainExecutor = mainExecutor;
- mImpl = new DragAndDropImpl();
- }
-
- public DragAndDrop asDragAndDrop() {
- return mImpl;
}
public void initialize(Optional<SplitScreenController> splitscreen) {
mSplitScreen = splitscreen.orElse(null);
mDisplayController.addDisplayWindowListener(this);
+ mShellController.addConfigurationChangeListener(this);
}
/** Adds a listener to be notified of drag and drop events. */
@@ -310,13 +312,15 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
return mimeTypes;
}
- private void onThemeChange() {
+ @Override
+ public void onThemeChanged() {
for (int i = 0; i < mDisplayDropTargets.size(); i++) {
mDisplayDropTargets.get(i).dragLayout.onThemeChange();
}
}
- private void onConfigChanged(Configuration newConfig) {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
for (int i = 0; i < mDisplayDropTargets.size(); i++) {
mDisplayDropTargets.get(i).dragLayout.onConfigChanged(newConfig);
}
@@ -342,21 +346,4 @@ public class DragAndDropController implements DisplayController.OnDisplaysChange
dragLayout = dl;
}
}
-
- private class DragAndDropImpl implements DragAndDrop {
-
- @Override
- public void onThemeChanged() {
- mMainExecutor.execute(() -> {
- DragAndDropController.this.onThemeChange();
- });
- }
-
- @Override
- public void onConfigChanged(Configuration newConfig) {
- mMainExecutor.execute(() -> {
- DragAndDropController.this.onConfigChanged(newConfig);
- });
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 756831007c35..435d8eaa563e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -45,12 +45,10 @@ import android.app.WindowConfiguration;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
import android.content.ClipDescription;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.LauncherApps;
-import android.content.pm.ResolveInfo;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
@@ -64,11 +62,9 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.R;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.splitscreen.SplitScreenController;
import java.lang.annotation.Retention;
@@ -267,50 +263,11 @@ public class DragAndDropPolicy {
mStarter.startShortcut(packageName, id, position, opts, user);
} else {
final PendingIntent launchIntent = intent.getParcelableExtra(EXTRA_PENDING_INTENT);
- mStarter.startIntent(launchIntent, getStartIntentFillInIntent(launchIntent, position),
- position, opts);
+ mStarter.startIntent(launchIntent, null /* fillIntent */, position, opts);
}
}
/**
- * Returns the fill-in intent to use when starting an app from a drop.
- */
- @VisibleForTesting
- Intent getStartIntentFillInIntent(PendingIntent launchIntent, @SplitPosition int position) {
- // Get the drag app
- final List<ResolveInfo> infos = launchIntent.queryIntentComponents(0 /* flags */);
- final ComponentName dragIntentActivity = !infos.isEmpty()
- ? infos.get(0).activityInfo.getComponentName()
- : null;
-
- // Get the current app (either fullscreen or the remaining app post-drop if in splitscreen)
- final boolean inSplitScreen = mSplitScreen != null
- && mSplitScreen.isSplitScreenVisible();
- final ComponentName currentActivity;
- if (!inSplitScreen) {
- currentActivity = mSession.runningTaskInfo != null
- ? mSession.runningTaskInfo.baseActivity
- : null;
- } else {
- final int nonReplacedSplitPosition = position == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT
- : SPLIT_POSITION_TOP_OR_LEFT;
- ActivityManager.RunningTaskInfo nonReplacedTaskInfo =
- mSplitScreen.getTaskInfo(nonReplacedSplitPosition);
- currentActivity = nonReplacedTaskInfo.baseActivity;
- }
-
- if (currentActivity.equals(dragIntentActivity)) {
- // Only apply MULTIPLE_TASK if we are dragging the same activity
- final Intent fillInIntent = new Intent();
- fillInIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP, "Adding MULTIPLE_TASK");
- return fillInIntent;
- }
- return null;
- }
-
- /**
* Per-drag session data.
*/
private static class DragSession {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index fef9be36a35f..692e6acb540c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -21,8 +21,6 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDO
import android.app.ActivityManager.RunningTaskInfo;
import android.content.Context;
-import android.graphics.Point;
-import android.graphics.Rect;
import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
@@ -32,26 +30,35 @@ import com.android.internal.protolog.common.ProtoLog;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.windowdecor.WindowDecorViewModel;
import java.io.PrintWriter;
/**
* {@link ShellTaskOrganizer.TaskListener} for {@link
* ShellTaskOrganizer#TASK_LISTENER_TYPE_FREEFORM}.
+ *
+ * @param <T> the type of window decoration instance
*/
-public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
+public class FreeformTaskListener<T extends AutoCloseable>
+ implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FreeformTaskListener";
+ private final WindowDecorViewModel<T> mWindowDecorationViewModel;
private final SyncTransactionQueue mSyncQueue;
- private final SparseArray<State> mTasks = new SparseArray<>();
+ private final SparseArray<State<T>> mTasks = new SparseArray<>();
- private static class State {
+ private static class State<T extends AutoCloseable> {
RunningTaskInfo mTaskInfo;
SurfaceControl mLeash;
+ T mWindowDecoration;
}
- public FreeformTaskListener(SyncTransactionQueue syncQueue) {
+ public FreeformTaskListener(
+ WindowDecorViewModel<T> windowDecorationViewModel,
+ SyncTransactionQueue syncQueue) {
+ mWindowDecorationViewModel = windowDecorationViewModel;
mSyncQueue = syncQueue;
}
@@ -62,23 +69,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
}
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Appeared: #%d",
taskInfo.taskId);
- final State state = new State();
+ final State<T> state = new State<>();
state.mTaskInfo = taskInfo;
state.mLeash = leash;
+ state.mWindowDecoration =
+ mWindowDecorationViewModel.createWindowDecoration(taskInfo, leash);
mTasks.put(taskInfo.taskId, state);
-
- final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
- mSyncQueue.runInSync(t -> {
- Point taskPosition = taskInfo.positionInParent;
- t.setPosition(leash, taskPosition.x, taskPosition.y)
- .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
- .show(leash);
- });
}
@Override
public void onTaskVanished(RunningTaskInfo taskInfo) {
- State state = mTasks.get(taskInfo.taskId);
+ State<T> state = mTasks.get(taskInfo.taskId);
if (state == null) {
Slog.e(TAG, "Task already vanished: #" + taskInfo.taskId);
return;
@@ -86,11 +87,17 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Vanished: #%d",
taskInfo.taskId);
mTasks.remove(taskInfo.taskId);
+
+ try {
+ state.mWindowDecoration.close();
+ } catch (Exception e) {
+ Slog.e(TAG, "Failed to release window decoration.", e);
+ }
}
@Override
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- State state = mTasks.get(taskInfo.taskId);
+ State<T> state = mTasks.get(taskInfo.taskId);
if (state == null) {
throw new RuntimeException(
"Task info changed before appearing: #" + taskInfo.taskId);
@@ -98,15 +105,7 @@ public class FreeformTaskListener implements ShellTaskOrganizer.TaskListener {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Freeform Task Info Changed: #%d",
taskInfo.taskId);
state.mTaskInfo = taskInfo;
-
- final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
- final SurfaceControl leash = state.mLeash;
- mSyncQueue.runInSync(t -> {
- Point taskPosition = taskInfo.positionInParent;
- t.setPosition(leash, taskPosition.x, taskPosition.y)
- .setWindowCrop(leash, taskBounds.width(), taskBounds.height())
- .show(leash);
- });
+ mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo, state.mWindowDecoration);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
index 73e6cba43ec0..79e363bcdb41 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenTaskListener.java
@@ -16,17 +16,13 @@
package com.android.wm.shell.fullscreen;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
import android.app.ActivityManager.RunningTaskInfo;
-import android.app.TaskInfo;
import android.graphics.Point;
import android.util.Slog;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
import android.view.SurfaceControl;
import androidx.annotation.NonNull;
@@ -48,22 +44,17 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = "FullscreenTaskListener";
private final SyncTransactionQueue mSyncQueue;
- private final FullscreenUnfoldController mFullscreenUnfoldController;
private final Optional<RecentTasksController> mRecentTasksOptional;
private final SparseArray<TaskData> mDataByTaskId = new SparseArray<>();
- private final AnimatableTasksListener mAnimatableTasksListener = new AnimatableTasksListener();
- public FullscreenTaskListener(SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> unfoldController) {
- this(syncQueue, unfoldController, Optional.empty());
+ public FullscreenTaskListener(SyncTransactionQueue syncQueue) {
+ this(syncQueue, Optional.empty());
}
public FullscreenTaskListener(SyncTransactionQueue syncQueue,
- Optional<FullscreenUnfoldController> unfoldController,
Optional<RecentTasksController> recentTasks) {
mSyncQueue = syncQueue;
- mFullscreenUnfoldController = unfoldController.orElse(null);
mRecentTasksOptional = recentTasks;
}
@@ -76,6 +67,7 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
taskInfo.taskId);
final Point positionInParent = taskInfo.positionInParent;
mDataByTaskId.put(taskInfo.taskId, new TaskData(leash, positionInParent));
+
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
mSyncQueue.runInSync(t -> {
// Reset several properties back to fullscreen (PiP, for example, leaves all these
@@ -87,7 +79,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
t.show(leash);
});
- mAnimatableTasksListener.onTaskAppeared(taskInfo);
updateRecentsForVisibleFullscreenTask(taskInfo);
}
@@ -95,7 +86,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- mAnimatableTasksListener.onTaskInfoChanged(taskInfo);
updateRecentsForVisibleFullscreenTask(taskInfo);
final TaskData data = mDataByTaskId.get(taskInfo.taskId);
@@ -115,7 +105,6 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
return;
}
- mAnimatableTasksListener.onTaskVanished(taskInfo);
mDataByTaskId.remove(taskInfo.taskId);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TASK_ORG, "Fullscreen Task Vanished: #%d",
@@ -173,65 +162,4 @@ public class FullscreenTaskListener implements ShellTaskOrganizer.TaskListener {
this.positionInParent = positionInParent;
}
}
-
- class AnimatableTasksListener {
- private final SparseBooleanArray mTaskIds = new SparseBooleanArray();
-
- public void onTaskAppeared(RunningTaskInfo taskInfo) {
- final boolean isApplicable = isAnimatable(taskInfo);
- if (isApplicable) {
- mTaskIds.put(taskInfo.taskId, true);
-
- if (mFullscreenUnfoldController != null) {
- SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
- mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
- }
- }
- }
-
- public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
- final boolean isApplicable = isAnimatable(taskInfo);
-
- if (isCurrentlyApplicable) {
- if (isApplicable) {
- // Still applicable, send update
- if (mFullscreenUnfoldController != null) {
- mFullscreenUnfoldController.onTaskInfoChanged(taskInfo);
- }
- } else {
- // Became inapplicable
- if (mFullscreenUnfoldController != null) {
- mFullscreenUnfoldController.onTaskVanished(taskInfo);
- }
- mTaskIds.put(taskInfo.taskId, false);
- }
- } else {
- if (isApplicable) {
- // Became applicable
- mTaskIds.put(taskInfo.taskId, true);
-
- if (mFullscreenUnfoldController != null) {
- SurfaceControl leash = mDataByTaskId.get(taskInfo.taskId).surface;
- mFullscreenUnfoldController.onTaskAppeared(taskInfo, leash);
- }
- }
- }
- }
-
- public void onTaskVanished(RunningTaskInfo taskInfo) {
- final boolean isCurrentlyApplicable = mTaskIds.get(taskInfo.taskId);
- if (isCurrentlyApplicable && mFullscreenUnfoldController != null) {
- mFullscreenUnfoldController.onTaskVanished(taskInfo);
- }
- mTaskIds.put(taskInfo.taskId, false);
- }
-
- private boolean isAnimatable(TaskInfo taskInfo) {
- // Filter all visible tasks that are not launcher tasks
- // We do not animate launcher as it handles the animation by itself
- return taskInfo != null && taskInfo.isVisible && taskInfo.getConfiguration()
- .windowConfiguration.getActivityType() != ACTIVITY_TYPE_HOME;
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
index 23f76ca5f6ae..665b035bc41c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutController.java
@@ -19,7 +19,6 @@ package com.android.wm.shell.hidedisplaycutout;
import android.content.Context;
import android.content.res.Configuration;
import android.os.SystemProperties;
-import android.util.Slog;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -27,20 +26,20 @@ import androidx.annotation.VisibleForTesting;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import java.io.PrintWriter;
-import java.util.concurrent.TimeUnit;
/**
* Manages the hide display cutout status.
*/
-public class HideDisplayCutoutController {
+public class HideDisplayCutoutController implements ConfigurationChangeListener {
private static final String TAG = "HideDisplayCutoutController";
private final Context mContext;
+ private final ShellController mShellController;
private final HideDisplayCutoutOrganizer mOrganizer;
- private final ShellExecutor mMainExecutor;
- private final HideDisplayCutoutImpl mImpl = new HideDisplayCutoutImpl();
@VisibleForTesting
boolean mEnabled;
@@ -49,8 +48,9 @@ public class HideDisplayCutoutController {
* supported.
*/
@Nullable
- public static HideDisplayCutoutController create(
- Context context, DisplayController displayController, ShellExecutor mainExecutor) {
+ public static HideDisplayCutoutController create(Context context,
+ ShellController shellController, DisplayController displayController,
+ ShellExecutor mainExecutor) {
// The SystemProperty is set for devices that support this feature and is used to control
// whether to create the HideDisplayCutout instance.
// It's defined in the device.mk (e.g. device/google/crosshatch/device.mk).
@@ -60,19 +60,16 @@ public class HideDisplayCutoutController {
HideDisplayCutoutOrganizer organizer =
new HideDisplayCutoutOrganizer(context, displayController, mainExecutor);
- return new HideDisplayCutoutController(context, organizer, mainExecutor);
+ return new HideDisplayCutoutController(context, shellController, organizer);
}
- HideDisplayCutoutController(Context context, HideDisplayCutoutOrganizer organizer,
- ShellExecutor mainExecutor) {
+ HideDisplayCutoutController(Context context, ShellController shellController,
+ HideDisplayCutoutOrganizer organizer) {
mContext = context;
+ mShellController = shellController;
mOrganizer = organizer;
- mMainExecutor = mainExecutor;
updateStatus();
- }
-
- public HideDisplayCutout asHideDisplayCutout() {
- return mImpl;
+ mShellController.addConfigurationChangeListener(this);
}
@VisibleForTesting
@@ -94,7 +91,8 @@ public class HideDisplayCutoutController {
}
}
- private void onConfigurationChanged(Configuration newConfig) {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
updateStatus();
}
@@ -107,13 +105,4 @@ public class HideDisplayCutoutController {
pw.println(mEnabled);
mOrganizer.dump(pw);
}
-
- private class HideDisplayCutoutImpl implements HideDisplayCutout {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- mMainExecutor.execute(() -> {
- HideDisplayCutoutController.this.onConfigurationChanged(newConfig);
- });
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
index 3f7d78dda037..9478b347653f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizer.java
@@ -128,9 +128,10 @@ class HideDisplayCutoutOrganizer extends DisplayAreaOrganizer {
final WindowContainerTransaction wct = new WindowContainerTransaction();
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
- applyBoundsAndOffsets(
- displayAreaInfo.token, mDisplayAreaMap.get(displayAreaInfo.token), wct, t);
+ final SurfaceControl leash = mDisplayAreaMap.get(displayAreaInfo.token);
+ applyBoundsAndOffsets(displayAreaInfo.token, leash, wct, t);
applyTransaction(wct, t);
+ leash.release();
mDisplayAreaMap.remove(displayAreaInfo.token);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
index b4c87b6cbf95..2c8ba0970ccc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java
@@ -51,6 +51,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import java.io.PrintWriter;
import java.util.List;
@@ -146,9 +147,11 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks,
KidsModeSettingsObserver kidsModeSettingsObserver) {
- super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null, recentTasks);
+ super(taskOrganizerController, mainExecutor, context, /* compatUI= */ null,
+ unfoldAnimationController, recentTasks);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
@@ -164,8 +167,9 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer {
SyncTransactionQueue syncTransactionQueue,
DisplayController displayController,
DisplayInsetsController displayInsetsController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<RecentTasksController> recentTasks) {
- super(mainExecutor, context, /* compatUI= */ null, recentTasks);
+ super(mainExecutor, context, /* compatUI= */ null, unfoldAnimationController, recentTasks);
mContext = context;
mMainHandler = mainHandler;
mSyncQueue = syncTransactionQueue;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
deleted file mode 100644
index aced072c8c71..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerImeController.java
+++ /dev/null
@@ -1,418 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
-import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.util.Slog;
-import android.view.Choreographer;
-import android.view.SurfaceControl;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
-
-class DividerImeController implements DisplayImeController.ImePositionProcessor {
- private static final String TAG = "DividerImeController";
- private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
-
- private static final float ADJUSTED_NONFOCUS_DIM = 0.3f;
-
- private final LegacySplitScreenTaskListener mSplits;
- private final TransactionPool mTransactionPool;
- private final ShellExecutor mMainExecutor;
- private final TaskOrganizer mTaskOrganizer;
-
- /**
- * These are the y positions of the top of the IME surface when it is hidden and when it is
- * shown respectively. These are NOT necessarily the top of the visible IME itself.
- */
- private int mHiddenTop = 0;
- private int mShownTop = 0;
-
- // The following are target states (what we are curretly animating towards).
- /**
- * {@code true} if, at the end of the animation, the split task positions should be
- * adjusted by height of the IME. This happens when the secondary split is the IME target.
- */
- private boolean mTargetAdjusted = false;
- /**
- * {@code true} if, at the end of the animation, the IME should be shown/visible
- * regardless of what has focus.
- */
- private boolean mTargetShown = false;
- private float mTargetPrimaryDim = 0.f;
- private float mTargetSecondaryDim = 0.f;
-
- // The following are the current (most recent) states set during animation
- /** {@code true} if the secondary split has IME focus. */
- private boolean mSecondaryHasFocus = false;
- /** The dimming currently applied to the primary/secondary splits. */
- private float mLastPrimaryDim = 0.f;
- private float mLastSecondaryDim = 0.f;
- /** The most recent y position of the top of the IME surface */
- private int mLastAdjustTop = -1;
-
- // The following are states reached last time an animation fully completed.
- /** {@code true} if the IME was shown/visible by the last-completed animation. */
- private boolean mImeWasShown = false;
- /** {@code true} if the split positions were adjusted by the last-completed animation. */
- private boolean mAdjusted = false;
-
- /**
- * When some aspect of split-screen needs to animate independent from the IME,
- * this will be non-null and control split animation.
- */
- @Nullable
- private ValueAnimator mAnimation = null;
-
- private boolean mPaused = true;
- private boolean mPausedTargetAdjusted = false;
-
- DividerImeController(LegacySplitScreenTaskListener splits, TransactionPool pool,
- ShellExecutor mainExecutor, TaskOrganizer taskOrganizer) {
- mSplits = splits;
- mTransactionPool = pool;
- mMainExecutor = mainExecutor;
- mTaskOrganizer = taskOrganizer;
- }
-
- private DividerView getView() {
- return mSplits.mSplitScreenController.getDividerView();
- }
-
- private LegacySplitDisplayLayout getLayout() {
- return mSplits.mSplitScreenController.getSplitLayout();
- }
-
- private boolean isDividerHidden() {
- final DividerView view = mSplits.mSplitScreenController.getDividerView();
- return view == null || view.isHidden();
- }
-
- private boolean getSecondaryHasFocus(int displayId) {
- WindowContainerToken imeSplit = mTaskOrganizer.getImeTarget(displayId);
- return imeSplit != null
- && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
- }
-
- void reset() {
- mPaused = true;
- mPausedTargetAdjusted = false;
- mAnimation = null;
- mAdjusted = mTargetAdjusted = false;
- mImeWasShown = mTargetShown = false;
- mTargetPrimaryDim = mTargetSecondaryDim = mLastPrimaryDim = mLastSecondaryDim = 0.f;
- mSecondaryHasFocus = false;
- mLastAdjustTop = -1;
- }
-
- private void updateDimTargets() {
- final boolean splitIsVisible = !getView().isHidden();
- mTargetPrimaryDim = (mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- mTargetSecondaryDim = (!mSecondaryHasFocus && mTargetShown && splitIsVisible)
- ? ADJUSTED_NONFOCUS_DIM : 0.f;
- }
-
-
- @Override
- public void onImeControlTargetChanged(int displayId, boolean controlling) {
- // Restore the split layout when wm-shell is not controlling IME insets anymore.
- if (!controlling && mTargetShown) {
- mPaused = false;
- mTargetAdjusted = mTargetShown = false;
- mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
- updateImeAdjustState(true /* force */);
- startAsyncAnimation();
- }
- }
-
- @Override
- @ImeAnimationFlags
- public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop,
- boolean imeShouldShow, boolean imeIsFloating, SurfaceControl.Transaction t) {
- if (isDividerHidden()) {
- return 0;
- }
- mHiddenTop = hiddenTop;
- mShownTop = shownTop;
- mTargetShown = imeShouldShow;
- mSecondaryHasFocus = getSecondaryHasFocus(displayId);
- final boolean targetAdjusted = imeShouldShow && mSecondaryHasFocus
- && !imeIsFloating && !getLayout().mDisplayLayout.isLandscape()
- && !mSplits.mSplitScreenController.isMinimized();
- if (mLastAdjustTop < 0) {
- mLastAdjustTop = imeShouldShow ? hiddenTop : shownTop;
- } else if (mLastAdjustTop != (imeShouldShow ? mShownTop : mHiddenTop)) {
- if (mTargetAdjusted != targetAdjusted && targetAdjusted == mAdjusted) {
- // Check for an "interruption" of an existing animation. In this case, we
- // need to fake-flip the last-known state direction so that the animation
- // completes in the other direction.
- mAdjusted = mTargetAdjusted;
- } else if (targetAdjusted && mTargetAdjusted && mAdjusted) {
- // Already fully adjusted for IME, but IME height has changed; so, force-start
- // an async animation to the new IME height.
- mAdjusted = false;
- }
- }
- if (mPaused) {
- mPausedTargetAdjusted = targetAdjusted;
- if (DEBUG) Slog.d(TAG, " ime starting but paused " + dumpState());
- return (targetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0;
- }
- mTargetAdjusted = targetAdjusted;
- updateDimTargets();
- if (DEBUG) Slog.d(TAG, " ime starting. " + dumpState());
- if (mAnimation != null || (mImeWasShown && imeShouldShow
- && mTargetAdjusted != mAdjusted)) {
- // We need to animate adjustment independently of the IME position, so
- // start our own animation to drive adjustment. This happens when a
- // different split's editor has gained focus while the IME is still visible.
- startAsyncAnimation();
- }
- updateImeAdjustState();
-
- return (mTargetAdjusted || mAdjusted) ? IME_ANIMATION_NO_ALPHA : 0;
- }
-
- private void updateImeAdjustState() {
- updateImeAdjustState(false /* force */);
- }
-
- private void updateImeAdjustState(boolean force) {
- if (mAdjusted != mTargetAdjusted || force) {
- // Reposition the server's secondary split position so that it evaluates
- // insets properly.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- final LegacySplitDisplayLayout splitLayout = getLayout();
- if (mTargetAdjusted) {
- splitLayout.updateAdjustedBounds(mShownTop, mHiddenTop, mShownTop);
- wct.setBounds(mSplits.mSecondary.token, splitLayout.mAdjustedSecondary);
- // "Freeze" the configuration size so that the app doesn't get a config
- // or relaunch. This is required because normally nav-bar contributes
- // to configuration bounds (via nondecorframe).
- Rect adjustAppBounds = new Rect(mSplits.mSecondary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, splitLayout.mAdjustedSecondary.top
- - splitLayout.mSecondary.top);
- wct.setAppBounds(mSplits.mSecondary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- mSplits.mSecondary.configuration.screenWidthDp,
- mSplits.mSecondary.configuration.screenHeightDp);
-
- wct.setBounds(mSplits.mPrimary.token, splitLayout.mAdjustedPrimary);
- adjustAppBounds = new Rect(mSplits.mPrimary.configuration
- .windowConfiguration.getAppBounds());
- adjustAppBounds.offset(0, splitLayout.mAdjustedPrimary.top
- - splitLayout.mPrimary.top);
- wct.setAppBounds(mSplits.mPrimary.token, adjustAppBounds);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- mSplits.mPrimary.configuration.screenWidthDp,
- mSplits.mPrimary.configuration.screenHeightDp);
- } else {
- wct.setBounds(mSplits.mSecondary.token, splitLayout.mSecondary);
- wct.setAppBounds(mSplits.mSecondary.token, null);
- wct.setScreenSizeDp(mSplits.mSecondary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- wct.setBounds(mSplits.mPrimary.token, splitLayout.mPrimary);
- wct.setAppBounds(mSplits.mPrimary.token, null);
- wct.setScreenSizeDp(mSplits.mPrimary.token,
- SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
- }
-
- if (!mSplits.mSplitScreenController.getWmProxy().queueSyncTransactionIfWaiting(wct)) {
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- // Update all the adjusted-for-ime states
- if (!mPaused) {
- final DividerView view = getView();
- if (view != null) {
- view.setAdjustedForIme(mTargetShown, mTargetShown
- ? DisplayImeController.ANIMATION_DURATION_SHOW_MS
- : DisplayImeController.ANIMATION_DURATION_HIDE_MS);
- }
- }
- mSplits.mSplitScreenController.setAdjustedForIme(mTargetShown && !mPaused);
- }
-
- @Override
- public void onImePositionChanged(int displayId, int imeTop,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || isDividerHidden() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- final float fraction = ((float) imeTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetShown ? fraction : 1.f - fraction;
- onProgress(progress, t);
- }
-
- @Override
- public void onImeEndPositioning(int displayId, boolean cancelled,
- SurfaceControl.Transaction t) {
- if (mAnimation != null || isDividerHidden() || mPaused) {
- // Not synchronized with IME anymore, so return.
- return;
- }
- onEnd(cancelled, t);
- }
-
- private void onProgress(float progress, SurfaceControl.Transaction t) {
- final DividerView view = getView();
- if (mTargetAdjusted != mAdjusted && !mPaused) {
- final LegacySplitDisplayLayout splitLayout = getLayout();
- final float fraction = mTargetAdjusted ? progress : 1.f - progress;
- mLastAdjustTop = (int) (fraction * mShownTop + (1.f - fraction) * mHiddenTop);
- splitLayout.updateAdjustedBounds(mLastAdjustTop, mHiddenTop, mShownTop);
- view.resizeSplitSurfaces(t, splitLayout.mAdjustedPrimary,
- splitLayout.mAdjustedSecondary);
- }
- final float invProg = 1.f - progress;
- view.setResizeDimLayer(t, true /* primary */,
- mLastPrimaryDim * invProg + progress * mTargetPrimaryDim);
- view.setResizeDimLayer(t, false /* primary */,
- mLastSecondaryDim * invProg + progress * mTargetSecondaryDim);
- }
-
- void setDimsHidden(SurfaceControl.Transaction t, boolean hidden) {
- final DividerView view = getView();
- if (hidden) {
- view.setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
- view.setResizeDimLayer(t, false /* primary */, 0.f /* alpha */);
- } else {
- updateDimTargets();
- view.setResizeDimLayer(t, true /* primary */, mTargetPrimaryDim);
- view.setResizeDimLayer(t, false /* primary */, mTargetSecondaryDim);
- }
- }
-
- private void onEnd(boolean cancelled, SurfaceControl.Transaction t) {
- if (!cancelled) {
- onProgress(1.f, t);
- mAdjusted = mTargetAdjusted;
- mImeWasShown = mTargetShown;
- mLastAdjustTop = mAdjusted ? mShownTop : mHiddenTop;
- mLastPrimaryDim = mTargetPrimaryDim;
- mLastSecondaryDim = mTargetSecondaryDim;
- }
- }
-
- private void startAsyncAnimation() {
- if (mAnimation != null) {
- mAnimation.cancel();
- }
- mAnimation = ValueAnimator.ofFloat(0.f, 1.f);
- mAnimation.setDuration(DisplayImeController.ANIMATION_DURATION_SHOW_MS);
- if (mTargetAdjusted != mAdjusted) {
- final float fraction =
- ((float) mLastAdjustTop - mHiddenTop) / (mShownTop - mHiddenTop);
- final float progress = mTargetAdjusted ? fraction : 1.f - fraction;
- mAnimation.setCurrentFraction(progress);
- }
-
- mAnimation.addUpdateListener(animation -> {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- float value = (float) animation.getAnimatedValue();
- onProgress(value, t);
- t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- t.apply();
- mTransactionPool.release(t);
- });
- mAnimation.setInterpolator(DisplayImeController.INTERPOLATOR);
- mAnimation.addListener(new AnimatorListenerAdapter() {
- private boolean mCancel = false;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancel = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- SurfaceControl.Transaction t = mTransactionPool.acquire();
- onEnd(mCancel, t);
- t.apply();
- mTransactionPool.release(t);
- mAnimation = null;
- }
- });
- mAnimation.start();
- }
-
- private String dumpState() {
- return "top:" + mHiddenTop + "->" + mShownTop
- + " adj:" + mAdjusted + "->" + mTargetAdjusted + "(" + mLastAdjustTop + ")"
- + " shw:" + mImeWasShown + "->" + mTargetShown
- + " dims:" + mLastPrimaryDim + "," + mLastSecondaryDim
- + "->" + mTargetPrimaryDim + "," + mTargetSecondaryDim
- + " shf:" + mSecondaryHasFocus + " desync:" + (mAnimation != null)
- + " paus:" + mPaused + "[" + mPausedTargetAdjusted + "]";
- }
-
- /** Completely aborts/resets adjustment state */
- public void pause(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime pause posting " + dumpState());
- mMainExecutor.execute(() -> {
- if (DEBUG) Slog.d(TAG, "ime pause run posted " + dumpState());
- if (mPaused) {
- return;
- }
- mPaused = true;
- mPausedTargetAdjusted = mTargetAdjusted;
- mTargetAdjusted = false;
- mTargetPrimaryDim = mTargetSecondaryDim = 0.f;
- updateImeAdjustState();
- startAsyncAnimation();
- if (mAnimation != null) {
- mAnimation.end();
- }
- });
- }
-
- public void resume(int displayId) {
- if (DEBUG) Slog.d(TAG, "ime resume posting " + dumpState());
- mMainExecutor.execute(() -> {
- if (DEBUG) Slog.d(TAG, "ime resume run posted " + dumpState());
- if (!mPaused) {
- return;
- }
- mPaused = false;
- mTargetAdjusted = mPausedTargetAdjusted;
- updateDimTargets();
- final DividerView view = getView();
- if ((mTargetAdjusted != mAdjusted) && !mSplits.mSplitScreenController.isMinimized()
- && view != null) {
- // End unminimize animations since they conflict with adjustment animations.
- view.finishAnimations();
- }
- updateImeAdjustState();
- startAsyncAnimation();
- });
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
deleted file mode 100644
index 73be2835d2cd..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerView.java
+++ /dev/null
@@ -1,1314 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
-import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
-import static android.view.WindowManager.DOCKED_RIGHT;
-
-import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
-import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
-import static com.android.wm.shell.common.split.DividerView.TOUCH_ANIMATION_DURATION;
-import static com.android.wm.shell.common.split.DividerView.TOUCH_RELEASE_ANIMATION_DURATION;
-
-import android.animation.AnimationHandler;
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Matrix;
-import android.graphics.Rect;
-import android.graphics.Region;
-import android.graphics.Region.Op;
-import android.hardware.display.DisplayManager;
-import android.os.Bundle;
-import android.util.AttributeSet;
-import android.util.Slog;
-import android.view.Choreographer;
-import android.view.Display;
-import android.view.MotionEvent;
-import android.view.PointerIcon;
-import android.view.SurfaceControl;
-import android.view.SurfaceControl.Transaction;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
-import android.view.ViewTreeObserver.InternalInsetsInfo;
-import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-import android.view.animation.Interpolator;
-import android.view.animation.PathInterpolator;
-import android.widget.FrameLayout;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.wm.shell.R;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.split.DividerHandleView;
-
-import java.util.function.Consumer;
-
-/**
- * Docked stack divider.
- */
-public class DividerView extends FrameLayout implements OnTouchListener,
- OnComputeInternalInsetsListener {
- private static final String TAG = "DividerView";
- private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
-
- interface DividerCallbacks {
- void onDraggingStart();
- void onDraggingEnd();
- }
-
- public static final int INVALID_RECENTS_GROW_TARGET = -1;
-
- private static final int LOG_VALUE_RESIZE_50_50 = 0;
- private static final int LOG_VALUE_RESIZE_DOCKED_SMALLER = 1;
- private static final int LOG_VALUE_RESIZE_DOCKED_LARGER = 2;
-
- private static final int LOG_VALUE_UNDOCK_MAX_DOCKED = 0;
- private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
-
- private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
-
- /**
- * How much the background gets scaled when we are in the minimized dock state.
- */
- private static final float MINIMIZE_DOCK_SCALE = 0f;
- private static final float ADJUSTED_FOR_IME_SCALE = 0.5f;
-
- private static final Interpolator IME_ADJUST_INTERPOLATOR =
- new PathInterpolator(0.2f, 0f, 0.1f, 1f);
-
- private DividerHandleView mHandle;
- private View mBackground;
- private MinimizedDockShadow mMinimizedShadow;
- private int mStartX;
- private int mStartY;
- private int mStartPosition;
- private int mDockSide;
- private boolean mMoving;
- private int mTouchSlop;
- private boolean mBackgroundLifted;
- private boolean mIsInMinimizeInteraction;
- SnapTarget mSnapTargetBeforeMinimized;
-
- private int mDividerInsets;
- private final Display mDefaultDisplay;
-
- private int mDividerSize;
- private int mTouchElevation;
- private int mLongPressEntraceAnimDuration;
-
- private final Rect mDockedRect = new Rect();
- private final Rect mDockedTaskRect = new Rect();
- private final Rect mOtherTaskRect = new Rect();
- private final Rect mOtherRect = new Rect();
- private final Rect mDockedInsetRect = new Rect();
- private final Rect mOtherInsetRect = new Rect();
- private final Rect mLastResizeRect = new Rect();
- private final Rect mTmpRect = new Rect();
- private LegacySplitScreenController mSplitScreenController;
- private WindowManagerProxy mWindowManagerProxy;
- private DividerWindowManager mWindowManager;
- private VelocityTracker mVelocityTracker;
- private FlingAnimationUtils mFlingAnimationUtils;
- private LegacySplitDisplayLayout mSplitLayout;
- private DividerImeController mImeController;
- private DividerCallbacks mCallback;
-
- private AnimationHandler mSfVsyncAnimationHandler;
- private ValueAnimator mCurrentAnimator;
- private boolean mEntranceAnimationRunning;
- private boolean mExitAnimationRunning;
- private int mExitStartPosition;
- private boolean mDockedStackMinimized;
- private boolean mHomeStackResizable;
- private boolean mAdjustedForIme;
- private DividerState mState;
-
- private LegacySplitScreenTaskListener mTiles;
- boolean mFirstLayout = true;
- int mDividerPositionX;
- int mDividerPositionY;
-
- private final Matrix mTmpMatrix = new Matrix();
- private final float[] mTmpValues = new float[9];
-
- // The view is removed or in the process of been removed from the system.
- private boolean mRemoved;
-
- // Whether the surface for this view has been hidden regardless of actual visibility. This is
- // used interact with keyguard.
- private boolean mSurfaceHidden = false;
-
- private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- final DividerSnapAlgorithm snapAlgorithm = getSnapAlgorithm();
- if (isHorizontalDivision()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
- mContext.getString(R.string.accessibility_action_divider_top_full)));
- if (snapAlgorithm.isFirstSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
- mContext.getString(R.string.accessibility_action_divider_top_70)));
- }
- if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
- // Only show the middle target if there are more than 1 split target
- info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
- mContext.getString(R.string.accessibility_action_divider_top_50)));
- }
- if (snapAlgorithm.isLastSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
- mContext.getString(R.string.accessibility_action_divider_top_30)));
- }
- info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
- mContext.getString(R.string.accessibility_action_divider_bottom_full)));
- } else {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_full,
- mContext.getString(R.string.accessibility_action_divider_left_full)));
- if (snapAlgorithm.isFirstSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_70,
- mContext.getString(R.string.accessibility_action_divider_left_70)));
- }
- if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) {
- // Only show the middle target if there are more than 1 split target
- info.addAction(new AccessibilityAction(R.id.action_move_tl_50,
- mContext.getString(R.string.accessibility_action_divider_left_50)));
- }
- if (snapAlgorithm.isLastSplitTargetAvailable()) {
- info.addAction(new AccessibilityAction(R.id.action_move_tl_30,
- mContext.getString(R.string.accessibility_action_divider_left_30)));
- }
- info.addAction(new AccessibilityAction(R.id.action_move_rb_full,
- mContext.getString(R.string.accessibility_action_divider_right_full)));
- }
- }
-
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- int currentPosition = getCurrentPosition();
- SnapTarget nextTarget = null;
- DividerSnapAlgorithm snapAlgorithm = mSplitLayout.getSnapAlgorithm();
- if (action == R.id.action_move_tl_full) {
- nextTarget = snapAlgorithm.getDismissEndTarget();
- } else if (action == R.id.action_move_tl_70) {
- nextTarget = snapAlgorithm.getLastSplitTarget();
- } else if (action == R.id.action_move_tl_50) {
- nextTarget = snapAlgorithm.getMiddleTarget();
- } else if (action == R.id.action_move_tl_30) {
- nextTarget = snapAlgorithm.getFirstSplitTarget();
- } else if (action == R.id.action_move_rb_full) {
- nextTarget = snapAlgorithm.getDismissStartTarget();
- }
- if (nextTarget != null) {
- startDragging(true /* animate */, false /* touching */);
- stopDragging(currentPosition, nextTarget, 250, Interpolators.FAST_OUT_SLOW_IN);
- return true;
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
-
- private final Runnable mResetBackgroundRunnable = new Runnable() {
- @Override
- public void run() {
- resetBackground();
- }
- };
-
- public DividerView(Context context) {
- this(context, null);
- }
-
- public DividerView(Context context, @Nullable AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
- public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
- this(context, attrs, defStyleAttr, 0);
- }
-
- public DividerView(Context context, @Nullable AttributeSet attrs, int defStyleAttr,
- int defStyleRes) {
- super(context, attrs, defStyleAttr, defStyleRes);
- final DisplayManager displayManager =
- (DisplayManager) mContext.getSystemService(Context.DISPLAY_SERVICE);
- mDefaultDisplay = displayManager.getDisplay(Display.DEFAULT_DISPLAY);
- }
-
- public void setAnimationHandler(AnimationHandler sfVsyncAnimationHandler) {
- mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mHandle = findViewById(R.id.docked_divider_handle);
- mBackground = findViewById(R.id.docked_divider_background);
- mMinimizedShadow = findViewById(R.id.minimized_dock_shadow);
- mHandle.setOnTouchListener(this);
- final int dividerWindowWidth = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- mDividerInsets = getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_insets);
- mDividerSize = dividerWindowWidth - 2 * mDividerInsets;
- mTouchElevation = getResources().getDimensionPixelSize(
- R.dimen.docked_stack_divider_lift_elevation);
- mLongPressEntraceAnimDuration = getResources().getInteger(
- R.integer.long_press_dock_anim_duration);
- mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
- mFlingAnimationUtils = new FlingAnimationUtils(getResources().getDisplayMetrics(), 0.3f);
- boolean landscape = getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
- mHandle.setPointerIcon(PointerIcon.getSystemIcon(getContext(),
- landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
- getViewTreeObserver().addOnComputeInternalInsetsListener(this);
- mHandle.setAccessibilityDelegate(mHandleDelegate);
- }
-
- @Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
-
- // Save the current target if not minimized once attached to window
- if (mDockSide != WindowManager.DOCKED_INVALID && !mIsInMinimizeInteraction) {
- saveSnapTargetBeforeMinimized(mSnapTargetBeforeMinimized);
- }
- mFirstLayout = true;
- }
-
- void onDividerRemoved() {
- mRemoved = true;
- mCallback = null;
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mFirstLayout) {
- // Wait for first layout so that the ViewRootImpl surface has been created.
- initializeSurfaceState();
- mFirstLayout = false;
- }
- int minimizeLeft = 0;
- int minimizeTop = 0;
- if (mDockSide == WindowManager.DOCKED_TOP) {
- minimizeTop = mBackground.getTop();
- } else if (mDockSide == WindowManager.DOCKED_LEFT) {
- minimizeLeft = mBackground.getLeft();
- } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
- minimizeLeft = mBackground.getRight() - mMinimizedShadow.getWidth();
- }
- mMinimizedShadow.layout(minimizeLeft, minimizeTop,
- minimizeLeft + mMinimizedShadow.getMeasuredWidth(),
- minimizeTop + mMinimizedShadow.getMeasuredHeight());
- if (changed) {
- notifySplitScreenBoundsChanged();
- }
- }
-
- void injectDependencies(LegacySplitScreenController splitScreenController,
- DividerWindowManager windowManager, DividerState dividerState,
- DividerCallbacks callback, LegacySplitScreenTaskListener tiles,
- LegacySplitDisplayLayout sdl, DividerImeController imeController,
- WindowManagerProxy wmProxy) {
- mSplitScreenController = splitScreenController;
- mWindowManager = windowManager;
- mState = dividerState;
- mCallback = callback;
- mTiles = tiles;
- mSplitLayout = sdl;
- mImeController = imeController;
- mWindowManagerProxy = wmProxy;
-
- if (mState.mRatioPositionBeforeMinimized == 0) {
- // Set the middle target as the initial state
- mSnapTargetBeforeMinimized = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
- } else {
- repositionSnapTargetBeforeMinimized();
- }
- }
-
- /** Gets non-minimized secondary bounds of split screen. */
- public Rect getNonMinimizedSplitScreenSecondaryBounds() {
- mOtherTaskRect.set(mSplitLayout.mSecondary);
- return mOtherTaskRect;
- }
-
- private boolean inSplitMode() {
- return getVisibility() == VISIBLE;
- }
-
- /** Unlike setVisible, this directly hides the surface without changing view visibility. */
- void setHidden(boolean hidden) {
- if (mSurfaceHidden == hidden) {
- return;
- }
- mSurfaceHidden = hidden;
- post(() -> {
- final SurfaceControl sc = getWindowSurfaceControl();
- if (sc == null) {
- return;
- }
- Transaction t = mTiles.getTransaction();
- if (hidden) {
- t.hide(sc);
- } else {
- t.show(sc);
- }
- mImeController.setDimsHidden(t, hidden);
- t.apply();
- mTiles.releaseTransaction(t);
- });
- }
-
- boolean isHidden() {
- return getVisibility() != View.VISIBLE || mSurfaceHidden;
- }
-
- /** Starts dragging the divider bar. */
- public boolean startDragging(boolean animate, boolean touching) {
- cancelFlingAnimation();
- if (touching) {
- mHandle.setTouching(true, animate);
- }
- mDockSide = mSplitLayout.getPrimarySplitSide();
-
- mWindowManagerProxy.setResizing(true);
- if (touching) {
- mWindowManager.setSlippery(false);
- liftBackground();
- }
- if (mCallback != null) {
- mCallback.onDraggingStart();
- }
- return inSplitMode();
- }
-
- /** Stops dragging the divider bar. */
- public void stopDragging(int position, float velocity, boolean avoidDismissStart,
- boolean logMetrics) {
- mHandle.setTouching(false, true /* animate */);
- fling(position, velocity, avoidDismissStart, logMetrics);
- mWindowManager.setSlippery(true);
- releaseBackground();
- }
-
- private void stopDragging(int position, SnapTarget target, long duration,
- Interpolator interpolator) {
- stopDragging(position, target, duration, 0 /* startDelay*/, 0 /* endDelay */, interpolator);
- }
-
- private void stopDragging(int position, SnapTarget target, long duration,
- Interpolator interpolator, long endDelay) {
- stopDragging(position, target, duration, 0 /* startDelay*/, endDelay, interpolator);
- }
-
- private void stopDragging(int position, SnapTarget target, long duration, long startDelay,
- long endDelay, Interpolator interpolator) {
- mHandle.setTouching(false, true /* animate */);
- flingTo(position, target, duration, startDelay, endDelay, interpolator);
- mWindowManager.setSlippery(true);
- releaseBackground();
- }
-
- private void stopDragging() {
- mHandle.setTouching(false, true /* animate */);
- mWindowManager.setSlippery(true);
- mWindowManagerProxy.setResizing(false);
- releaseBackground();
- }
-
- private void updateDockSide() {
- mDockSide = mSplitLayout.getPrimarySplitSide();
- mMinimizedShadow.setDockSide(mDockSide);
- }
-
- public DividerSnapAlgorithm getSnapAlgorithm() {
- return mDockedStackMinimized ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- : mSplitLayout.getSnapAlgorithm();
- }
-
- public int getCurrentPosition() {
- return isHorizontalDivision() ? mDividerPositionY : mDividerPositionX;
- }
-
- public boolean isMinimized() {
- return mDockedStackMinimized;
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- convertToScreenCoordinates(event);
- final int action = event.getAction() & MotionEvent.ACTION_MASK;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- mVelocityTracker = VelocityTracker.obtain();
- mVelocityTracker.addMovement(event);
- mStartX = (int) event.getX();
- mStartY = (int) event.getY();
- boolean result = startDragging(true /* animate */, true /* touching */);
- if (!result) {
-
- // Weren't able to start dragging successfully, so cancel it again.
- stopDragging();
- }
- mStartPosition = getCurrentPosition();
- mMoving = false;
- return result;
- case MotionEvent.ACTION_MOVE:
- mVelocityTracker.addMovement(event);
- int x = (int) event.getX();
- int y = (int) event.getY();
- boolean exceededTouchSlop =
- isHorizontalDivision() && Math.abs(y - mStartY) > mTouchSlop
- || (!isHorizontalDivision() && Math.abs(x - mStartX) > mTouchSlop);
- if (!mMoving && exceededTouchSlop) {
- mStartX = x;
- mStartY = y;
- mMoving = true;
- }
- if (mMoving && mDockSide != WindowManager.DOCKED_INVALID) {
- SnapTarget snapTarget = getSnapAlgorithm().calculateSnapTarget(
- mStartPosition, 0 /* velocity */, false /* hardDismiss */);
- resizeStackSurfaces(calculatePosition(x, y), mStartPosition, snapTarget,
- null /* transaction */);
- }
- break;
- case MotionEvent.ACTION_UP:
- case MotionEvent.ACTION_CANCEL:
- if (!mMoving) {
- stopDragging();
- break;
- }
-
- x = (int) event.getRawX();
- y = (int) event.getRawY();
- mVelocityTracker.addMovement(event);
- mVelocityTracker.computeCurrentVelocity(1000);
- int position = calculatePosition(x, y);
- stopDragging(position, isHorizontalDivision() ? mVelocityTracker.getYVelocity()
- : mVelocityTracker.getXVelocity(), false /* avoidDismissStart */,
- true /* log */);
- mMoving = false;
- break;
- }
- return true;
- }
-
- private void logResizeEvent(SnapTarget snapTarget) {
- if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissStartTarget()) {
- MetricsLogger.action(
- mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideTopLeft(mDockSide)
- ? LOG_VALUE_UNDOCK_MAX_OTHER
- : LOG_VALUE_UNDOCK_MAX_DOCKED);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getDismissEndTarget()) {
- MetricsLogger.action(
- mContext, MetricsEvent.ACTION_WINDOW_UNDOCK_MAX, dockSideBottomRight(mDockSide)
- ? LOG_VALUE_UNDOCK_MAX_OTHER
- : LOG_VALUE_UNDOCK_MAX_DOCKED);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getMiddleTarget()) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
- LOG_VALUE_RESIZE_50_50);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getFirstSplitTarget()) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
- dockSideTopLeft(mDockSide)
- ? LOG_VALUE_RESIZE_DOCKED_SMALLER
- : LOG_VALUE_RESIZE_DOCKED_LARGER);
- } else if (snapTarget == mSplitLayout.getSnapAlgorithm().getLastSplitTarget()) {
- MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_RESIZE,
- dockSideTopLeft(mDockSide)
- ? LOG_VALUE_RESIZE_DOCKED_LARGER
- : LOG_VALUE_RESIZE_DOCKED_SMALLER);
- }
- }
-
- private void convertToScreenCoordinates(MotionEvent event) {
- event.setLocation(event.getRawX(), event.getRawY());
- }
-
- private void fling(int position, float velocity, boolean avoidDismissStart,
- boolean logMetrics) {
- DividerSnapAlgorithm currentSnapAlgorithm = getSnapAlgorithm();
- SnapTarget snapTarget = currentSnapAlgorithm.calculateSnapTarget(position, velocity);
- if (avoidDismissStart && snapTarget == currentSnapAlgorithm.getDismissStartTarget()) {
- snapTarget = currentSnapAlgorithm.getFirstSplitTarget();
- }
- if (logMetrics) {
- logResizeEvent(snapTarget);
- }
- ValueAnimator anim = getFlingAnimator(position, snapTarget, 0 /* endDelay */);
- mFlingAnimationUtils.apply(anim, position, snapTarget.position, velocity);
- anim.start();
- }
-
- private void flingTo(int position, SnapTarget target, long duration, long startDelay,
- long endDelay, Interpolator interpolator) {
- ValueAnimator anim = getFlingAnimator(position, target, endDelay);
- anim.setDuration(duration);
- anim.setStartDelay(startDelay);
- anim.setInterpolator(interpolator);
- anim.start();
- }
-
- private ValueAnimator getFlingAnimator(int position, final SnapTarget snapTarget,
- final long endDelay) {
- if (mCurrentAnimator != null) {
- cancelFlingAnimation();
- updateDockSide();
- }
- if (DEBUG) Slog.d(TAG, "Getting fling " + position + "->" + snapTarget.position);
- final boolean taskPositionSameAtEnd = snapTarget.flag == SnapTarget.FLAG_NONE;
- ValueAnimator anim = ValueAnimator.ofInt(position, snapTarget.position);
- anim.addUpdateListener(animation -> resizeStackSurfaces((int) animation.getAnimatedValue(),
- taskPositionSameAtEnd && animation.getAnimatedFraction() == 1f
- ? TASK_POSITION_SAME
- : snapTarget.taskPosition,
- snapTarget, null /* transaction */));
- Consumer<Boolean> endAction = cancelled -> {
- if (DEBUG) Slog.d(TAG, "End Fling " + cancelled + " min:" + mIsInMinimizeInteraction);
- final boolean wasMinimizeInteraction = mIsInMinimizeInteraction;
- // Reset minimized divider position after unminimized state animation finishes.
- if (!cancelled && !mDockedStackMinimized && mIsInMinimizeInteraction) {
- mIsInMinimizeInteraction = false;
- }
- boolean dismissed = commitSnapFlags(snapTarget);
- mWindowManagerProxy.setResizing(false);
- updateDockSide();
- mCurrentAnimator = null;
- mEntranceAnimationRunning = false;
- mExitAnimationRunning = false;
- if (!dismissed && !wasMinimizeInteraction) {
- mWindowManagerProxy.applyResizeSplits(snapTarget.position, mSplitLayout);
- }
- if (mCallback != null) {
- mCallback.onDraggingEnd();
- }
-
- // Record last snap target the divider moved to
- if (!mIsInMinimizeInteraction) {
- // The last snapTarget position can be negative when the last divider position was
- // offscreen. In that case, save the middle (default) SnapTarget so calculating next
- // position isn't negative.
- final SnapTarget saveTarget;
- if (snapTarget.position < 0) {
- saveTarget = mSplitLayout.getSnapAlgorithm().getMiddleTarget();
- } else {
- saveTarget = snapTarget;
- }
- final DividerSnapAlgorithm snapAlgo = mSplitLayout.getSnapAlgorithm();
- if (saveTarget.position != snapAlgo.getDismissEndTarget().position
- && saveTarget.position != snapAlgo.getDismissStartTarget().position) {
- saveSnapTargetBeforeMinimized(saveTarget);
- }
- }
- notifySplitScreenBoundsChanged();
- };
- anim.addListener(new AnimatorListenerAdapter() {
-
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- long delay = 0;
- if (endDelay != 0) {
- delay = endDelay;
- } else if (mCancelled) {
- delay = 0;
- }
- if (delay == 0) {
- endAction.accept(mCancelled);
- } else {
- final Boolean cancelled = mCancelled;
- if (DEBUG) Slog.d(TAG, "Posting endFling " + cancelled + " d:" + delay + "ms");
- getHandler().postDelayed(() -> endAction.accept(cancelled), delay);
- }
- }
- });
- mCurrentAnimator = anim;
- mCurrentAnimator.setAnimationHandler(mSfVsyncAnimationHandler);
- return anim;
- }
-
- private void notifySplitScreenBoundsChanged() {
- if (mSplitLayout.mPrimary == null || mSplitLayout.mSecondary == null) {
- return;
- }
- mOtherTaskRect.set(mSplitLayout.mSecondary);
-
- mTmpRect.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(), mHandle.getBottom());
- if (isHorizontalDivision()) {
- mTmpRect.offsetTo(mHandle.getLeft(), mDividerPositionY);
- } else {
- mTmpRect.offsetTo(mDividerPositionX, mHandle.getTop());
- }
- mWindowManagerProxy.setTouchRegion(mTmpRect);
-
- mTmpRect.set(mSplitLayout.mDisplayLayout.stableInsets());
- switch (mSplitLayout.getPrimarySplitSide()) {
- case WindowManager.DOCKED_LEFT:
- mTmpRect.left = 0;
- break;
- case WindowManager.DOCKED_RIGHT:
- mTmpRect.right = 0;
- break;
- case WindowManager.DOCKED_TOP:
- mTmpRect.top = 0;
- break;
- }
- mSplitScreenController.notifyBoundsChanged(mOtherTaskRect, mTmpRect);
- }
-
- private void cancelFlingAnimation() {
- if (mCurrentAnimator != null) {
- mCurrentAnimator.cancel();
- }
- }
-
- private boolean commitSnapFlags(SnapTarget target) {
- if (target.flag == SnapTarget.FLAG_NONE) {
- return false;
- }
- final boolean dismissOrMaximize;
- if (target.flag == SnapTarget.FLAG_DISMISS_START) {
- dismissOrMaximize = mDockSide == WindowManager.DOCKED_LEFT
- || mDockSide == WindowManager.DOCKED_TOP;
- } else {
- dismissOrMaximize = mDockSide == WindowManager.DOCKED_RIGHT
- || mDockSide == WindowManager.DOCKED_BOTTOM;
- }
- mWindowManagerProxy.dismissOrMaximizeDocked(mTiles, mSplitLayout, dismissOrMaximize);
- Transaction t = mTiles.getTransaction();
- setResizeDimLayer(t, true /* primary */, 0f);
- setResizeDimLayer(t, false /* primary */, 0f);
- t.apply();
- mTiles.releaseTransaction(t);
- return true;
- }
-
- private void liftBackground() {
- if (mBackgroundLifted) {
- return;
- }
- if (isHorizontalDivision()) {
- mBackground.animate().scaleY(1.4f);
- } else {
- mBackground.animate().scaleX(1.4f);
- }
- mBackground.animate()
- .setInterpolator(Interpolators.TOUCH_RESPONSE)
- .setDuration(TOUCH_ANIMATION_DURATION)
- .translationZ(mTouchElevation)
- .start();
-
- // Lift handle as well so it doesn't get behind the background, even though it doesn't
- // cast shadow.
- mHandle.animate()
- .setInterpolator(Interpolators.TOUCH_RESPONSE)
- .setDuration(TOUCH_ANIMATION_DURATION)
- .translationZ(mTouchElevation)
- .start();
- mBackgroundLifted = true;
- }
-
- private void releaseBackground() {
- if (!mBackgroundLifted) {
- return;
- }
- mBackground.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
- .translationZ(0)
- .scaleX(1f)
- .scaleY(1f)
- .start();
- mHandle.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(TOUCH_RELEASE_ANIMATION_DURATION)
- .translationZ(0)
- .start();
- mBackgroundLifted = false;
- }
-
- private void initializeSurfaceState() {
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- // Recalculate the split-layout's internal tile bounds
- mSplitLayout.resizeSplits(midPos);
- Transaction t = mTiles.getTransaction();
- if (mDockedStackMinimized) {
- int position = mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- .getMiddleTarget().position;
- calculateBoundsForPosition(position, mDockSide, mDockedRect);
- calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherRect);
- mDividerPositionX = mDividerPositionY = position;
- resizeSplitSurfaces(t, mDockedRect, mSplitLayout.mPrimary,
- mOtherRect, mSplitLayout.mSecondary);
- } else {
- resizeSplitSurfaces(t, mSplitLayout.mPrimary, null,
- mSplitLayout.mSecondary, null);
- }
- setResizeDimLayer(t, true /* primary */, 0.f /* alpha */);
- setResizeDimLayer(t, false /* secondary */, 0.f /* alpha */);
- t.apply();
- mTiles.releaseTransaction(t);
-
- // Get the actually-visible bar dimensions (relative to full window). This is a thin
- // bar going through the center.
- final Rect dividerBar = isHorizontalDivision()
- ? new Rect(0, mDividerInsets, mSplitLayout.mDisplayLayout.width(),
- mDividerInsets + mDividerSize)
- : new Rect(mDividerInsets, 0, mDividerInsets + mDividerSize,
- mSplitLayout.mDisplayLayout.height());
- final Region touchRegion = new Region(dividerBar);
- // Add in the "draggable" portion. While not visible, this is an expanded area that the
- // user can interact with.
- touchRegion.union(new Rect(mHandle.getLeft(), mHandle.getTop(),
- mHandle.getRight(), mHandle.getBottom()));
- mWindowManager.setTouchRegion(touchRegion);
- }
-
- void setMinimizedDockStack(boolean minimized, boolean isHomeStackResizable,
- Transaction t) {
- mHomeStackResizable = isHomeStackResizable;
- updateDockSide();
- if (!minimized) {
- resetBackground();
- }
- mMinimizedShadow.setAlpha(minimized ? 1f : 0f);
- if (mDockedStackMinimized != minimized) {
- mDockedStackMinimized = minimized;
- if (mSplitLayout.mDisplayLayout.rotation() != mDefaultDisplay.getRotation()) {
- // Splitscreen to minimize is about to starts after rotating landscape to seascape,
- // update display info and snap algorithm targets
- repositionSnapTargetBeforeMinimized();
- }
- if (mIsInMinimizeInteraction != minimized || mCurrentAnimator != null) {
- cancelFlingAnimation();
- if (minimized) {
- // Relayout to recalculate the divider shadow when minimizing
- requestLayout();
- mIsInMinimizeInteraction = true;
- resizeStackSurfaces(mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- .getMiddleTarget(), t);
- } else {
- resizeStackSurfaces(mSnapTargetBeforeMinimized, t);
- mIsInMinimizeInteraction = false;
- }
- }
- }
- }
-
- void enterSplitMode(boolean isHomeStackResizable) {
- setHidden(false);
-
- SnapTarget miniMid =
- mSplitLayout.getMinimizedSnapAlgorithm(isHomeStackResizable).getMiddleTarget();
- if (mDockedStackMinimized) {
- mDividerPositionY = mDividerPositionX = miniMid.position;
- }
- }
-
- /**
- * Tries to grab a surface control from ViewRootImpl. If this isn't available for some reason
- * (ie. the window isn't ready yet), it will get the surfacecontrol that the WindowlessWM has
- * assigned to it.
- */
- private SurfaceControl getWindowSurfaceControl() {
- return mWindowManager.mSystemWindows.getViewSurface(this);
- }
-
- void exitSplitMode() {
- // The view is going to be removed right after this function involved, updates the surface
- // in the current thread instead of posting it to the view's UI thread.
- final SurfaceControl sc = getWindowSurfaceControl();
- if (sc == null) {
- return;
- }
- Transaction t = mTiles.getTransaction();
- t.hide(sc);
- mImeController.setDimsHidden(t, true);
- t.apply();
- mTiles.releaseTransaction(t);
-
- // Reset tile bounds
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- mWindowManagerProxy.applyResizeSplits(midPos, mSplitLayout);
- }
-
- void setMinimizedDockStack(boolean minimized, long animDuration,
- boolean isHomeStackResizable) {
- if (DEBUG) Slog.d(TAG, "setMinDock: " + mDockedStackMinimized + "->" + minimized);
- mHomeStackResizable = isHomeStackResizable;
- updateDockSide();
- if (mDockedStackMinimized != minimized) {
- mIsInMinimizeInteraction = true;
- mDockedStackMinimized = minimized;
- stopDragging(minimized
- ? mSnapTargetBeforeMinimized.position
- : getCurrentPosition(),
- minimized
- ? mSplitLayout.getMinimizedSnapAlgorithm(mHomeStackResizable)
- .getMiddleTarget()
- : mSnapTargetBeforeMinimized,
- animDuration, Interpolators.FAST_OUT_SLOW_IN, 0);
- setAdjustedForIme(false, animDuration);
- }
- if (!minimized) {
- mBackground.animate().withEndAction(mResetBackgroundRunnable);
- }
- mBackground.animate()
- .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
- .setDuration(animDuration)
- .start();
- }
-
- // Needed to end any currently playing animations when they might compete with other anims
- // (specifically, IME adjust animation immediately after leaving minimized). Someday maybe
- // these can be unified, but not today.
- void finishAnimations() {
- if (mCurrentAnimator != null) {
- mCurrentAnimator.end();
- }
- }
-
- void setAdjustedForIme(boolean adjustedForIme, long animDuration) {
- if (mAdjustedForIme == adjustedForIme) {
- return;
- }
- updateDockSide();
- mHandle.animate()
- .setInterpolator(IME_ADJUST_INTERPOLATOR)
- .setDuration(animDuration)
- .alpha(adjustedForIme ? 0f : 1f)
- .start();
- if (mDockSide == WindowManager.DOCKED_TOP) {
- mBackground.setPivotY(0);
- mBackground.animate()
- .scaleY(adjustedForIme ? ADJUSTED_FOR_IME_SCALE : 1f);
- }
- if (!adjustedForIme) {
- mBackground.animate().withEndAction(mResetBackgroundRunnable);
- }
- mBackground.animate()
- .setInterpolator(IME_ADJUST_INTERPOLATOR)
- .setDuration(animDuration)
- .start();
- mAdjustedForIme = adjustedForIme;
- }
-
- private void saveSnapTargetBeforeMinimized(SnapTarget target) {
- mSnapTargetBeforeMinimized = target;
- mState.mRatioPositionBeforeMinimized = (float) target.position
- / (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
- : mSplitLayout.mDisplayLayout.width());
- }
-
- private void resetBackground() {
- mBackground.setPivotX(mBackground.getWidth() / 2);
- mBackground.setPivotY(mBackground.getHeight() / 2);
- mBackground.setScaleX(1f);
- mBackground.setScaleY(1f);
- mMinimizedShadow.setAlpha(0f);
- }
-
- @Override
- protected void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- }
-
- private void repositionSnapTargetBeforeMinimized() {
- int position = (int) (mState.mRatioPositionBeforeMinimized
- * (isHorizontalDivision() ? mSplitLayout.mDisplayLayout.height()
- : mSplitLayout.mDisplayLayout.width()));
-
- // Set the snap target before minimized but do not save until divider is attached and not
- // minimized because it does not know its minimized state yet.
- mSnapTargetBeforeMinimized =
- mSplitLayout.getSnapAlgorithm().calculateNonDismissingSnapTarget(position);
- }
-
- private int calculatePosition(int touchX, int touchY) {
- return isHorizontalDivision() ? calculateYPosition(touchY) : calculateXPosition(touchX);
- }
-
- public boolean isHorizontalDivision() {
- return getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT;
- }
-
- private int calculateXPosition(int touchX) {
- return mStartPosition + touchX - mStartX;
- }
-
- private int calculateYPosition(int touchY) {
- return mStartPosition + touchY - mStartY;
- }
-
- private void alignTopLeft(Rect containingRect, Rect rect) {
- int width = rect.width();
- int height = rect.height();
- rect.set(containingRect.left, containingRect.top,
- containingRect.left + width, containingRect.top + height);
- }
-
- private void alignBottomRight(Rect containingRect, Rect rect) {
- int width = rect.width();
- int height = rect.height();
- rect.set(containingRect.right - width, containingRect.bottom - height,
- containingRect.right, containingRect.bottom);
- }
-
- private void calculateBoundsForPosition(int position, int dockSide, Rect outRect) {
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, outRect,
- mSplitLayout.mDisplayLayout.width(), mSplitLayout.mDisplayLayout.height(),
- mDividerSize);
- }
-
- private void resizeStackSurfaces(SnapTarget taskSnapTarget, Transaction t) {
- resizeStackSurfaces(taskSnapTarget.position, taskSnapTarget.position, taskSnapTarget, t);
- }
-
- void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect otherRect) {
- resizeSplitSurfaces(t, dockedRect, null, otherRect, null);
- }
-
- private void resizeSplitSurfaces(Transaction t, Rect dockedRect, Rect dockedTaskRect,
- Rect otherRect, Rect otherTaskRect) {
- dockedTaskRect = dockedTaskRect == null ? dockedRect : dockedTaskRect;
- otherTaskRect = otherTaskRect == null ? otherRect : otherTaskRect;
-
- mDividerPositionX = mSplitLayout.getPrimarySplitSide() == DOCKED_RIGHT
- ? otherRect.right : dockedRect.right;
- mDividerPositionY = dockedRect.bottom;
-
- if (DEBUG) {
- Slog.d(TAG, "Resizing split surfaces: " + dockedRect + " " + dockedTaskRect
- + " " + otherRect + " " + otherTaskRect);
- }
-
- t.setPosition(mTiles.mPrimarySurface, dockedTaskRect.left, dockedTaskRect.top);
- Rect crop = new Rect(dockedRect);
- crop.offsetTo(-Math.min(dockedTaskRect.left - dockedRect.left, 0),
- -Math.min(dockedTaskRect.top - dockedRect.top, 0));
- t.setWindowCrop(mTiles.mPrimarySurface, crop);
- t.setPosition(mTiles.mSecondarySurface, otherTaskRect.left, otherTaskRect.top);
- crop.set(otherRect);
- crop.offsetTo(-(otherTaskRect.left - otherRect.left),
- -(otherTaskRect.top - otherRect.top));
- t.setWindowCrop(mTiles.mSecondarySurface, crop);
- final SurfaceControl dividerCtrl = getWindowSurfaceControl();
- if (dividerCtrl != null) {
- if (isHorizontalDivision()) {
- t.setPosition(dividerCtrl, 0, mDividerPositionY - mDividerInsets);
- } else {
- t.setPosition(dividerCtrl, mDividerPositionX - mDividerInsets, 0);
- }
- }
- }
-
- void setResizeDimLayer(Transaction t, boolean primary, float alpha) {
- SurfaceControl dim = primary ? mTiles.mPrimaryDim : mTiles.mSecondaryDim;
- if (alpha <= 0.001f) {
- t.hide(dim);
- } else {
- t.setAlpha(dim, alpha);
- t.show(dim);
- }
- }
-
- void resizeStackSurfaces(int position, int taskPosition, SnapTarget taskSnapTarget,
- Transaction transaction) {
- if (mRemoved) {
- // This divider view has been removed so shouldn't have any additional influence.
- return;
- }
- calculateBoundsForPosition(position, mDockSide, mDockedRect);
- calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherRect);
-
- if (mDockedRect.equals(mLastResizeRect) && !mEntranceAnimationRunning) {
- return;
- }
-
- // Make sure shadows are updated
- if (mBackground.getZ() > 0f) {
- mBackground.invalidate();
- }
-
- final boolean ownTransaction = transaction == null;
- final Transaction t = ownTransaction ? mTiles.getTransaction() : transaction;
- mLastResizeRect.set(mDockedRect);
- if (mIsInMinimizeInteraction) {
- calculateBoundsForPosition(mSnapTargetBeforeMinimized.position, mDockSide,
- mDockedTaskRect);
- calculateBoundsForPosition(mSnapTargetBeforeMinimized.position,
- DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
-
- // Move a right-docked-app to line up with the divider while dragging it
- if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- - mDockedTaskRect.left + mDividerSize, 0);
- }
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- if (ownTransaction) {
- t.setFrameTimelineVsync(Choreographer.getSfInstance().getVsyncId());
- t.apply();
- mTiles.releaseTransaction(t);
- }
- return;
- }
-
- if (mEntranceAnimationRunning && taskPosition != TASK_POSITION_SAME) {
- calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
-
- // Move a docked app if from the right in position with the divider up to insets
- if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(Math.max(position, -mDividerSize)
- - mDockedTaskRect.left + mDividerSize, 0);
- }
- calculateBoundsForPosition(taskPosition, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherTaskRect);
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- } else if (mExitAnimationRunning && taskPosition != TASK_POSITION_SAME) {
- calculateBoundsForPosition(taskPosition, mDockSide, mDockedTaskRect);
- mDockedInsetRect.set(mDockedTaskRect);
- calculateBoundsForPosition(mExitStartPosition,
- DockedDividerUtils.invertDockSide(mDockSide), mOtherTaskRect);
- mOtherInsetRect.set(mOtherTaskRect);
- applyExitAnimationParallax(mOtherTaskRect, position);
-
- // Move a right-docked-app to line up with the divider while dragging it
- if (mDockSide == DOCKED_RIGHT) {
- mDockedTaskRect.offset(position + mDividerSize, 0);
- }
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- } else if (taskPosition != TASK_POSITION_SAME) {
- calculateBoundsForPosition(position, DockedDividerUtils.invertDockSide(mDockSide),
- mOtherRect);
- int dockSideInverted = DockedDividerUtils.invertDockSide(mDockSide);
- int taskPositionDocked =
- restrictDismissingTaskPosition(taskPosition, mDockSide, taskSnapTarget);
- int taskPositionOther =
- restrictDismissingTaskPosition(taskPosition, dockSideInverted, taskSnapTarget);
- calculateBoundsForPosition(taskPositionDocked, mDockSide, mDockedTaskRect);
- calculateBoundsForPosition(taskPositionOther, dockSideInverted, mOtherTaskRect);
- mTmpRect.set(0, 0, mSplitLayout.mDisplayLayout.width(),
- mSplitLayout.mDisplayLayout.height());
- alignTopLeft(mDockedRect, mDockedTaskRect);
- alignTopLeft(mOtherRect, mOtherTaskRect);
- mDockedInsetRect.set(mDockedTaskRect);
- mOtherInsetRect.set(mOtherTaskRect);
- if (dockSideTopLeft(mDockSide)) {
- alignTopLeft(mTmpRect, mDockedInsetRect);
- alignBottomRight(mTmpRect, mOtherInsetRect);
- } else {
- alignBottomRight(mTmpRect, mDockedInsetRect);
- alignTopLeft(mTmpRect, mOtherInsetRect);
- }
- applyDismissingParallax(mDockedTaskRect, mDockSide, taskSnapTarget, position,
- taskPositionDocked);
- applyDismissingParallax(mOtherTaskRect, dockSideInverted, taskSnapTarget, position,
- taskPositionOther);
- resizeSplitSurfaces(t, mDockedRect, mDockedTaskRect, mOtherRect, mOtherTaskRect);
- } else {
- resizeSplitSurfaces(t, mDockedRect, null, mOtherRect, null);
- }
- SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
- float dimFraction = getDimFraction(position, closestDismissTarget);
- setResizeDimLayer(t, isDismissTargetPrimary(closestDismissTarget), dimFraction);
- if (ownTransaction) {
- t.apply();
- mTiles.releaseTransaction(t);
- }
- }
-
- private void applyExitAnimationParallax(Rect taskRect, int position) {
- if (mDockSide == WindowManager.DOCKED_TOP) {
- taskRect.offset(0, (int) ((position - mExitStartPosition) * 0.25f));
- } else if (mDockSide == WindowManager.DOCKED_LEFT) {
- taskRect.offset((int) ((position - mExitStartPosition) * 0.25f), 0);
- } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
- taskRect.offset((int) ((mExitStartPosition - position) * 0.25f), 0);
- }
- }
-
- private float getDimFraction(int position, SnapTarget dismissTarget) {
- if (mEntranceAnimationRunning) {
- return 0f;
- }
- float fraction = getSnapAlgorithm().calculateDismissingFraction(position);
- fraction = Math.max(0, Math.min(fraction, 1f));
- fraction = DIM_INTERPOLATOR.getInterpolation(fraction);
- return fraction;
- }
-
- /**
- * When the snap target is dismissing one side, make sure that the dismissing side doesn't get
- * 0 size.
- */
- private int restrictDismissingTaskPosition(int taskPosition, int dockSide,
- SnapTarget snapTarget) {
- if (snapTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(dockSide)) {
- return Math.max(mSplitLayout.getSnapAlgorithm().getFirstSplitTarget().position,
- mStartPosition);
- } else if (snapTarget.flag == SnapTarget.FLAG_DISMISS_END
- && dockSideBottomRight(dockSide)) {
- return Math.min(mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position,
- mStartPosition);
- } else {
- return taskPosition;
- }
- }
-
- /**
- * Applies a parallax to the task when dismissing.
- */
- private void applyDismissingParallax(Rect taskRect, int dockSide, SnapTarget snapTarget,
- int position, int taskPosition) {
- float fraction = Math.min(1, Math.max(0,
- mSplitLayout.getSnapAlgorithm().calculateDismissingFraction(position)));
- SnapTarget dismissTarget = null;
- SnapTarget splitTarget = null;
- int start = 0;
- if (position <= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
- && dockSideTopLeft(dockSide)) {
- dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
- splitTarget = mSplitLayout.getSnapAlgorithm().getFirstSplitTarget();
- start = taskPosition;
- } else if (position >= mSplitLayout.getSnapAlgorithm().getLastSplitTarget().position
- && dockSideBottomRight(dockSide)) {
- dismissTarget = mSplitLayout.getSnapAlgorithm().getDismissEndTarget();
- splitTarget = mSplitLayout.getSnapAlgorithm().getLastSplitTarget();
- start = splitTarget.position;
- }
- if (dismissTarget != null && fraction > 0f
- && isDismissing(splitTarget, position, dockSide)) {
- fraction = calculateParallaxDismissingFraction(fraction, dockSide);
- int offsetPosition = (int) (start + fraction
- * (dismissTarget.position - splitTarget.position));
- int width = taskRect.width();
- int height = taskRect.height();
- switch (dockSide) {
- case WindowManager.DOCKED_LEFT:
- taskRect.left = offsetPosition - width;
- taskRect.right = offsetPosition;
- break;
- case WindowManager.DOCKED_RIGHT:
- taskRect.left = offsetPosition + mDividerSize;
- taskRect.right = offsetPosition + width + mDividerSize;
- break;
- case WindowManager.DOCKED_TOP:
- taskRect.top = offsetPosition - height;
- taskRect.bottom = offsetPosition;
- break;
- case WindowManager.DOCKED_BOTTOM:
- taskRect.top = offsetPosition + mDividerSize;
- taskRect.bottom = offsetPosition + height + mDividerSize;
- break;
- }
- }
- }
-
- /**
- * @return for a specified {@code fraction}, this returns an adjusted value that simulates a
- * slowing down parallax effect
- */
- private static float calculateParallaxDismissingFraction(float fraction, int dockSide) {
- float result = SLOWDOWN_INTERPOLATOR.getInterpolation(fraction) / 3.5f;
-
- // Less parallax at the top, just because.
- if (dockSide == WindowManager.DOCKED_TOP) {
- result /= 2f;
- }
- return result;
- }
-
- private static boolean isDismissing(SnapTarget snapTarget, int position, int dockSide) {
- if (dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT) {
- return position < snapTarget.position;
- } else {
- return position > snapTarget.position;
- }
- }
-
- private boolean isDismissTargetPrimary(SnapTarget dismissTarget) {
- return (dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
- || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
- && dockSideBottomRight(mDockSide));
- }
-
- /**
- * @return true if and only if {@code dockSide} is top or left
- */
- private static boolean dockSideTopLeft(int dockSide) {
- return dockSide == WindowManager.DOCKED_TOP || dockSide == WindowManager.DOCKED_LEFT;
- }
-
- /**
- * @return true if and only if {@code dockSide} is bottom or right
- */
- private static boolean dockSideBottomRight(int dockSide) {
- return dockSide == WindowManager.DOCKED_BOTTOM || dockSide == WindowManager.DOCKED_RIGHT;
- }
-
- @Override
- public void onComputeInternalInsets(InternalInsetsInfo inoutInfo) {
- inoutInfo.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
- inoutInfo.touchableRegion.set(mHandle.getLeft(), mHandle.getTop(), mHandle.getRight(),
- mHandle.getBottom());
- inoutInfo.touchableRegion.op(mBackground.getLeft(), mBackground.getTop(),
- mBackground.getRight(), mBackground.getBottom(), Op.UNION);
- }
-
- void onUndockingTask() {
- int dockSide = mSplitLayout.getPrimarySplitSide();
- if (inSplitMode()) {
- startDragging(false /* animate */, false /* touching */);
- SnapTarget target = dockSideTopLeft(dockSide)
- ? mSplitLayout.getSnapAlgorithm().getDismissEndTarget()
- : mSplitLayout.getSnapAlgorithm().getDismissStartTarget();
-
- // Don't start immediately - give a little bit time to settle the drag resize change.
- mExitAnimationRunning = true;
- mExitStartPosition = getCurrentPosition();
- stopDragging(mExitStartPosition, target, 336 /* duration */, 100 /* startDelay */,
- 0 /* endDelay */, Interpolators.FAST_OUT_SLOW_IN);
- }
- }
-
- private int calculatePositionForInsetBounds() {
- mSplitLayout.mDisplayLayout.getStableBounds(mTmpRect);
- return DockedDividerUtils.calculatePositionForBounds(mTmpRect, mDockSide, mDividerSize);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java
deleted file mode 100644
index 2c3ae68e4749..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerWindowManager.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
-import static android.view.WindowManager.LayoutParams.FLAG_SLIPPERY;
-import static android.view.WindowManager.LayoutParams.FLAG_SPLIT_TOUCH;
-import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
-import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.SHELL_ROOT_LAYER_DIVIDER;
-
-import android.graphics.PixelFormat;
-import android.graphics.Region;
-import android.os.Binder;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.wm.shell.common.SystemWindows;
-
-/**
- * Manages the window parameters of the docked stack divider.
- */
-final class DividerWindowManager {
-
- private static final String WINDOW_TITLE = "DockedStackDivider";
-
- final SystemWindows mSystemWindows;
- private WindowManager.LayoutParams mLp;
- private View mView;
-
- DividerWindowManager(SystemWindows systemWindows) {
- mSystemWindows = systemWindows;
- }
-
- /** Add a divider view */
- void add(View view, int width, int height, int displayId) {
- mLp = new WindowManager.LayoutParams(
- width, height, TYPE_DOCK_DIVIDER,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL
- | FLAG_WATCH_OUTSIDE_TOUCH | FLAG_SPLIT_TOUCH | FLAG_SLIPPERY,
- PixelFormat.TRANSLUCENT);
- mLp.token = new Binder();
- mLp.setTitle(WINDOW_TITLE);
- mLp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
- view.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
- | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
- | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
- mSystemWindows.addView(view, mLp, displayId, SHELL_ROOT_LAYER_DIVIDER);
- mView = view;
- }
-
- void remove() {
- if (mView != null) {
- mSystemWindows.removeView(mView);
- }
- mView = null;
- }
-
- void setSlippery(boolean slippery) {
- boolean changed = false;
- if (slippery && (mLp.flags & FLAG_SLIPPERY) == 0) {
- mLp.flags |= FLAG_SLIPPERY;
- changed = true;
- } else if (!slippery && (mLp.flags & FLAG_SLIPPERY) != 0) {
- mLp.flags &= ~FLAG_SLIPPERY;
- changed = true;
- }
- if (changed) {
- mSystemWindows.updateViewLayout(mView, mLp);
- }
- }
-
- void setTouchable(boolean touchable) {
- if (mView == null) {
- return;
- }
- boolean changed = false;
- if (!touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) == 0) {
- mLp.flags |= FLAG_NOT_TOUCHABLE;
- changed = true;
- } else if (touchable && (mLp.flags & FLAG_NOT_TOUCHABLE) != 0) {
- mLp.flags &= ~FLAG_NOT_TOUCHABLE;
- changed = true;
- }
- if (changed) {
- mSystemWindows.updateViewLayout(mView, mLp);
- }
- }
-
- /** Sets the touch region to `touchRegion`. Use null to unset.*/
- void setTouchRegion(Region touchRegion) {
- if (mView == null) {
- return;
- }
- mSystemWindows.setTouchableRegion(mView, touchRegion);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java
deleted file mode 100644
index 4fe28e630114..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivity.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
-import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.ActivityManager;
-import android.os.Bundle;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.View.OnTouchListener;
-import android.widget.TextView;
-
-import com.android.wm.shell.R;
-
-/**
- * Translucent activity that gets started on top of a task in multi-window to inform the user that
- * we forced the activity below to be resizable.
- *
- * Note: This activity runs on the main thread of the process hosting the Shell lib.
- */
-public class ForcedResizableInfoActivity extends Activity implements OnTouchListener {
-
- public static final String EXTRA_FORCED_RESIZEABLE_REASON = "extra_forced_resizeable_reason";
-
- private static final long DISMISS_DELAY = 2500;
-
- private final Runnable mFinishRunnable = new Runnable() {
- @Override
- public void run() {
- finish();
- }
- };
-
- @Override
- protected void onCreate(@Nullable Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.forced_resizable_activity);
- TextView tv = findViewById(com.android.internal.R.id.message);
- int reason = getIntent().getIntExtra(EXTRA_FORCED_RESIZEABLE_REASON, -1);
- String text;
- switch (reason) {
- case FORCED_RESIZEABLE_REASON_SPLIT_SCREEN:
- text = getString(R.string.dock_forced_resizable);
- break;
- case FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY:
- text = getString(R.string.forced_resizable_secondary_display);
- break;
- default:
- throw new IllegalArgumentException("Unexpected forced resizeable reason: "
- + reason);
- }
- tv.setText(text);
- getWindow().setTitle(text);
- getWindow().getDecorView().setOnTouchListener(this);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- getWindow().getDecorView().postDelayed(mFinishRunnable, DISMISS_DELAY);
- }
-
- @Override
- protected void onStop() {
- super.onStop();
- finish();
- }
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- finish();
- return true;
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- finish();
- return true;
- }
-
- @Override
- public void finish() {
- super.finish();
- overridePendingTransition(0, R.anim.forced_resizable_exit);
- }
-
- @Override
- public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
- // Do nothing
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
deleted file mode 100644
index 139544f951ce..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/ForcedResizableInfoActivityController.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-
-import static com.android.wm.shell.legacysplitscreen.ForcedResizableInfoActivity.EXTRA_FORCED_RESIZEABLE_REASON;
-
-import android.app.ActivityOptions;
-import android.content.Context;
-import android.content.Intent;
-import android.os.UserHandle;
-import android.util.ArraySet;
-import android.widget.Toast;
-
-import com.android.wm.shell.R;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.util.function.Consumer;
-
-/**
- * Controller that decides when to show the {@link ForcedResizableInfoActivity}.
- */
-final class ForcedResizableInfoActivityController implements DividerView.DividerCallbacks {
-
- private static final String SELF_PACKAGE_NAME = "com.android.systemui";
-
- private static final int TIMEOUT = 1000;
- private final Context mContext;
- private final ShellExecutor mMainExecutor;
- private final ArraySet<PendingTaskRecord> mPendingTasks = new ArraySet<>();
- private final ArraySet<String> mPackagesShownInSession = new ArraySet<>();
- private boolean mDividerDragging;
-
- private final Runnable mTimeoutRunnable = this::showPending;
-
- private final Consumer<Boolean> mDockedStackExistsListener = exists -> {
- if (!exists) {
- mPackagesShownInSession.clear();
- }
- };
-
- /** Record of force resized task that's pending to be handled. */
- private class PendingTaskRecord {
- int mTaskId;
- /**
- * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SPLIT_SCREEN} or
- * {@link android.app.ITaskStackListener#FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY}
- */
- int mReason;
-
- PendingTaskRecord(int taskId, int reason) {
- this.mTaskId = taskId;
- this.mReason = reason;
- }
- }
-
- ForcedResizableInfoActivityController(Context context,
- LegacySplitScreenController splitScreenController,
- ShellExecutor mainExecutor) {
- mContext = context;
- mMainExecutor = mainExecutor;
- splitScreenController.registerInSplitScreenListener(mDockedStackExistsListener);
- }
-
- @Override
- public void onDraggingStart() {
- mDividerDragging = true;
- mMainExecutor.removeCallbacks(mTimeoutRunnable);
- }
-
- @Override
- public void onDraggingEnd() {
- mDividerDragging = false;
- showPending();
- }
-
- void onAppTransitionFinished() {
- if (!mDividerDragging) {
- showPending();
- }
- }
-
- void activityForcedResizable(String packageName, int taskId, int reason) {
- if (debounce(packageName)) {
- return;
- }
- mPendingTasks.add(new PendingTaskRecord(taskId, reason));
- postTimeout();
- }
-
- void activityDismissingSplitScreen() {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- }
-
- void activityLaunchOnSecondaryDisplayFailed() {
- Toast.makeText(mContext, R.string.activity_launch_on_secondary_display_failed_text,
- Toast.LENGTH_SHORT).show();
- }
-
- private void showPending() {
- mMainExecutor.removeCallbacks(mTimeoutRunnable);
- for (int i = mPendingTasks.size() - 1; i >= 0; i--) {
- PendingTaskRecord pendingRecord = mPendingTasks.valueAt(i);
- Intent intent = new Intent(mContext, ForcedResizableInfoActivity.class);
- ActivityOptions options = ActivityOptions.makeBasic();
- options.setLaunchTaskId(pendingRecord.mTaskId);
- // Set as task overlay and allow to resume, so that when an app enters split-screen and
- // becomes paused, the overlay will still be shown.
- options.setTaskOverlay(true, true /* canResume */);
- intent.putExtra(EXTRA_FORCED_RESIZEABLE_REASON, pendingRecord.mReason);
- mContext.startActivityAsUser(intent, options.toBundle(), UserHandle.CURRENT);
- }
- mPendingTasks.clear();
- }
-
- private void postTimeout() {
- mMainExecutor.removeCallbacks(mTimeoutRunnable);
- mMainExecutor.executeDelayed(mTimeoutRunnable, TIMEOUT);
- }
-
- private boolean debounce(String packageName) {
- if (packageName == null) {
- return false;
- }
-
- // We launch ForcedResizableInfoActivity into a task that was forced resizable, so that
- // triggers another notification. So ignore our own activity.
- if (SELF_PACKAGE_NAME.equals(packageName)) {
- return true;
- }
- boolean debounce = mPackagesShownInSession.contains(packageName);
- mPackagesShownInSession.add(packageName);
- return debounce;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
deleted file mode 100644
index f201634d3d4a..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitDisplayLayout.java
+++ /dev/null
@@ -1,326 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.util.RotationUtils.rotateBounds;
-import static android.view.WindowManager.DOCKED_BOTTOM;
-import static android.view.WindowManager.DOCKED_INVALID;
-import static android.view.WindowManager.DOCKED_LEFT;
-import static android.view.WindowManager.DOCKED_RIGHT;
-import static android.view.WindowManager.DOCKED_TOP;
-
-import android.annotation.NonNull;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.graphics.Rect;
-import android.util.TypedValue;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.internal.policy.DockedDividerUtils;
-import com.android.wm.shell.common.DisplayLayout;
-
-/**
- * Handles split-screen related internal display layout. In general, this represents the
- * WM-facing understanding of the splits.
- */
-public class LegacySplitDisplayLayout {
- /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
- * restrict IME adjustment so that a min portion of top stack remains visible.*/
- private static final float ADJUSTED_STACK_FRACTION_MIN = 0.3f;
-
- private static final int DIVIDER_WIDTH_INACTIVE_DP = 4;
-
- LegacySplitScreenTaskListener mTiles;
- DisplayLayout mDisplayLayout;
- Context mContext;
-
- // Lazy stuff
- boolean mResourcesValid = false;
- int mDividerSize;
- int mDividerSizeInactive;
- private DividerSnapAlgorithm mSnapAlgorithm = null;
- private DividerSnapAlgorithm mMinimizedSnapAlgorithm = null;
- Rect mPrimary = null;
- Rect mSecondary = null;
- Rect mAdjustedPrimary = null;
- Rect mAdjustedSecondary = null;
- final Rect mTmpBounds = new Rect();
-
- public LegacySplitDisplayLayout(Context ctx, DisplayLayout dl,
- LegacySplitScreenTaskListener taskTiles) {
- mTiles = taskTiles;
- mDisplayLayout = dl;
- mContext = ctx;
- }
-
- void rotateTo(int newRotation) {
- mDisplayLayout.rotateTo(mContext.getResources(), newRotation);
- final Configuration config = new Configuration();
- config.unset();
- config.orientation = mDisplayLayout.getOrientation();
- Rect tmpRect = new Rect(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- tmpRect.inset(mDisplayLayout.nonDecorInsets());
- config.windowConfiguration.setAppBounds(tmpRect);
- tmpRect.set(0, 0, mDisplayLayout.width(), mDisplayLayout.height());
- tmpRect.inset(mDisplayLayout.stableInsets());
- config.screenWidthDp = (int) (tmpRect.width() / mDisplayLayout.density());
- config.screenHeightDp = (int) (tmpRect.height() / mDisplayLayout.density());
- mContext = mContext.createConfigurationContext(config);
- mSnapAlgorithm = null;
- mMinimizedSnapAlgorithm = null;
- mResourcesValid = false;
- }
-
- private void updateResources() {
- if (mResourcesValid) {
- return;
- }
- mResourcesValid = true;
- Resources res = mContext.getResources();
- mDividerSize = DockedDividerUtils.getDividerSize(res,
- DockedDividerUtils.getDividerInsets(res));
- mDividerSizeInactive = (int) TypedValue.applyDimension(
- TypedValue.COMPLEX_UNIT_DIP, DIVIDER_WIDTH_INACTIVE_DP, res.getDisplayMetrics());
- }
-
- int getPrimarySplitSide() {
- switch (mDisplayLayout.getNavigationBarPosition(mContext.getResources())) {
- case DisplayLayout.NAV_BAR_BOTTOM:
- return mDisplayLayout.isLandscape() ? DOCKED_LEFT : DOCKED_TOP;
- case DisplayLayout.NAV_BAR_LEFT:
- return DOCKED_RIGHT;
- case DisplayLayout.NAV_BAR_RIGHT:
- return DOCKED_LEFT;
- default:
- return DOCKED_INVALID;
- }
- }
-
- DividerSnapAlgorithm getSnapAlgorithm() {
- if (mSnapAlgorithm == null) {
- updateResources();
- boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
- mSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
- isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide());
- }
- return mSnapAlgorithm;
- }
-
- DividerSnapAlgorithm getMinimizedSnapAlgorithm(boolean homeStackResizable) {
- if (mMinimizedSnapAlgorithm == null) {
- updateResources();
- boolean isHorizontalDivision = !mDisplayLayout.isLandscape();
- mMinimizedSnapAlgorithm = new DividerSnapAlgorithm(mContext.getResources(),
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize,
- isHorizontalDivision, mDisplayLayout.stableInsets(), getPrimarySplitSide(),
- true /* isMinimized */, homeStackResizable);
- }
- return mMinimizedSnapAlgorithm;
- }
-
- /**
- * Resize primary bounds and secondary bounds by divider position.
- *
- * @param position divider position.
- * @return true if calculated bounds changed.
- */
- boolean resizeSplits(int position) {
- mPrimary = mPrimary == null ? new Rect() : mPrimary;
- mSecondary = mSecondary == null ? new Rect() : mSecondary;
- int dockSide = getPrimarySplitSide();
- boolean boundsChanged;
-
- mTmpBounds.set(mPrimary);
- DockedDividerUtils.calculateBoundsForPosition(position, dockSide, mPrimary,
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
- boundsChanged = !mPrimary.equals(mTmpBounds);
-
- mTmpBounds.set(mSecondary);
- DockedDividerUtils.calculateBoundsForPosition(position,
- DockedDividerUtils.invertDockSide(dockSide), mSecondary, mDisplayLayout.width(),
- mDisplayLayout.height(), mDividerSize);
- boundsChanged |= !mSecondary.equals(mTmpBounds);
- return boundsChanged;
- }
-
- void resizeSplits(int position, WindowContainerTransaction t) {
- if (resizeSplits(position)) {
- t.setBounds(mTiles.mPrimary.token, mPrimary);
- t.setBounds(mTiles.mSecondary.token, mSecondary);
-
- t.setSmallestScreenWidthDp(mTiles.mPrimary.token,
- getSmallestWidthDpForBounds(mContext, mDisplayLayout, mPrimary));
- t.setSmallestScreenWidthDp(mTiles.mSecondary.token,
- getSmallestWidthDpForBounds(mContext, mDisplayLayout, mSecondary));
- }
- }
-
- Rect calcResizableMinimizedHomeStackBounds() {
- DividerSnapAlgorithm.SnapTarget miniMid =
- getMinimizedSnapAlgorithm(true /* resizable */).getMiddleTarget();
- Rect homeBounds = new Rect();
- DockedDividerUtils.calculateBoundsForPosition(miniMid.position,
- DockedDividerUtils.invertDockSide(getPrimarySplitSide()), homeBounds,
- mDisplayLayout.width(), mDisplayLayout.height(), mDividerSize);
- return homeBounds;
- }
-
- /**
- * Updates the adjustment depending on it's current state.
- */
- void updateAdjustedBounds(int currImeTop, int hiddenTop, int shownTop) {
- adjustForIME(mDisplayLayout, currImeTop, hiddenTop, shownTop, mDividerSize,
- mDividerSizeInactive, mPrimary, mSecondary);
- }
-
- /** Assumes top/bottom split. Splits are not adjusted for left/right splits. */
- private void adjustForIME(DisplayLayout dl, int currImeTop, int hiddenTop, int shownTop,
- int dividerWidth, int dividerWidthInactive, Rect primaryBounds, Rect secondaryBounds) {
- if (mAdjustedPrimary == null) {
- mAdjustedPrimary = new Rect();
- mAdjustedSecondary = new Rect();
- }
-
- final Rect displayStableRect = new Rect();
- dl.getStableBounds(displayStableRect);
-
- final float shownFraction = ((float) (currImeTop - hiddenTop)) / (shownTop - hiddenTop);
- final int currDividerWidth =
- (int) (dividerWidthInactive * shownFraction + dividerWidth * (1.f - shownFraction));
-
- // Calculate the highest we can move the bottom of the top stack to keep 30% visible.
- final int minTopStackBottom = displayStableRect.top
- + (int) ((mPrimary.bottom - displayStableRect.top) * ADJUSTED_STACK_FRACTION_MIN);
- // Based on that, calculate the maximum amount we'll allow the ime to shift things.
- final int maxOffset = mPrimary.bottom - minTopStackBottom;
- // Calculate how much we would shift things without limits (basically the height of ime).
- final int desiredOffset = hiddenTop - shownTop;
- // Calculate an "adjustedTop" which is the currImeTop but restricted by our constraints.
- // We want an effect where the adjustment only occurs during the "highest" portion of the
- // ime animation. This is done by shifting the adjustment values by the difference in
- // offsets (effectively playing the whole adjustment animation some fixed amount of pixels
- // below the ime top).
- final int topCorrection = Math.max(0, desiredOffset - maxOffset);
- final int adjustedTop = currImeTop + topCorrection;
- // The actual yOffset is the distance between adjustedTop and the bottom of the display.
- // Since our adjustedTop values are playing "below" the ime, we clamp at 0 so we only
- // see adjustment upward.
- final int yOffset = Math.max(0, dl.height() - adjustedTop);
-
- // TOP
- // Reduce the offset by an additional small amount to squish the divider bar.
- mAdjustedPrimary.set(primaryBounds);
- mAdjustedPrimary.offset(0, -yOffset + (dividerWidth - currDividerWidth));
-
- // BOTTOM
- mAdjustedSecondary.set(secondaryBounds);
- mAdjustedSecondary.offset(0, -yOffset);
- }
-
- static int getSmallestWidthDpForBounds(@NonNull Context context, DisplayLayout dl,
- Rect bounds) {
- int dividerSize = DockedDividerUtils.getDividerSize(context.getResources(),
- DockedDividerUtils.getDividerInsets(context.getResources()));
-
- int minWidth = Integer.MAX_VALUE;
-
- // Go through all screen orientations and find the orientation in which the task has the
- // smallest width.
- Rect tmpRect = new Rect();
- Rect rotatedDisplayRect = new Rect();
- Rect displayRect = new Rect(0, 0, dl.width(), dl.height());
-
- DisplayLayout tmpDL = new DisplayLayout();
- for (int rotation = 0; rotation < 4; rotation++) {
- tmpDL.set(dl);
- tmpDL.rotateTo(context.getResources(), rotation);
- DividerSnapAlgorithm snap = initSnapAlgorithmForRotation(context, tmpDL, dividerSize);
-
- tmpRect.set(bounds);
- rotateBounds(tmpRect, displayRect, dl.rotation(), rotation);
- rotatedDisplayRect.set(0, 0, tmpDL.width(), tmpDL.height());
- final int dockSide = getPrimarySplitSide(tmpRect, rotatedDisplayRect,
- tmpDL.getOrientation());
- final int position = DockedDividerUtils.calculatePositionForBounds(tmpRect, dockSide,
- dividerSize);
-
- final int snappedPosition =
- snap.calculateNonDismissingSnapTarget(position).position;
- DockedDividerUtils.calculateBoundsForPosition(snappedPosition, dockSide, tmpRect,
- tmpDL.width(), tmpDL.height(), dividerSize);
- Rect insettedDisplay = new Rect(rotatedDisplayRect);
- insettedDisplay.inset(tmpDL.stableInsets());
- tmpRect.intersect(insettedDisplay);
- minWidth = Math.min(tmpRect.width(), minWidth);
- }
- return (int) (minWidth / dl.density());
- }
-
- static DividerSnapAlgorithm initSnapAlgorithmForRotation(Context context, DisplayLayout dl,
- int dividerSize) {
- final Configuration config = new Configuration();
- config.unset();
- config.orientation = dl.getOrientation();
- Rect tmpRect = new Rect(0, 0, dl.width(), dl.height());
- tmpRect.inset(dl.nonDecorInsets());
- config.windowConfiguration.setAppBounds(tmpRect);
- tmpRect.set(0, 0, dl.width(), dl.height());
- tmpRect.inset(dl.stableInsets());
- config.screenWidthDp = (int) (tmpRect.width() / dl.density());
- config.screenHeightDp = (int) (tmpRect.height() / dl.density());
- final Context rotationContext = context.createConfigurationContext(config);
- return new DividerSnapAlgorithm(
- rotationContext.getResources(), dl.width(), dl.height(), dividerSize,
- config.orientation == ORIENTATION_PORTRAIT, dl.stableInsets());
- }
-
- /**
- * Get the current primary-split side. Determined by its location of {@param bounds} within
- * {@param displayRect} but if both are the same, it will try to dock to each side and determine
- * if allowed in its respected {@param orientation}.
- *
- * @param bounds bounds of the primary split task to get which side is docked
- * @param displayRect bounds of the display that contains the primary split task
- * @param orientation the origination of device
- * @return current primary-split side
- */
- static int getPrimarySplitSide(Rect bounds, Rect displayRect, int orientation) {
- if (orientation == ORIENTATION_PORTRAIT) {
- // Portrait mode, docked either at the top or the bottom.
- final int diff = (displayRect.bottom - bounds.bottom) - (bounds.top - displayRect.top);
- if (diff < 0) {
- return DOCKED_BOTTOM;
- } else {
- // Top is default
- return DOCKED_TOP;
- }
- } else if (orientation == ORIENTATION_LANDSCAPE) {
- // Landscape mode, docked either on the left or on the right.
- final int diff = (displayRect.right - bounds.right) - (bounds.left - displayRect.left);
- if (diff < 0) {
- return DOCKED_RIGHT;
- }
- return DOCKED_LEFT;
- }
- return DOCKED_INVALID;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java
deleted file mode 100644
index 499a9c5fa631..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreen.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import android.graphics.Rect;
-import android.window.WindowContainerToken;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-/**
- * Interface to engage split screen feature.
- */
-@ExternalThread
-public interface LegacySplitScreen {
- /** Called when keyguard showing state changed. */
- void onKeyguardVisibilityChanged(boolean isShowing);
-
- /** Returns {@link DividerView}. */
- DividerView getDividerView();
-
- /** Returns {@code true} if one of the split screen is in minimized mode. */
- boolean isMinimized();
-
- /** Returns {@code true} if the home stack is resizable. */
- boolean isHomeStackResizable();
-
- /** Returns {@code true} if the divider is visible. */
- boolean isDividerVisible();
-
- /** Switch to minimized state if appropriate. */
- void setMinimized(boolean minimized);
-
- /** Called when there's a task undocking. */
- void onUndockingTask();
-
- /** Called when app transition finished. */
- void onAppTransitionFinished();
-
- /** Dumps current status of Split Screen. */
- void dump(PrintWriter pw);
-
- /** Registers listener that gets called whenever the existence of the divider changes. */
- void registerInSplitScreenListener(Consumer<Boolean> listener);
-
- /** Unregisters listener that gets called whenever the existence of the divider changes. */
- void unregisterInSplitScreenListener(Consumer<Boolean> listener);
-
- /** Registers listener that gets called whenever the split screen bounds changes. */
- void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener);
-
- /** @return the container token for the secondary split root task. */
- WindowContainerToken getSecondaryRoot();
-
- /**
- * Splits the primary task if feasible, this is to preserve legacy way to toggle split screen.
- * Like triggering split screen through long pressing recents app button or through
- * {@link android.accessibilityservice.AccessibilityService#GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN}.
- *
- * @return {@code true} if it successes to split the primary task.
- */
- boolean splitPrimaryTask();
-
- /**
- * Exits the split to make the primary task fullscreen.
- */
- void dismissSplitToPrimaryTask();
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
deleted file mode 100644
index 67e487de0993..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenController.java
+++ /dev/null
@@ -1,762 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.animation.AnimationHandler;
-import android.app.ActivityManager;
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.ActivityTaskManager;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.provider.Settings;
-import android.util.Slog;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.widget.Toast;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.policy.DividerSnapAlgorithm;
-import com.android.wm.shell.R;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayChangeController;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayLayout;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TaskStackListenerCallback;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-
-/**
- * Controls split screen feature.
- */
-public class LegacySplitScreenController implements DisplayController.OnDisplaysChangedListener {
- static final boolean DEBUG = false;
-
- private static final String TAG = "SplitScreenCtrl";
- private static final int DEFAULT_APP_TRANSITION_DURATION = 336;
-
- private final Context mContext;
- private final DisplayChangeController.OnDisplayChangingListener mRotationController;
- private final DisplayController mDisplayController;
- private final DisplayImeController mImeController;
- private final DividerImeController mImePositionProcessor;
- private final DividerState mDividerState = new DividerState();
- private final ForcedResizableInfoActivityController mForcedResizableController;
- private final ShellExecutor mMainExecutor;
- private final AnimationHandler mSfVsyncAnimationHandler;
- private final LegacySplitScreenTaskListener mSplits;
- private final SystemWindows mSystemWindows;
- final TransactionPool mTransactionPool;
- private final WindowManagerProxy mWindowManagerProxy;
- private final TaskOrganizer mTaskOrganizer;
- private final SplitScreenImpl mImpl = new SplitScreenImpl();
-
- private final CopyOnWriteArrayList<WeakReference<Consumer<Boolean>>> mDockedStackExistsListeners
- = new CopyOnWriteArrayList<>();
- private final ArrayList<WeakReference<BiConsumer<Rect, Rect>>> mBoundsChangedListeners =
- new ArrayList<>();
-
-
- private DividerWindowManager mWindowManager;
- private DividerView mView;
-
- // Keeps track of real-time split geometry including snap positions and ime adjustments
- private LegacySplitDisplayLayout mSplitLayout;
-
- // Transient: this contains the layout calculated for a new rotation requested by WM. This is
- // kept around so that we can wait for a matching configuration change and then use the exact
- // layout that we sent back to WM.
- private LegacySplitDisplayLayout mRotateSplitLayout;
-
- private boolean mIsKeyguardShowing;
- private boolean mVisible = false;
- private volatile boolean mMinimized = false;
- private volatile boolean mAdjustedForIme = false;
- private boolean mHomeStackResizable = false;
-
- public LegacySplitScreenController(Context context,
- DisplayController displayController, SystemWindows systemWindows,
- DisplayImeController imeController, TransactionPool transactionPool,
- ShellTaskOrganizer shellTaskOrganizer, SyncTransactionQueue syncQueue,
- TaskStackListenerImpl taskStackListener, Transitions transitions,
- ShellExecutor mainExecutor, AnimationHandler sfVsyncAnimationHandler) {
- mContext = context;
- mDisplayController = displayController;
- mSystemWindows = systemWindows;
- mImeController = imeController;
- mMainExecutor = mainExecutor;
- mSfVsyncAnimationHandler = sfVsyncAnimationHandler;
- mForcedResizableController = new ForcedResizableInfoActivityController(context, this,
- mainExecutor);
- mTransactionPool = transactionPool;
- mWindowManagerProxy = new WindowManagerProxy(syncQueue, shellTaskOrganizer);
- mTaskOrganizer = shellTaskOrganizer;
- mSplits = new LegacySplitScreenTaskListener(this, shellTaskOrganizer, transitions,
- syncQueue);
- mImePositionProcessor = new DividerImeController(mSplits, mTransactionPool, mMainExecutor,
- shellTaskOrganizer);
- mRotationController =
- (display, fromRotation, toRotation, wct) -> {
- if (!mSplits.isSplitScreenSupported() || mWindowManagerProxy == null) {
- return;
- }
- WindowContainerTransaction t = new WindowContainerTransaction();
- DisplayLayout displayLayout =
- new DisplayLayout(mDisplayController.getDisplayLayout(display));
- LegacySplitDisplayLayout sdl =
- new LegacySplitDisplayLayout(mContext, displayLayout, mSplits);
- sdl.rotateTo(toRotation);
- mRotateSplitLayout = sdl;
- // snap resets to middle target when not minimized and rotation changed.
- final int position = mMinimized ? mView.mSnapTargetBeforeMinimized.position
- : sdl.getSnapAlgorithm().getMiddleTarget().position;
- DividerSnapAlgorithm snap = sdl.getSnapAlgorithm();
- final DividerSnapAlgorithm.SnapTarget target =
- snap.calculateNonDismissingSnapTarget(position);
- sdl.resizeSplits(target.position, t);
-
- if (isSplitActive() && mHomeStackResizable) {
- mWindowManagerProxy
- .applyHomeTasksMinimized(sdl, mSplits.mSecondary.token, t);
- }
- if (mWindowManagerProxy.queueSyncTransactionIfWaiting(t)) {
- // Because sync transactions are serialized, its possible for an "older"
- // bounds-change to get applied after a screen rotation. In that case, we
- // want to actually defer on that rather than apply immediately. Of course,
- // this means that the bounds may not change until after the rotation so
- // the user might see some artifacts. This should be rare.
- Slog.w(TAG, "Screen rotated while other operations were pending, this may"
- + " result in some graphical artifacts.");
- } else {
- wct.merge(t, true /* transfer */);
- }
- };
-
- mWindowManager = new DividerWindowManager(mSystemWindows);
-
- // No need to listen to display window container or create root tasks if the device is not
- // using legacy split screen.
- if (!context.getResources().getBoolean(com.android.internal.R.bool.config_useLegacySplit)) {
- return;
- }
-
-
- mDisplayController.addDisplayWindowListener(this);
- // Don't initialize the divider or anything until we get the default display.
-
- taskStackListener.addListener(
- new TaskStackListenerCallback() {
- @Override
- public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task,
- boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) {
- if (!wasVisible || task.getWindowingMode()
- != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
- || !mSplits.isSplitScreenSupported()) {
- return;
- }
-
- if (isMinimized()) {
- onUndockingTask();
- }
- }
-
- @Override
- public void onActivityForcedResizable(String packageName, int taskId,
- int reason) {
- mForcedResizableController.activityForcedResizable(packageName, taskId,
- reason);
- }
-
- @Override
- public void onActivityDismissingDockedStack() {
- mForcedResizableController.activityDismissingSplitScreen();
- }
-
- @Override
- public void onActivityLaunchOnSecondaryDisplayFailed() {
- mForcedResizableController.activityLaunchOnSecondaryDisplayFailed();
- }
- });
- }
-
- public LegacySplitScreen asLegacySplitScreen() {
- return mImpl;
- }
-
- public void onSplitScreenSupported() {
- // Set starting tile bounds based on middle target
- final WindowContainerTransaction tct = new WindowContainerTransaction();
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- mSplitLayout.resizeSplits(midPos, tct);
- mTaskOrganizer.applyTransaction(tct);
- }
-
- public void onKeyguardVisibilityChanged(boolean showing) {
- if (!isSplitActive() || mView == null) {
- return;
- }
- mView.setHidden(showing);
- mIsKeyguardShowing = showing;
- }
-
- @Override
- public void onDisplayAdded(int displayId) {
- if (displayId != DEFAULT_DISPLAY) {
- return;
- }
- mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
- mDisplayController.getDisplayLayout(displayId), mSplits);
- mImeController.addPositionProcessor(mImePositionProcessor);
- mDisplayController.addDisplayChangingController(mRotationController);
- if (!ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)) {
- removeDivider();
- return;
- }
- try {
- mSplits.init();
- } catch (Exception e) {
- Slog.e(TAG, "Failed to register docked stack listener", e);
- removeDivider();
- return;
- }
- }
-
- @Override
- public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
- if (displayId != DEFAULT_DISPLAY || !mSplits.isSplitScreenSupported()) {
- return;
- }
- mSplitLayout = new LegacySplitDisplayLayout(mDisplayController.getDisplayContext(displayId),
- mDisplayController.getDisplayLayout(displayId), mSplits);
- if (mRotateSplitLayout == null) {
- int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
- final WindowContainerTransaction tct = new WindowContainerTransaction();
- mSplitLayout.resizeSplits(midPos, tct);
- mTaskOrganizer.applyTransaction(tct);
- } else if (mSplitLayout.mDisplayLayout.rotation()
- == mRotateSplitLayout.mDisplayLayout.rotation()) {
- mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
- mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
- mRotateSplitLayout = null;
- }
- if (isSplitActive()) {
- update(newConfig);
- }
- }
-
- public boolean isMinimized() {
- return mMinimized;
- }
-
- public boolean isHomeStackResizable() {
- return mHomeStackResizable;
- }
-
- public DividerView getDividerView() {
- return mView;
- }
-
- public boolean isDividerVisible() {
- return mView != null && mView.getVisibility() == View.VISIBLE;
- }
-
- /**
- * This indicates that at-least one of the splits has content. This differs from
- * isDividerVisible because the divider is only visible once *everything* is in split mode
- * while this only cares if some things are (eg. while entering/exiting as well).
- */
- public boolean isSplitActive() {
- return mSplits.mPrimary != null && mSplits.mSecondary != null
- && (mSplits.mPrimary.topActivityType != ACTIVITY_TYPE_UNDEFINED
- || mSplits.mSecondary.topActivityType != ACTIVITY_TYPE_UNDEFINED);
- }
-
- public void addDivider(Configuration configuration) {
- Context dctx = mDisplayController.getDisplayContext(mContext.getDisplayId());
- mView = (DividerView)
- LayoutInflater.from(dctx).inflate(R.layout.docked_stack_divider, null);
- mView.setAnimationHandler(mSfVsyncAnimationHandler);
- DisplayLayout displayLayout = mDisplayController.getDisplayLayout(mContext.getDisplayId());
- mView.injectDependencies(this, mWindowManager, mDividerState, mForcedResizableController,
- mSplits, mSplitLayout, mImePositionProcessor, mWindowManagerProxy);
- mView.setVisibility(mVisible ? View.VISIBLE : View.INVISIBLE);
- mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, null /* transaction */);
- final int size = dctx.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.docked_stack_divider_thickness);
- final boolean landscape = configuration.orientation == ORIENTATION_LANDSCAPE;
- final int width = landscape ? size : displayLayout.width();
- final int height = landscape ? displayLayout.height() : size;
- mWindowManager.add(mView, width, height, mContext.getDisplayId());
- }
-
- public void removeDivider() {
- if (mView != null) {
- mView.onDividerRemoved();
- }
- mWindowManager.remove();
- }
-
- public void update(Configuration configuration) {
- final boolean isDividerHidden = mView != null && mIsKeyguardShowing;
-
- removeDivider();
- addDivider(configuration);
-
- if (mMinimized) {
- mView.setMinimizedDockStack(true, mHomeStackResizable, null /* transaction */);
- updateTouchable();
- }
- mView.setHidden(isDividerHidden);
- }
-
- public void onTaskVanished() {
- removeDivider();
- }
-
- public void updateVisibility(final boolean visible) {
- if (DEBUG) Slog.d(TAG, "Updating visibility " + mVisible + "->" + visible);
- if (mVisible != visible) {
- mVisible = visible;
- mView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
-
- if (visible) {
- mView.enterSplitMode(mHomeStackResizable);
- // Update state because animations won't finish.
- mWindowManagerProxy.runInSync(
- t -> mView.setMinimizedDockStack(mMinimized, mHomeStackResizable, t));
-
- } else {
- mView.exitSplitMode();
- mWindowManagerProxy.runInSync(
- t -> mView.setMinimizedDockStack(false, mHomeStackResizable, t));
- }
- // Notify existence listeners
- synchronized (mDockedStackExistsListeners) {
- mDockedStackExistsListeners.removeIf(wf -> {
- Consumer<Boolean> l = wf.get();
- if (l != null) l.accept(visible);
- return l == null;
- });
- }
- }
- }
-
- public void setMinimized(final boolean minimized) {
- if (DEBUG) Slog.d(TAG, "posting ext setMinimized " + minimized + " vis:" + mVisible);
- mMainExecutor.execute(() -> {
- if (DEBUG) Slog.d(TAG, "run posted ext setMinimized " + minimized + " vis:" + mVisible);
- if (!mVisible) {
- return;
- }
- setHomeMinimized(minimized);
- });
- }
-
- public void setHomeMinimized(final boolean minimized) {
- if (DEBUG) {
- Slog.d(TAG, "setHomeMinimized min:" + mMinimized + "->" + minimized + " hrsz:"
- + mHomeStackResizable + " split:" + isDividerVisible());
- }
- WindowContainerTransaction wct = new WindowContainerTransaction();
- final boolean minimizedChanged = mMinimized != minimized;
- // Update minimized state
- if (minimizedChanged) {
- mMinimized = minimized;
- }
- // Always set this because we could be entering split when mMinimized is already true
- wct.setFocusable(mSplits.mPrimary.token, !mMinimized);
-
- // Sync state to DividerView if it exists.
- if (mView != null) {
- final int displayId = mView.getDisplay() != null
- ? mView.getDisplay().getDisplayId() : DEFAULT_DISPLAY;
- // pause ime here (before updateMinimizedDockedStack)
- if (mMinimized) {
- mImePositionProcessor.pause(displayId);
- }
- if (minimizedChanged) {
- // This conflicts with IME adjustment, so only call it when things change.
- mView.setMinimizedDockStack(minimized, getAnimDuration(), mHomeStackResizable);
- }
- if (!mMinimized) {
- // afterwards so it can end any animations started in view
- mImePositionProcessor.resume(displayId);
- }
- }
- updateTouchable();
-
- // If we are only setting focusability, a sync transaction isn't necessary (in fact it
- // can interrupt other animations), so see if it can be submitted on pending instead.
- if (!mWindowManagerProxy.queueSyncTransactionIfWaiting(wct)) {
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- public void setAdjustedForIme(boolean adjustedForIme) {
- if (mAdjustedForIme == adjustedForIme) {
- return;
- }
- mAdjustedForIme = adjustedForIme;
- updateTouchable();
- }
-
- public void updateTouchable() {
- mWindowManager.setTouchable(!mAdjustedForIme);
- }
-
- public void onUndockingTask() {
- if (mView != null) {
- mView.onUndockingTask();
- }
- }
-
- public void onAppTransitionFinished() {
- if (mView == null) {
- return;
- }
- mForcedResizableController.onAppTransitionFinished();
- }
-
- public void dump(PrintWriter pw) {
- pw.print(" mVisible="); pw.println(mVisible);
- pw.print(" mMinimized="); pw.println(mMinimized);
- pw.print(" mAdjustedForIme="); pw.println(mAdjustedForIme);
- }
-
- public long getAnimDuration() {
- float transitionScale = Settings.Global.getFloat(mContext.getContentResolver(),
- Settings.Global.TRANSITION_ANIMATION_SCALE,
- mContext.getResources().getFloat(
- com.android.internal.R.dimen
- .config_appTransitionAnimationDurationScaleDefault));
- final long transitionDuration = DEFAULT_APP_TRANSITION_DURATION;
- return (long) (transitionDuration * transitionScale);
- }
-
- public void registerInSplitScreenListener(Consumer<Boolean> listener) {
- listener.accept(isDividerVisible());
- synchronized (mDockedStackExistsListeners) {
- mDockedStackExistsListeners.add(new WeakReference<>(listener));
- }
- }
-
- public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
- synchronized (mDockedStackExistsListeners) {
- for (int i = mDockedStackExistsListeners.size() - 1; i >= 0; i--) {
- if (mDockedStackExistsListeners.get(i) == listener) {
- mDockedStackExistsListeners.remove(i);
- }
- }
- }
- }
-
- public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
- synchronized (mBoundsChangedListeners) {
- mBoundsChangedListeners.add(new WeakReference<>(listener));
- }
- }
-
- public boolean splitPrimaryTask() {
- try {
- if (ActivityTaskManager.getService().getLockTaskModeState() == LOCK_TASK_MODE_PINNED) {
- return false;
- }
- } catch (RemoteException e) {
- return false;
- }
- if (isSplitActive() || mSplits.mPrimary == null) {
- return false;
- }
-
- // Try fetching the top running task.
- final List<RunningTaskInfo> runningTasks =
- ActivityTaskManager.getInstance().getTasks(1 /* maxNum */);
- if (runningTasks == null || runningTasks.isEmpty()) {
- return false;
- }
- // Note: The set of running tasks from the system is ordered by recency.
- final RunningTaskInfo topRunningTask = runningTasks.get(0);
- final int activityType = topRunningTask.getActivityType();
- if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
- return false;
- }
-
- if (!topRunningTask.supportsSplitScreenMultiWindow) {
- Toast.makeText(mContext, R.string.dock_non_resizeble_failed_to_dock_text,
- Toast.LENGTH_SHORT).show();
- return false;
- }
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Clear out current windowing mode before reparenting to split task.
- wct.setWindowingMode(topRunningTask.token, WINDOWING_MODE_UNDEFINED);
- wct.reparent(topRunningTask.token, mSplits.mPrimary.token, true /* onTop */);
- mWindowManagerProxy.applySyncTransaction(wct);
- return true;
- }
-
- public void dismissSplitToPrimaryTask() {
- startDismissSplit(true /* toPrimaryTask */);
- }
-
- /** Notifies the bounds of split screen changed. */
- public void notifyBoundsChanged(Rect secondaryWindowBounds, Rect secondaryWindowInsets) {
- synchronized (mBoundsChangedListeners) {
- mBoundsChangedListeners.removeIf(wf -> {
- BiConsumer<Rect, Rect> l = wf.get();
- if (l != null) l.accept(secondaryWindowBounds, secondaryWindowInsets);
- return l == null;
- });
- }
- }
-
- public void startEnterSplit() {
- update(mDisplayController.getDisplayContext(
- mContext.getDisplayId()).getResources().getConfiguration());
- // Set resizable directly here because applyEnterSplit already resizes home stack.
- mHomeStackResizable = mWindowManagerProxy.applyEnterSplit(mSplits,
- mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
- }
-
- public void prepareEnterSplitTransition(WindowContainerTransaction outWct) {
- // Set resizable directly here because buildEnterSplit already resizes home stack.
- mHomeStackResizable = mWindowManagerProxy.buildEnterSplit(outWct, mSplits,
- mRotateSplitLayout != null ? mRotateSplitLayout : mSplitLayout);
- }
-
- public void finishEnterSplitTransition(boolean minimized) {
- update(mDisplayController.getDisplayContext(
- mContext.getDisplayId()).getResources().getConfiguration());
- if (minimized) {
- ensureMinimizedSplit();
- } else {
- ensureNormalSplit();
- }
- }
-
- public void startDismissSplit(boolean toPrimaryTask) {
- startDismissSplit(toPrimaryTask, false /* snapped */);
- }
-
- public void startDismissSplit(boolean toPrimaryTask, boolean snapped) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- mSplits.getSplitTransitions().dismissSplit(
- mSplits, mSplitLayout, !toPrimaryTask, snapped);
- } else {
- mWindowManagerProxy.applyDismissSplit(mSplits, mSplitLayout, !toPrimaryTask);
- onDismissSplit();
- }
- }
-
- public void onDismissSplit() {
- updateVisibility(false /* visible */);
- mMinimized = false;
- // Resets divider bar position to undefined, so new divider bar will apply default position
- // next time entering split mode.
- mDividerState.mRatioPositionBeforeMinimized = 0;
- removeDivider();
- mImePositionProcessor.reset();
- }
-
- public void ensureMinimizedSplit() {
- setHomeMinimized(true /* minimized */);
- if (mView != null && !isDividerVisible()) {
- // Wasn't in split-mode yet, so enter now.
- if (DEBUG) {
- Slog.d(TAG, " entering split mode with minimized=true");
- }
- updateVisibility(true /* visible */);
- }
- }
-
- public void ensureNormalSplit() {
- setHomeMinimized(false /* minimized */);
- if (mView != null && !isDividerVisible()) {
- // Wasn't in split-mode, so enter now.
- if (DEBUG) {
- Slog.d(TAG, " enter split mode unminimized ");
- }
- updateVisibility(true /* visible */);
- }
- }
-
- public LegacySplitDisplayLayout getSplitLayout() {
- return mSplitLayout;
- }
-
- public WindowManagerProxy getWmProxy() {
- return mWindowManagerProxy;
- }
-
- public WindowContainerToken getSecondaryRoot() {
- if (mSplits == null || mSplits.mSecondary == null) {
- return null;
- }
- return mSplits.mSecondary.token;
- }
-
- private class SplitScreenImpl implements LegacySplitScreen {
- @Override
- public boolean isMinimized() {
- return mMinimized;
- }
-
- @Override
- public boolean isHomeStackResizable() {
- return mHomeStackResizable;
- }
-
- /**
- * TODO: Remove usage from outside the shell.
- */
- @Override
- public DividerView getDividerView() {
- return LegacySplitScreenController.this.getDividerView();
- }
-
- @Override
- public boolean isDividerVisible() {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = LegacySplitScreenController.this.isDividerVisible();
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to get divider visible");
- }
- return result[0];
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean isShowing) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.onKeyguardVisibilityChanged(isShowing);
- });
- }
-
- @Override
- public void setMinimized(boolean minimized) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.setMinimized(minimized);
- });
- }
-
- @Override
- public void onUndockingTask() {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.onUndockingTask();
- });
- }
-
- @Override
- public void onAppTransitionFinished() {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.onAppTransitionFinished();
- });
- }
-
- @Override
- public void registerInSplitScreenListener(Consumer<Boolean> listener) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.registerInSplitScreenListener(listener);
- });
- }
-
- @Override
- public void unregisterInSplitScreenListener(Consumer<Boolean> listener) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.unregisterInSplitScreenListener(listener);
- });
- }
-
- @Override
- public void registerBoundsChangeListener(BiConsumer<Rect, Rect> listener) {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.registerBoundsChangeListener(listener);
- });
- }
-
- @Override
- public WindowContainerToken getSecondaryRoot() {
- WindowContainerToken[] result = new WindowContainerToken[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = LegacySplitScreenController.this.getSecondaryRoot();
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to get secondary root");
- }
- return result[0];
- }
-
- @Override
- public boolean splitPrimaryTask() {
- boolean[] result = new boolean[1];
- try {
- mMainExecutor.executeBlocking(() -> {
- result[0] = LegacySplitScreenController.this.splitPrimaryTask();
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to split primary task");
- }
- return result[0];
- }
-
- @Override
- public void dismissSplitToPrimaryTask() {
- mMainExecutor.execute(() -> {
- LegacySplitScreenController.this.dismissSplitToPrimaryTask();
- });
- }
-
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> {
- LegacySplitScreenController.this.dump(pw);
- });
- } catch (InterruptedException e) {
- Slog.e(TAG, "Failed to dump LegacySplitScreenController in 2s");
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
deleted file mode 100644
index d2f42c39acd5..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTaskListener.java
+++ /dev/null
@@ -1,376 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TASK_ORG;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.Log;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.TaskOrganizer;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-
-class LegacySplitScreenTaskListener implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = LegacySplitScreenTaskListener.class.getSimpleName();
- private static final boolean DEBUG = LegacySplitScreenController.DEBUG;
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final SyncTransactionQueue mSyncQueue;
- private final SparseArray<SurfaceControl> mLeashByTaskId = new SparseArray<>();
-
- // TODO(shell-transitions): Remove when switched to shell-transitions.
- private final SparseArray<Point> mPositionByTaskId = new SparseArray<>();
-
- RunningTaskInfo mPrimary;
- RunningTaskInfo mSecondary;
- SurfaceControl mPrimarySurface;
- SurfaceControl mSecondarySurface;
- SurfaceControl mPrimaryDim;
- SurfaceControl mSecondaryDim;
- Rect mHomeBounds = new Rect();
- final LegacySplitScreenController mSplitScreenController;
- private boolean mSplitScreenSupported = false;
-
- final SurfaceSession mSurfaceSession = new SurfaceSession();
-
- private final LegacySplitScreenTransitions mSplitTransitions;
-
- LegacySplitScreenTaskListener(LegacySplitScreenController splitScreenController,
- ShellTaskOrganizer shellTaskOrganizer,
- Transitions transitions,
- SyncTransactionQueue syncQueue) {
- mSplitScreenController = splitScreenController;
- mTaskOrganizer = shellTaskOrganizer;
- mSplitTransitions = new LegacySplitScreenTransitions(splitScreenController.mTransactionPool,
- transitions, mSplitScreenController, this);
- transitions.addHandler(mSplitTransitions);
- mSyncQueue = syncQueue;
- }
-
- void init() {
- synchronized (this) {
- try {
- mTaskOrganizer.createRootTask(
- DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, this);
- mTaskOrganizer.createRootTask(
- DEFAULT_DISPLAY, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, this);
- } catch (Exception e) {
- // teardown to prevent callbacks
- mTaskOrganizer.removeListener(this);
- throw e;
- }
- }
- }
-
- boolean isSplitScreenSupported() {
- return mSplitScreenSupported;
- }
-
- SurfaceControl.Transaction getTransaction() {
- return mSplitScreenController.mTransactionPool.acquire();
- }
-
- void releaseTransaction(SurfaceControl.Transaction t) {
- mSplitScreenController.mTransactionPool.release(t);
- }
-
- TaskOrganizer getTaskOrganizer() {
- return mTaskOrganizer;
- }
-
- LegacySplitScreenTransitions getSplitTransitions() {
- return mSplitTransitions;
- }
-
- @Override
- public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
- synchronized (this) {
- if (taskInfo.hasParentTask()) {
- handleChildTaskAppeared(taskInfo, leash);
- return;
- }
-
- final int winMode = taskInfo.getWindowingMode();
- if (winMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- ProtoLog.v(WM_SHELL_TASK_ORG,
- "%s onTaskAppeared Primary taskId=%d", TAG, taskInfo.taskId);
- mPrimary = taskInfo;
- mPrimarySurface = leash;
- } else if (winMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- ProtoLog.v(WM_SHELL_TASK_ORG,
- "%s onTaskAppeared Secondary taskId=%d", TAG, taskInfo.taskId);
- mSecondary = taskInfo;
- mSecondarySurface = leash;
- } else {
- ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared unknown taskId=%d winMode=%d",
- TAG, taskInfo.taskId, winMode);
- }
-
- if (!mSplitScreenSupported && mPrimarySurface != null && mSecondarySurface != null) {
- mSplitScreenSupported = true;
- mSplitScreenController.onSplitScreenSupported();
- ProtoLog.v(WM_SHELL_TASK_ORG, "%s onTaskAppeared Supported", TAG);
-
- // Initialize dim surfaces:
- SurfaceControl.Transaction t = getTransaction();
- mPrimaryDim = SurfaceUtils.makeDimLayer(
- t, mPrimarySurface, "Primary Divider Dim", mSurfaceSession);
- mSecondaryDim = SurfaceUtils.makeDimLayer(
- t, mSecondarySurface, "Secondary Divider Dim", mSurfaceSession);
- t.apply();
- releaseTransaction(t);
- }
- }
- }
-
- @Override
- public void onTaskVanished(RunningTaskInfo taskInfo) {
- synchronized (this) {
- mPositionByTaskId.remove(taskInfo.taskId);
- if (taskInfo.hasParentTask()) {
- mLeashByTaskId.remove(taskInfo.taskId);
- return;
- }
-
- final boolean isPrimaryTask = mPrimary != null
- && taskInfo.token.equals(mPrimary.token);
- final boolean isSecondaryTask = mSecondary != null
- && taskInfo.token.equals(mSecondary.token);
-
- if (mSplitScreenSupported && (isPrimaryTask || isSecondaryTask)) {
- mSplitScreenSupported = false;
-
- SurfaceControl.Transaction t = getTransaction();
- t.remove(mPrimaryDim);
- t.remove(mSecondaryDim);
- t.remove(mPrimarySurface);
- t.remove(mSecondarySurface);
- t.apply();
- releaseTransaction(t);
-
- mSplitScreenController.onTaskVanished();
- }
- }
- }
-
- @Override
- public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
- if (taskInfo.displayId != DEFAULT_DISPLAY) {
- return;
- }
- synchronized (this) {
- if (!taskInfo.supportsMultiWindow) {
- if (mSplitScreenController.isDividerVisible()) {
- // Dismiss the split screen if the task no longer supports multi window.
- if (taskInfo.taskId == mPrimary.taskId
- || taskInfo.parentTaskId == mPrimary.taskId) {
- // If the primary is focused, dismiss to primary.
- mSplitScreenController
- .startDismissSplit(taskInfo.isFocused /* toPrimaryTask */);
- } else {
- // If the secondary is not focused, dismiss to primary.
- mSplitScreenController
- .startDismissSplit(!taskInfo.isFocused /* toPrimaryTask */);
- }
- }
- return;
- }
- if (taskInfo.hasParentTask()) {
- // changed messages are noisy since it reports on every ensureVisibility. This
- // conflicts with legacy app-transitions which "swaps" the position to a
- // leash. For now, only update when position actually changes to avoid
- // poorly-timed duplicate calls.
- if (taskInfo.positionInParent.equals(mPositionByTaskId.get(taskInfo.taskId))) {
- return;
- }
- handleChildTaskChanged(taskInfo);
- } else {
- handleTaskInfoChanged(taskInfo);
- }
- mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent));
- }
- }
-
- private void handleChildTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
- mLeashByTaskId.put(taskInfo.taskId, leash);
- mPositionByTaskId.put(taskInfo.taskId, new Point(taskInfo.positionInParent));
- if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
- }
-
- private void handleChildTaskChanged(RunningTaskInfo taskInfo) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
- updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
- }
-
- private void updateChildTaskSurface(
- RunningTaskInfo taskInfo, SurfaceControl leash, boolean firstAppeared) {
- final Point taskPositionInParent = taskInfo.positionInParent;
- mSyncQueue.runInSync(t -> {
- t.setWindowCrop(leash, null);
- t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
- if (firstAppeared && !Transitions.ENABLE_SHELL_TRANSITIONS) {
- t.setAlpha(leash, 1f);
- t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
- }
- });
- }
-
- /**
- * This is effectively a finite state machine which moves between the various split-screen
- * presentations based on the contents of the split regions.
- */
- private void handleTaskInfoChanged(RunningTaskInfo info) {
- if (!mSplitScreenSupported) {
- // This shouldn't happen; but apparently there is a chance that SysUI crashes without
- // system server receiving binder-death (or maybe it receives binder-death too late?).
- // In this situation, when sys-ui restarts, the split root-tasks will still exist so
- // there is a small window of time during init() where WM might send messages here
- // before init() fails. So, avoid a cycle of crashes by returning early.
- Log.e(TAG, "Got handleTaskInfoChanged when not initialized: " + info);
- return;
- }
- final boolean secondaryImpliedMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
- || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
- && mSplitScreenController.isHomeStackResizable());
- final boolean primaryWasEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- final boolean secondaryWasEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- if (info.token.asBinder() == mPrimary.token.asBinder()) {
- mPrimary = info;
- } else if (info.token.asBinder() == mSecondary.token.asBinder()) {
- mSecondary = info;
- }
- if (DEBUG) {
- Log.d(TAG, "onTaskInfoChanged " + mPrimary + " " + mSecondary);
- }
- if (Transitions.ENABLE_SHELL_TRANSITIONS) return;
- final boolean primaryIsEmpty = mPrimary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- final boolean secondaryIsEmpty = mSecondary.topActivityType == ACTIVITY_TYPE_UNDEFINED;
- final boolean secondaryImpliesMinimize = mSecondary.topActivityType == ACTIVITY_TYPE_HOME
- || (mSecondary.topActivityType == ACTIVITY_TYPE_RECENTS
- && mSplitScreenController.isHomeStackResizable());
- if (primaryIsEmpty == primaryWasEmpty && secondaryWasEmpty == secondaryIsEmpty
- && secondaryImpliedMinimize == secondaryImpliesMinimize) {
- // No relevant changes
- return;
- }
- if (primaryIsEmpty || secondaryIsEmpty) {
- // At-least one of the splits is empty which means we are currently transitioning
- // into or out-of split-screen mode.
- if (DEBUG) {
- Log.d(TAG, " at-least one split empty " + mPrimary.topActivityType
- + " " + mSecondary.topActivityType);
- }
- if (mSplitScreenController.isDividerVisible()) {
- // Was in split-mode, which means we are leaving split, so continue that.
- // This happens when the stack in the primary-split is dismissed.
- if (DEBUG) {
- Log.d(TAG, " was in split, so this means leave it "
- + mPrimary.topActivityType + " " + mSecondary.topActivityType);
- }
- mSplitScreenController.startDismissSplit(false /* toPrimaryTask */);
- } else if (!primaryIsEmpty && primaryWasEmpty && secondaryWasEmpty) {
- // Wasn't in split-mode (both were empty), but now that the primary split is
- // populated, we should fully enter split by moving everything else into secondary.
- // This just tells window-manager to reparent things, the UI will respond
- // when it gets new task info for the secondary split.
- if (DEBUG) {
- Log.d(TAG, " was not in split, but primary is populated, so enter it");
- }
- mSplitScreenController.startEnterSplit();
- }
- } else if (secondaryImpliesMinimize) {
- // Workaround for b/172686383, we can't rely on the sync bounds change transaction for
- // the home task to finish before the last updateChildTaskSurface() call even if it's
- // queued on the sync transaction queue, so ensure that the home task surface is updated
- // again before we minimize
- final ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
- mSplitScreenController.getWmProxy().getHomeAndRecentsTasks(tasks,
- mSplitScreenController.getSecondaryRoot());
- for (int i = 0; i < tasks.size(); i++) {
- final RunningTaskInfo taskInfo = tasks.get(i);
- final SurfaceControl leash = mLeashByTaskId.get(taskInfo.taskId);
- if (leash != null) {
- updateChildTaskSurface(taskInfo, leash, false /* firstAppeared */);
- }
- }
-
- // Both splits are populated but the secondary split has a home/recents stack on top,
- // so enter minimized mode.
- mSplitScreenController.ensureMinimizedSplit();
- } else {
- // Both splits are populated by normal activities, so make sure we aren't minimized.
- mSplitScreenController.ensureNormalSplit();
- }
- }
-
- @Override
- public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- b.setParent(findTaskSurface(taskId));
- }
-
- @Override
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- t.reparent(sc, findTaskSurface(taskId));
- }
-
- private SurfaceControl findTaskSurface(int taskId) {
- if (!mLeashByTaskId.contains(taskId)) {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- return mLeashByTaskId.get(taskId);
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- pw.println(innerPrefix + "mSplitScreenSupported=" + mSplitScreenSupported);
- if (mPrimary != null) pw.println(innerPrefix + "mPrimary.taskId=" + mPrimary.taskId);
- if (mSecondary != null) pw.println(innerPrefix + "mSecondary.taskId=" + mSecondary.taskId);
- }
-
- @Override
- public String toString() {
- return TAG;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
deleted file mode 100644
index b1fa2ac25fe7..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/LegacySplitScreenTransitions.java
+++ /dev/null
@@ -1,348 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.WindowConfiguration;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-
-/** Plays transition animations for split-screen */
-public class LegacySplitScreenTransitions implements Transitions.TransitionHandler {
- private static final String TAG = "SplitScreenTransitions";
-
- public static final int TRANSIT_SPLIT_DISMISS_SNAP = TRANSIT_FIRST_CUSTOM + 10;
-
- private final TransactionPool mTransactionPool;
- private final Transitions mTransitions;
- private final LegacySplitScreenController mSplitScreen;
- private final LegacySplitScreenTaskListener mListener;
-
- private IBinder mPendingDismiss = null;
- private boolean mDismissFromSnap = false;
- private IBinder mPendingEnter = null;
- private IBinder mAnimatingTransition = null;
-
- /** Keeps track of currently running animations */
- private final ArrayList<Animator> mAnimations = new ArrayList<>();
-
- private Transitions.TransitionFinishCallback mFinishCallback = null;
- private SurfaceControl.Transaction mFinishTransaction;
-
- LegacySplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
- @NonNull LegacySplitScreenController splitScreen,
- @NonNull LegacySplitScreenTaskListener listener) {
- mTransactionPool = pool;
- mTransitions = transitions;
- mSplitScreen = splitScreen;
- mListener = listener;
- }
-
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @Nullable TransitionRequestInfo request) {
- WindowContainerTransaction out = null;
- final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
- final @WindowManager.TransitionType int type = request.getType();
- if (mSplitScreen.isDividerVisible()) {
- // try to handle everything while in split-screen
- out = new WindowContainerTransaction();
- if (triggerTask != null) {
- final boolean shouldDismiss =
- // if we close the primary-docked task, then leave split-screen since there
- // is nothing behind it.
- ((type == TRANSIT_CLOSE || type == TRANSIT_TO_BACK)
- && triggerTask.parentTaskId == mListener.mPrimary.taskId)
- // if an activity that is not supported in multi window mode is launched,
- // we also need to leave split-screen.
- || ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
- && !triggerTask.supportsMultiWindow);
- // In both cases, dismiss the primary
- if (shouldDismiss) {
- WindowManagerProxy.buildDismissSplit(out, mListener,
- mSplitScreen.getSplitLayout(), true /* dismiss */);
- if (type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT) {
- out.reorder(triggerTask.token, true /* onTop */);
- }
- mPendingDismiss = transition;
- }
- }
- } else if (triggerTask != null) {
- // Not in split mode, so look for an open with a trigger task.
- if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)
- && triggerTask.configuration.windowConfiguration.getWindowingMode()
- == WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
- out = new WindowContainerTransaction();
- mSplitScreen.prepareEnterSplitTransition(out);
- mPendingEnter = transition;
- }
- }
- return out;
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
- final float end = show ? 1.f : 0.f;
- final float start = 1.f - end;
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(start, end);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setAlpha(leash, end);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
- @NonNull Rect startBounds, @NonNull Rect endBounds) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setWindowCrop(leash,
- (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
- (int) (startBounds.height() * (1.f - fraction)
- + endBounds.height() * fraction));
- transaction.setPosition(leash,
- startBounds.left * (1.f - fraction) + endBounds.left * fraction,
- startBounds.top * (1.f - fraction) + endBounds.top * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setWindowCrop(leash, 0, 0);
- transaction.setPosition(leash, endBounds.left, endBounds.top);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mPendingDismiss && transition != mPendingEnter) {
- // If we're not in split-mode, just abort
- if (!mSplitScreen.isDividerVisible()) return false;
- // Check to see if HOME is involved
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null
- || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) continue;
- if (change.getMode() == TRANSIT_OPEN || change.getMode() == TRANSIT_TO_FRONT) {
- mSplitScreen.ensureMinimizedSplit();
- } else if (change.getMode() == TRANSIT_CLOSE
- || change.getMode() == TRANSIT_TO_BACK) {
- mSplitScreen.ensureNormalSplit();
- }
- }
- // Use normal animations.
- return false;
- }
-
- mFinishCallback = finishCallback;
- mFinishTransaction = mTransactionPool.acquire();
- mAnimatingTransition = transition;
-
- // Play fade animations
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
-
- if (mode == TRANSIT_CHANGE) {
- if (change.getParent() != null) {
- // This is probably reparented, so we want the parent to be immediately visible
- final TransitionInfo.Change parentChange = info.getChange(change.getParent());
- startTransaction.show(parentChange.getLeash());
- startTransaction.setAlpha(parentChange.getLeash(), 1.f);
- // and then animate this layer outside the parent (since, for example, this is
- // the home task animating from fullscreen to part-screen).
- startTransaction.reparent(leash, info.getRootLeash());
- startTransaction.setLayer(leash, info.getChanges().size() - i);
- // build the finish reparent/reposition
- mFinishTransaction.reparent(leash, parentChange.getLeash());
- mFinishTransaction.setPosition(leash,
- change.getEndRelOffset().x, change.getEndRelOffset().y);
- }
- // TODO(shell-transitions): screenshot here
- final Rect startBounds = new Rect(change.getStartAbsBounds());
- final boolean isHome = change.getTaskInfo() != null
- && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME;
- if (mPendingDismiss == transition && mDismissFromSnap && !isHome) {
- // Home is special since it doesn't move during fling. Everything else, though,
- // when dismissing from snap, the top/left is at 0,0.
- startBounds.offsetTo(0, 0);
- }
- final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- startExampleResizeAnimation(leash, startBounds, endBounds);
- }
- if (change.getParent() != null) {
- continue;
- }
-
- if (transition == mPendingEnter
- && mListener.mPrimary.token.equals(change.getContainer())
- || mListener.mSecondary.token.equals(change.getContainer())) {
- startTransaction.setWindowCrop(leash, change.getStartAbsBounds().width(),
- change.getStartAbsBounds().height());
- if (mListener.mPrimary.token.equals(change.getContainer())) {
- // Move layer to top since we want it above the oversized home task during
- // animation even though home task is on top in hierarchy.
- startTransaction.setLayer(leash, info.getChanges().size() + 1);
- }
- }
- boolean isOpening = Transitions.isOpeningType(info.getType());
- if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
- // fade in
- startExampleAnimation(leash, true /* show */);
- } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
- // fade out
- if (transition == mPendingDismiss && mDismissFromSnap) {
- // Dismissing via snap-to-top/bottom means that the dismissed task is already
- // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
- // and don't animate it so it doesn't pop-in when reparented.
- startTransaction.setAlpha(leash, 0.f);
- } else {
- startExampleAnimation(leash, false /* show */);
- }
- }
- }
- if (transition == mPendingEnter) {
- // If entering, check if we should enter into minimized or normal split
- boolean homeIsVisible = false;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() == null
- || change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME) {
- continue;
- }
- homeIsVisible = change.getMode() == TRANSIT_OPEN
- || change.getMode() == TRANSIT_TO_FRONT
- || change.getMode() == TRANSIT_CHANGE;
- break;
- }
- mSplitScreen.finishEnterSplitTransition(homeIsVisible);
- }
- startTransaction.apply();
- onFinish();
- return true;
- }
-
- @ExternalThread
- void dismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
- boolean dismissOrMaximize, boolean snapped) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- WindowManagerProxy.buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
- mTransitions.getMainExecutor().execute(() -> {
- mDismissFromSnap = snapped;
- mPendingDismiss = mTransitions.startTransition(TRANSIT_SPLIT_DISMISS_SNAP, wct, this);
- });
- }
-
- private void onFinish() {
- if (!mAnimations.isEmpty()) return;
- mFinishTransaction.apply();
- mTransactionPool.release(mFinishTransaction);
- mFinishTransaction = null;
- mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
- mFinishCallback = null;
- if (mAnimatingTransition == mPendingEnter) {
- mPendingEnter = null;
- }
- if (mAnimatingTransition == mPendingDismiss) {
- mSplitScreen.onDismissSplit();
- mPendingDismiss = null;
- }
- mDismissFromSnap = false;
- mAnimatingTransition = null;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java
deleted file mode 100644
index 1e9223cbe3e2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/MinimizedDockShadow.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.LinearGradient;
-import android.graphics.Paint;
-import android.graphics.Shader;
-import android.util.AttributeSet;
-import android.view.View;
-import android.view.WindowManager;
-
-import com.android.wm.shell.R;
-
-/**
- * Shadow for the minimized dock state on homescreen.
- */
-public class MinimizedDockShadow extends View {
-
- private final Paint mShadowPaint = new Paint();
-
- private int mDockSide = WindowManager.DOCKED_INVALID;
-
- public MinimizedDockShadow(Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- }
-
- void setDockSide(int dockSide) {
- if (dockSide != mDockSide) {
- mDockSide = dockSide;
- updatePaint(getLeft(), getTop(), getRight(), getBottom());
- invalidate();
- }
- }
-
- private void updatePaint(int left, int top, int right, int bottom) {
- int startColor = mContext.getResources().getColor(
- R.color.minimize_dock_shadow_start, null);
- int endColor = mContext.getResources().getColor(
- R.color.minimize_dock_shadow_end, null);
- final int middleColor = Color.argb(
- (Color.alpha(startColor) + Color.alpha(endColor)) / 2, 0, 0, 0);
- final int quarter = Color.argb(
- (int) (Color.alpha(startColor) * 0.25f + Color.alpha(endColor) * 0.75f),
- 0, 0, 0);
- if (mDockSide == WindowManager.DOCKED_TOP) {
- mShadowPaint.setShader(new LinearGradient(
- 0, 0, 0, bottom - top,
- new int[] { startColor, middleColor, quarter, endColor },
- new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
- } else if (mDockSide == WindowManager.DOCKED_LEFT) {
- mShadowPaint.setShader(new LinearGradient(
- 0, 0, right - left, 0,
- new int[] { startColor, middleColor, quarter, endColor },
- new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
- } else if (mDockSide == WindowManager.DOCKED_RIGHT) {
- mShadowPaint.setShader(new LinearGradient(
- right - left, 0, 0, 0,
- new int[] { startColor, middleColor, quarter, endColor },
- new float[] { 0f, 0.35f, 0.6f, 1f }, Shader.TileMode.CLAMP));
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (changed) {
- updatePaint(left, top, right, bottom);
- invalidate();
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.drawRect(0, 0, getWidth(), getHeight(), mShadowPaint);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
deleted file mode 100644
index e42e43bbc2be..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/WindowManagerProxy.java
+++ /dev/null
@@ -1,386 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.legacysplitscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.content.res.Configuration.ORIENTATION_UNDEFINED;
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.graphics.Rect;
-import android.os.RemoteException;
-import android.util.Log;
-import android.view.Display;
-import android.view.SurfaceControl;
-import android.view.WindowManagerGlobal;
-import android.window.TaskOrganizer;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Proxy to simplify calls into window manager/activity manager
- */
-class WindowManagerProxy {
-
- private static final String TAG = "WindowManagerProxy";
- private static final int[] HOME_AND_RECENTS = {ACTIVITY_TYPE_HOME, ACTIVITY_TYPE_RECENTS};
- private static final int[] CONTROLLED_ACTIVITY_TYPES = {
- ACTIVITY_TYPE_STANDARD,
- ACTIVITY_TYPE_HOME,
- ACTIVITY_TYPE_RECENTS,
- ACTIVITY_TYPE_UNDEFINED
- };
- private static final int[] CONTROLLED_WINDOWING_MODES = {
- WINDOWING_MODE_FULLSCREEN,
- WINDOWING_MODE_SPLIT_SCREEN_SECONDARY,
- WINDOWING_MODE_UNDEFINED
- };
-
- @GuardedBy("mDockedRect")
- private final Rect mDockedRect = new Rect();
-
- private final Rect mTmpRect1 = new Rect();
-
- @GuardedBy("mDockedRect")
- private final Rect mTouchableRegion = new Rect();
-
- private final SyncTransactionQueue mSyncTransactionQueue;
- private final TaskOrganizer mTaskOrganizer;
-
- WindowManagerProxy(SyncTransactionQueue syncQueue, TaskOrganizer taskOrganizer) {
- mSyncTransactionQueue = syncQueue;
- mTaskOrganizer = taskOrganizer;
- }
-
- void dismissOrMaximizeDocked(final LegacySplitScreenTaskListener tiles,
- LegacySplitDisplayLayout layout, final boolean dismissOrMaximize) {
- if (Transitions.ENABLE_SHELL_TRANSITIONS) {
- tiles.mSplitScreenController.startDismissSplit(!dismissOrMaximize, true /* snapped */);
- } else {
- applyDismissSplit(tiles, layout, dismissOrMaximize);
- }
- }
-
- public void setResizing(final boolean resizing) {
- try {
- ActivityTaskManager.getService().setSplitScreenResizing(resizing);
- } catch (RemoteException e) {
- Log.w(TAG, "Error calling setDockedStackResizing: " + e);
- }
- }
-
- /** Sets a touch region */
- public void setTouchRegion(Rect region) {
- try {
- synchronized (mDockedRect) {
- mTouchableRegion.set(region);
- }
- WindowManagerGlobal.getWindowManagerService().setDockedTaskDividerTouchRegion(
- mTouchableRegion);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set touchable region: " + e);
- }
- }
-
- void applyResizeSplits(int position, LegacySplitDisplayLayout splitLayout) {
- WindowContainerTransaction t = new WindowContainerTransaction();
- splitLayout.resizeSplits(position, t);
- applySyncTransaction(t);
- }
-
- boolean getHomeAndRecentsTasks(List<ActivityManager.RunningTaskInfo> out,
- WindowContainerToken parent) {
- boolean resizable = false;
- List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
- ? mTaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
- : mTaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
- for (int i = 0, n = rootTasks.size(); i < n; ++i) {
- final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
- out.add(ti);
- if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
- resizable = ti.isResizeable;
- }
- }
- return resizable;
- }
-
- /**
- * Assign a fixed override-bounds to home tasks that reflect their geometry while the primary
- * split is minimized. This actually "sticks out" of the secondary split area, but when in
- * minimized mode, the secondary split gets a 'negative' crop to expose it.
- */
- boolean applyHomeTasksMinimized(LegacySplitDisplayLayout layout, WindowContainerToken parent,
- @NonNull WindowContainerTransaction wct) {
- // Resize the home/recents stacks to the larger minimized-state size
- final Rect homeBounds;
- final ArrayList<ActivityManager.RunningTaskInfo> homeStacks = new ArrayList<>();
- boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
- if (isHomeResizable) {
- homeBounds = layout.calcResizableMinimizedHomeStackBounds();
- } else {
- // home is not resizable, so lock it to its inherent orientation size.
- homeBounds = new Rect(0, 0, 0, 0);
- for (int i = homeStacks.size() - 1; i >= 0; --i) {
- if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_HOME) {
- final int orient = homeStacks.get(i).configuration.orientation;
- final boolean displayLandscape = layout.mDisplayLayout.isLandscape();
- final boolean isLandscape = orient == ORIENTATION_LANDSCAPE
- || (orient == ORIENTATION_UNDEFINED && displayLandscape);
- homeBounds.right = isLandscape == displayLandscape
- ? layout.mDisplayLayout.width() : layout.mDisplayLayout.height();
- homeBounds.bottom = isLandscape == displayLandscape
- ? layout.mDisplayLayout.height() : layout.mDisplayLayout.width();
- break;
- }
- }
- }
- for (int i = homeStacks.size() - 1; i >= 0; --i) {
- // For non-resizable homes, the minimized size is actually the fullscreen-size. As a
- // result, we don't minimize for recents since it only shows half-size screenshots.
- if (!isHomeResizable) {
- if (homeStacks.get(i).topActivityType == ACTIVITY_TYPE_RECENTS) {
- continue;
- }
- wct.setWindowingMode(homeStacks.get(i).token, WINDOWING_MODE_FULLSCREEN);
- }
- wct.setBounds(homeStacks.get(i).token, homeBounds);
- }
- layout.mTiles.mHomeBounds.set(homeBounds);
- return isHomeResizable;
- }
-
- /** @see #buildEnterSplit */
- boolean applyEnterSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout) {
- // Set launchtile first so that any stack created after
- // getAllRootTaskInfos and before reparent (even if unlikely) are placed
- // correctly.
- WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setLaunchRoot(tiles.mSecondary.token, CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES);
- final boolean isHomeResizable = buildEnterSplit(wct, tiles, layout);
- applySyncTransaction(wct);
- return isHomeResizable;
- }
-
- /**
- * Finishes entering split-screen by reparenting all FULLSCREEN tasks into the secondary split.
- * This assumes there is already something in the primary split since that is usually what
- * triggers a call to this. In the same transaction, this overrides the home task bounds via
- * {@link #applyHomeTasksMinimized}.
- *
- * @return whether the home stack is resizable
- */
- boolean buildEnterSplit(WindowContainerTransaction outWct, LegacySplitScreenTaskListener tiles,
- LegacySplitDisplayLayout layout) {
- List<ActivityManager.RunningTaskInfo> rootTasks =
- mTaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
- if (rootTasks.isEmpty()) {
- return false;
- }
- ActivityManager.RunningTaskInfo topHomeTask = null;
- for (int i = rootTasks.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
- // Check whether the task can be moved to split secondary.
- if (!rootTask.supportsMultiWindow && rootTask.topActivityType != ACTIVITY_TYPE_HOME) {
- continue;
- }
- // Only move split controlling tasks to split secondary.
- final int windowingMode = rootTask.getWindowingMode();
- if (!ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, windowingMode)
- || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, rootTask.getActivityType())
- // Excludes split screen secondary due to it's the root we're reparenting to.
- || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
- continue;
- }
- // Since this iterates from bottom to top, update topHomeTask for every fullscreen task
- // so it will be left with the status of the top one.
- topHomeTask = isHomeOrRecentTask(rootTask) ? rootTask : null;
- outWct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
- }
- // Move the secondary split-forward.
- outWct.reorder(tiles.mSecondary.token, true /* onTop */);
- boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */,
- outWct);
- if (topHomeTask != null && !Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Translate/update-crop of secondary out-of-band with sync transaction -- Until BALST
- // is enabled, this temporarily syncs the home surface position with offset until
- // sync transaction finishes.
- outWct.setBoundsChangeTransaction(topHomeTask.token, tiles.mHomeBounds);
- }
- return isHomeResizable;
- }
-
- static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
- final int atype = ti.getActivityType();
- return atype == ACTIVITY_TYPE_HOME || atype == ACTIVITY_TYPE_RECENTS;
- }
-
- /** @see #buildDismissSplit */
- void applyDismissSplit(LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
- boolean dismissOrMaximize) {
- // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
- // plus specific APIs to clean this up.
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Set launch root first so that any task created after getChildContainers and
- // before reparent (pretty unlikely) are put into fullscreen.
- wct.setLaunchRoot(tiles.mSecondary.token, null, null);
- buildDismissSplit(wct, tiles, layout, dismissOrMaximize);
- applySyncTransaction(wct);
- }
-
- /**
- * Reparents all tile members back to their display and resets home task override bounds.
- * @param dismissOrMaximize When {@code true} this resolves the split by closing the primary
- * split (thus resulting in the top of the secondary split becoming
- * fullscreen. {@code false} resolves the other way.
- */
- static void buildDismissSplit(WindowContainerTransaction outWct,
- LegacySplitScreenTaskListener tiles, LegacySplitDisplayLayout layout,
- boolean dismissOrMaximize) {
- // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
- // plus specific APIs to clean this up.
- final TaskOrganizer taskOrg = tiles.getTaskOrganizer();
- List<ActivityManager.RunningTaskInfo> primaryChildren =
- taskOrg.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
- List<ActivityManager.RunningTaskInfo> secondaryChildren =
- taskOrg.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
- // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
- // as a result, the above will not capture any tasks; yet, we need to clean-up the
- // home task bounds.
- List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
- taskOrg.getRootTasks(DEFAULT_DISPLAY, HOME_AND_RECENTS);
- // Filter out the root split tasks
- freeHomeAndRecents.removeIf(p -> p.token.equals(tiles.mSecondary.token)
- || p.token.equals(tiles.mPrimary.token));
-
- if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
- && freeHomeAndRecents.isEmpty()) {
- return;
- }
- if (dismissOrMaximize) {
- // Dismissing, so move all primary split tasks first
- for (int i = primaryChildren.size() - 1; i >= 0; --i) {
- outWct.reparent(primaryChildren.get(i).token, null /* parent */,
- true /* onTop */);
- }
- boolean homeOnTop = false;
- // Don't need to worry about home tasks because they are already in the "proper"
- // order within the secondary split.
- for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
- outWct.reparent(ti.token, null /* parent */, true /* onTop */);
- if (isHomeOrRecentTask(ti)) {
- outWct.setBounds(ti.token, null);
- outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
- if (i == 0) {
- homeOnTop = true;
- }
- }
- }
- if (homeOnTop && !Transitions.ENABLE_SHELL_TRANSITIONS) {
- // Translate/update-crop of secondary out-of-band with sync transaction -- instead
- // play this in sync with new home-app frame because until BALST is enabled this
- // shows up on screen before the syncTransaction returns.
- // We only have access to the secondary root surface, though, so in order to
- // position things properly, we have to take into account the existing negative
- // offset/crop of the minimized-home task.
- final boolean landscape = layout.mDisplayLayout.isLandscape();
- final int posX = landscape ? layout.mSecondary.left - tiles.mHomeBounds.left
- : layout.mSecondary.left;
- final int posY = landscape ? layout.mSecondary.top
- : layout.mSecondary.top - tiles.mHomeBounds.top;
- final SurfaceControl.Transaction sft = new SurfaceControl.Transaction();
- sft.setPosition(tiles.mSecondarySurface, posX, posY);
- final Rect crop = new Rect(0, 0, layout.mDisplayLayout.width(),
- layout.mDisplayLayout.height());
- crop.offset(-posX, -posY);
- sft.setWindowCrop(tiles.mSecondarySurface, crop);
- outWct.setBoundsChangeTransaction(tiles.mSecondary.token, sft);
- }
- } else {
- // Maximize, so move non-home secondary split first
- for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
- if (isHomeOrRecentTask(secondaryChildren.get(i))) {
- continue;
- }
- outWct.reparent(secondaryChildren.get(i).token, null /* parent */,
- true /* onTop */);
- }
- // Find and place home tasks in-between. This simulates the fact that there was
- // nothing behind the primary split's tasks.
- for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
- if (isHomeOrRecentTask(ti)) {
- outWct.reparent(ti.token, null /* parent */, true /* onTop */);
- // reset bounds and mode too
- outWct.setBounds(ti.token, null);
- outWct.setWindowingMode(ti.token, WINDOWING_MODE_UNDEFINED);
- }
- }
- for (int i = primaryChildren.size() - 1; i >= 0; --i) {
- outWct.reparent(primaryChildren.get(i).token, null /* parent */,
- true /* onTop */);
- }
- }
- for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
- outWct.setBounds(freeHomeAndRecents.get(i).token, null);
- outWct.setWindowingMode(freeHomeAndRecents.get(i).token, WINDOWING_MODE_UNDEFINED);
- }
- // Reset focusable to true
- outWct.setFocusable(tiles.mPrimary.token, true /* focusable */);
- }
-
- /**
- * Utility to apply a sync transaction serially with other sync transactions.
- *
- * @see SyncTransactionQueue#queue
- */
- void applySyncTransaction(WindowContainerTransaction wct) {
- mSyncTransactionQueue.queue(wct);
- }
-
- /**
- * @see SyncTransactionQueue#queueIfWaiting
- */
- boolean queueSyncTransactionIfWaiting(WindowContainerTransaction wct) {
- return mSyncTransactionQueue.queueIfWaiting(wct);
- }
-
- /**
- * @see SyncTransactionQueue#runInSync
- */
- void runInSync(SyncTransactionQueue.TransactionRunnable runnable) {
- mSyncTransactionQueue.runInSync(runnable);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
index b00182f36cc8..76c0f41997ad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHanded.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.onehanded;
-import android.content.res.Configuration;
import android.os.SystemProperties;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -83,17 +82,7 @@ public interface OneHanded {
void registerTransitionCallback(OneHandedTransitionCallback callback);
/**
- * Receive onConfigurationChanged() events
- */
- void onConfigChanged(Configuration newConfig);
-
- /**
* Notifies when user switch complete
*/
void onUserSwitch(int userId);
-
- /**
- * Notifies when keyguard visibility changed
- */
- void onKeyguardVisibilityChanged(boolean showing);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 179b725ab210..24f02ac1a6cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -28,17 +28,16 @@ import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
import android.annotation.BinderThread;
import android.content.ComponentName;
import android.content.Context;
-import android.content.om.IOverlayManager;
import android.content.res.Configuration;
import android.database.ContentObserver;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.Settings;
import android.util.Slog;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
+import android.window.DisplayAreaInfo;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
@@ -55,6 +54,9 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerCallback;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.common.annotations.ExternalThread;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import java.io.PrintWriter;
@@ -62,7 +64,8 @@ import java.io.PrintWriter;
* Manages and manipulates the one handed states, transitions, and gesture for phones.
*/
public class OneHandedController implements RemoteCallable<OneHandedController>,
- DisplayChangeController.OnDisplayChangingListener {
+ DisplayChangeController.OnDisplayChangingListener, ConfigurationChangeListener,
+ KeyguardChangeListener {
private static final String TAG = "OneHandedController";
private static final String ONE_HANDED_MODE_OFFSET_PERCENTAGE =
@@ -82,6 +85,7 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
private Context mContext;
+ private final ShellController mShellController;
private final AccessibilityManager mAccessibilityManager;
private final DisplayController mDisplayController;
private final OneHandedSettingsUtil mOneHandedSettingsUtil;
@@ -91,7 +95,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
private final OneHandedState mState;
private final OneHandedTutorialHandler mTutorialHandler;
private final TaskStackListenerImpl mTaskStackListener;
- private final IOverlayManager mOverlayManager;
private final ShellExecutor mMainExecutor;
private final Handler mMainHandler;
private final OneHandedImpl mImpl = new OneHandedImpl();
@@ -190,8 +193,9 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
* Creates {@link OneHandedController}, returns {@code null} if the feature is not supported.
*/
public static OneHandedController create(
- Context context, WindowManager windowManager, DisplayController displayController,
- DisplayLayout displayLayout, TaskStackListenerImpl taskStackListener,
+ Context context, ShellController shellController, WindowManager windowManager,
+ DisplayController displayController, DisplayLayout displayLayout,
+ TaskStackListenerImpl taskStackListener,
InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
ShellExecutor mainExecutor, Handler mainHandler) {
OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
@@ -209,16 +213,15 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
context, displayLayout, settingsUtil, animationController, tutorialHandler,
jankMonitor, mainExecutor);
OneHandedUiEventLogger oneHandedUiEventsLogger = new OneHandedUiEventLogger(uiEventLogger);
- IOverlayManager overlayManager = IOverlayManager.Stub.asInterface(
- ServiceManager.getService(Context.OVERLAY_SERVICE));
- return new OneHandedController(context, displayController, organizer, touchHandler,
- tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler, oneHandedState,
- jankMonitor, oneHandedUiEventsLogger, overlayManager, taskStackListener,
+ return new OneHandedController(context, shellController, displayController, organizer,
+ touchHandler, tutorialHandler, settingsUtil, accessibilityUtil, timeoutHandler,
+ oneHandedState, oneHandedUiEventsLogger, taskStackListener,
mainExecutor, mainHandler);
}
@VisibleForTesting
OneHandedController(Context context,
+ ShellController shellController,
DisplayController displayController,
OneHandedDisplayAreaOrganizer displayAreaOrganizer,
OneHandedTouchHandler touchHandler,
@@ -227,13 +230,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
OneHandedAccessibilityUtil oneHandedAccessibilityUtil,
OneHandedTimeoutHandler timeoutHandler,
OneHandedState state,
- InteractionJankMonitor jankMonitor,
OneHandedUiEventLogger uiEventsLogger,
- IOverlayManager overlayManager,
TaskStackListenerImpl taskStackListener,
ShellExecutor mainExecutor,
Handler mainHandler) {
mContext = context;
+ mShellController = shellController;
mOneHandedSettingsUtil = settingsUtil;
mOneHandedAccessibilityUtil = oneHandedAccessibilityUtil;
mDisplayAreaOrganizer = displayAreaOrganizer;
@@ -241,7 +243,6 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mTouchHandler = touchHandler;
mState = state;
mTutorialHandler = tutorialHandler;
- mOverlayManager = overlayManager;
mMainExecutor = mainExecutor;
mMainHandler = mainHandler;
mOneHandedUiEventLogger = uiEventsLogger;
@@ -279,6 +280,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mAccessibilityStateChangeListener);
mState.addSListeners(mTutorialHandler);
+ mShellController.addConfigurationChangeListener(this);
+ mShellController.addKeyguardChangeListener(this);
}
public OneHanded asOneHanded() {
@@ -594,7 +597,8 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mLockedDisabled = locked && !enabled;
}
- private void onConfigChanged(Configuration newConfig) {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
if (mTutorialHandler == null) {
return;
}
@@ -604,8 +608,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
mTutorialHandler.onConfigurationChanged();
}
- private void onKeyguardVisibilityChanged(boolean showing) {
- mKeyguardShowing = showing;
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mKeyguardShowing = visible;
+ stopOneHanded();
}
private void onUserSwitch(int newUserId) {
@@ -659,11 +666,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
}
/**
- * Handles rotation based on OnDisplayChangingListener callback
+ * Handles display change based on OnDisplayChangingListener callback
*/
@Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction wct) {
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
if (!isInitialized()) {
return;
}
@@ -674,9 +681,12 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
return;
}
+ if (mState.getState() == STATE_ACTIVE) {
+ mOneHandedUiEventLogger.writeEvent(
+ OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
+ }
+
mDisplayAreaOrganizer.onRotateDisplay(mContext, toRotation, wct);
- mOneHandedUiEventLogger.writeEvent(
- OneHandedUiEventLogger.EVENT_ONE_HANDED_TRIGGER_ROTATION_OUT);
}
/**
@@ -750,25 +760,11 @@ public class OneHandedController implements RemoteCallable<OneHandedController>,
}
@Override
- public void onConfigChanged(Configuration newConfig) {
- mMainExecutor.execute(() -> {
- OneHandedController.this.onConfigChanged(newConfig);
- });
- }
-
- @Override
public void onUserSwitch(int userId) {
mMainExecutor.execute(() -> {
OneHandedController.this.onUserSwitch(userId);
});
}
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- OneHandedController.this.onKeyguardVisibilityChanged(showing);
- });
- }
}
/**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
index f61d1b95bd85..451afa08040c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java
@@ -159,6 +159,10 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer {
@Override
public void onDisplayAreaVanished(@NonNull DisplayAreaInfo displayAreaInfo) {
+ final SurfaceControl leash = mDisplayAreaTokenMap.get(displayAreaInfo.token);
+ if (leash != null) {
+ leash.release();
+ }
mDisplayAreaTokenMap.remove(displayAreaInfo.token);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
index 3b3091a9caf3..38631ce26cd1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/Pip.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.pip;
-import android.content.res.Configuration;
import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
@@ -44,24 +43,6 @@ public interface Pip {
}
/**
- * Called when configuration is changed.
- */
- default void onConfigurationChanged(Configuration newConfig) {
- }
-
- /**
- * Called when display size or font size of settings changed
- */
- default void onDensityOrFontScaleChanged() {
- }
-
- /**
- * Called when overlay package change invoked.
- */
- default void onOverlayChanged() {
- }
-
- /**
* Called when SysUI state changed.
*
* @param isSysUiStateValid Is SysUI state valid or not.
@@ -86,12 +67,12 @@ public interface Pip {
}
/**
- * Registers the pinned stack animation listener.
+ * Set the callback when {@link PipTaskOrganizer#isInPip()} state is changed.
*
- * @param callback The callback of pinned stack animation.
+ * @param callback The callback accepts the result of {@link PipTaskOrganizer#isInPip()}
+ * when it's changed.
*/
- default void setPinnedStackAnimationListener(Consumer<Boolean> callback) {
- }
+ default void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {}
/**
* Set the pinned stack with {@link PipAnimationController.AnimationType}
@@ -120,23 +101,6 @@ public interface Pip {
default void removePipExclusionBoundsChangeListener(Consumer<Rect> listener) { }
/**
- * Called when the visibility of keyguard is changed.
- * @param showing {@code true} if keyguard is now showing, {@code false} otherwise.
- * @param animating {@code true} if system is animating between keyguard and surface behind,
- * this only makes sense when showing is {@code false}.
- */
- default void onKeyguardVisibilityChanged(boolean showing, boolean animating) { }
-
- /**
- * Called when the dismissing animation keyguard and surfaces behind is finished.
- * See also {@link #onKeyguardVisibilityChanged(boolean, boolean)}.
- *
- * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
- * keyguard dismiss animation.
- */
- default void onKeyguardDismissAnimationFinished() { }
-
- /**
* Dump the current state and information if need.
*
* @param pw The stream to dump information to.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 4eba1697b595..cf2734c375f2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -591,7 +591,7 @@ public class PipAnimationController {
final Rect insets = computeInsets(fraction);
getSurfaceTransactionHelper().scaleAndCrop(tx, leash,
sourceHintRect, initialSourceValue, bounds, insets,
- isInPipDirection);
+ isInPipDirection, fraction);
if (shouldApplyCornerRadius()) {
final Rect sourceBounds = new Rect(initialContainerRect);
sourceBounds.inset(insets);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
index a017a2674359..3ac08a66100a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipSurfaceTransactionHelper.java
@@ -39,6 +39,10 @@ public class PipSurfaceTransactionHelper {
private int mCornerRadius;
private int mShadowRadius;
+ public PipSurfaceTransactionHelper(Context context) {
+ onDensityOrFontScaleChanged(context);
+ }
+
/**
* Called when display size or font size of settings changed
*
@@ -104,7 +108,7 @@ public class PipSurfaceTransactionHelper {
public PipSurfaceTransactionHelper scaleAndCrop(SurfaceControl.Transaction tx,
SurfaceControl leash, Rect sourceRectHint,
Rect sourceBounds, Rect destinationBounds, Rect insets,
- boolean isInPipDirection) {
+ boolean isInPipDirection, float fraction) {
mTmpDestinationRect.set(sourceBounds);
// Similar to {@link #scale}, we want to position the surface relative to the screen
// coordinates so offset the bounds to 0,0
@@ -116,9 +120,13 @@ public class PipSurfaceTransactionHelper {
if (isInPipDirection
&& sourceRectHint != null && sourceRectHint.width() < sourceBounds.width()) {
// scale by sourceRectHint if it's not edge-to-edge, for entering PiP transition only.
- scale = sourceBounds.width() <= sourceBounds.height()
+ final float endScale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceRectHint.width()
: (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceBounds.width() <= sourceBounds.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - fraction) * startScale + fraction * endScale;
} else {
scale = sourceBounds.width() <= sourceBounds.height()
? (float) destinationBounds.width() / sourceBounds.width()
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index e624de661737..1155ea174ed1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -62,6 +62,7 @@ import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.RemoteException;
import android.os.SystemClock;
+import android.util.Log;
import android.view.Display;
import android.view.Surface;
import android.view.SurfaceControl;
@@ -297,6 +298,10 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
displayController.addDisplayWindowListener(this);
}
+ public PipTransitionController getTransitionController() {
+ return mPipTransitionController;
+ }
+
public Rect getCurrentOrAnimatingBounds() {
PipAnimationController.PipTransitionAnimator animator =
mPipAnimationController.getCurrentAnimator();
@@ -440,7 +445,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// When exit to fullscreen with Shell transition enabled, we update the Task windowing
// mode directly so that it can also trigger display rotation and visibility update in
// the same transition if there will be any.
- wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mToken, getOutPipWindowingMode());
// We can inherit the parent bounds as it is going to be fullscreen. The
// destinationBounds calculated above will be incorrect if this is with rotation.
wct.setBounds(mToken, null);
@@ -458,7 +463,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
// Cancel the existing animator if there is any.
- cancelCurrentAnimator();
+ // TODO(b/232439933): this is disabled temporarily to unblock b/234502692.
+ // cancelCurrentAnimator();
// Set the exiting state first so if there is fixed rotation later, the running animation
// won't be interrupted by alpha animation for existing PiP.
@@ -538,7 +544,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.setBounds(mToken, null);
- wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+ wct.setWindowingMode(mToken, getOutPipWindowingMode());
wct.reorder(mToken, false);
mPipTransitionController.startExitTransition(TRANSIT_REMOVE_PIP, wct,
null /* destinationBounds */);
@@ -925,6 +931,12 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
/** Called when exiting PIP transition is finished to do the state cleanup. */
void onExitPipFinished(TaskInfo info) {
+ if (mLeash == null) {
+ // TODO(239461594): Remove once the double call to onExitPipFinished() is fixed
+ Log.w(TAG, "Warning, onExitPipFinished() called multiple times in the same sessino");
+ return;
+ }
+
clearWaitForFixedRotation();
if (mSwipePipToHomeOverlay != null) {
removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */);
@@ -937,7 +949,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
// Re-set the PIP bounds to none.
mPipBoundsState.setBounds(new Rect());
mPipUiEventLoggerLogger.setTaskInfo(null);
- mPipMenuController.detach();
+ mMainExecutor.executeDelayed(() -> mPipMenuController.detach(), 0);
+ mLeash = null;
if (info.displayId != Display.DEFAULT_DISPLAY && mOnDisplayIdChangeCallback != null) {
mOnDisplayIdChangeCallback.accept(Display.DEFAULT_DISPLAY);
@@ -1467,6 +1480,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
"%s: Abort animation, invalid leash", TAG);
return null;
}
+ if (isInPipDirection(direction)
+ && !isSourceRectHintValidForEnterPip(sourceHintRect, destinationBounds)) {
+ // The given source rect hint is too small for enter PiP animation, reset it to null.
+ sourceHintRect = null;
+ }
final int rotationDelta = mWaitForFixedRotation
? deltaRotation(mCurrentRotation, mNextRotation)
: Surface.ROTATION_0;
@@ -1541,6 +1559,20 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener,
}
/**
+ * This is a situation in which the source rect hint on at least one axis is smaller
+ * than the destination bounds, which represents a problem because we would have to scale
+ * up that axis to fit the bounds. So instead, just fallback to the non-source hint
+ * animation in this case.
+ *
+ * @return {@code false} if the given source is too small to use for the entering animation.
+ */
+ private boolean isSourceRectHintValidForEnterPip(Rect sourceRectHint, Rect destinationBounds) {
+ return sourceRectHint != null
+ && sourceRectHint.width() > destinationBounds.width()
+ && sourceRectHint.height() > destinationBounds.height();
+ }
+
+ /**
* Sync with {@link SplitScreenController} on destination bounds if PiP is going to
* split screen.
*
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 36e712459863..51be2a534dd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -28,7 +28,6 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
@@ -43,6 +42,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SP
import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
+import android.animation.Animator;
import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
@@ -52,6 +52,7 @@ import android.graphics.Rect;
import android.os.IBinder;
import android.view.Surface;
import android.view.SurfaceControl;
+import android.view.WindowManager;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
@@ -145,6 +146,11 @@ public class PipTransition extends PipTransitionController {
if (destinationBounds != null) {
mExitDestinationBounds.set(destinationBounds);
}
+ final PipAnimationController.PipTransitionAnimator animator =
+ mPipAnimationController.getCurrentAnimator();
+ if (animator != null && animator.isRunning()) {
+ animator.cancel();
+ }
mExitTransition = mTransitions.startTransition(type, out, this);
}
@@ -217,13 +223,20 @@ public class PipTransition extends PipTransitionController {
}
// Entering PIP.
- if (isEnteringPip(info, mCurrentPipTaskToken)) {
- return startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
+ if (isEnteringPip(info)) {
+ startEnterAnimation(info, startTransaction, finishTransaction, finishCallback);
+ return true;
}
// For transition that we don't animate, but contains the PIP leash, we need to update the
// PIP surface, otherwise it will be reset after the transition.
if (currentPipTaskChange != null) {
+ // Set the "end" bounds of pip. The default setup uses the start bounds. Since this is
+ // changing the *finish*Transaction, we need to use the end bounds. This will also
+ // make sure that the fade-in animation (below) uses the end bounds as well.
+ if (!currentPipTaskChange.getEndAbsBounds().isEmpty()) {
+ mPipBoundsState.setBounds(currentPipTaskChange.getEndAbsBounds());
+ }
updatePipForUnhandledTransition(currentPipTaskChange, startTransaction,
finishTransaction);
}
@@ -236,6 +249,13 @@ public class PipTransition extends PipTransitionController {
return false;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ end();
+ }
+
/** Helper to identify whether this handler is currently the one playing an animation */
private boolean isAnimatingLocally() {
return mFinishTransaction != null;
@@ -245,16 +265,9 @@ public class PipTransition extends PipTransitionController {
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
- if (request.getType() == TRANSIT_PIP) {
+ if (requestHasPipEnter(request)) {
WindowContainerTransaction wct = new WindowContainerTransaction();
- if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- mRequestedEnterTransition = transition;
- mRequestedEnterTask = request.getTriggerTask().token;
- wct.setActivityWindowingMode(request.getTriggerTask().token,
- WINDOWING_MODE_UNDEFINED);
- final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
- wct.setBounds(request.getTriggerTask().token, destinationBounds);
- }
+ augmentRequest(transition, request, wct);
return wct;
} else {
return null;
@@ -262,6 +275,29 @@ public class PipTransition extends PipTransitionController {
}
@Override
+ public void augmentRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
+ if (!requestHasPipEnter(request)) {
+ throw new IllegalStateException("Called PiP augmentRequest when request has no PiP");
+ }
+ if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ mRequestedEnterTransition = transition;
+ mRequestedEnterTask = request.getTriggerTask().token;
+ outWCT.setActivityWindowingMode(request.getTriggerTask().token,
+ WINDOWING_MODE_UNDEFINED);
+ final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
+ outWCT.setBounds(request.getTriggerTask().token, destinationBounds);
+ }
+ }
+
+ @Override
+ public void end() {
+ Animator animator = mPipAnimationController.getCurrentAnimator();
+ if (animator == null) return;
+ animator.end();
+ }
+
+ @Override
public boolean handleRotateDisplay(int startRotation, int endRotation,
WindowContainerTransaction wct) {
if (mRequestedEnterTransition != null && mOneShotAnimationType == ANIM_TYPE_ALPHA) {
@@ -279,7 +315,7 @@ public class PipTransition extends PipTransitionController {
}
@Override
- public void onTransitionMerged(@NonNull IBinder transition) {
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
if (transition != mExitTransition) {
return;
}
@@ -292,7 +328,7 @@ public class PipTransition extends PipTransitionController {
}
// Unset exitTransition AFTER cancel so that finishResize knows we are merging.
mExitTransition = null;
- if (!cancelled) return;
+ if (!cancelled || aborted) return;
final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
if (taskInfo != null) {
startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
@@ -315,11 +351,27 @@ public class PipTransition extends PipTransitionController {
// (likely a remote like launcher), so don't fire the finish-callback here -- wait until
// the exit transition is merged.
if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) {
- WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareFinishResizeTransaction(taskInfo, destinationBounds,
- direction, wct);
- if (tx != null) {
- wct.setBoundsChangeTransaction(taskInfo.token, tx);
+ WindowContainerTransaction wct = null;
+ if (isOutPipDirection(direction)) {
+ // Only need to reset surface properties. The server-side operations were already
+ // done at the start.
+ if (tx != null) {
+ mFinishTransaction.merge(tx);
+ }
+ } else {
+ wct = new WindowContainerTransaction();
+ if (isInPipDirection(direction)) {
+ // If we are animating from fullscreen using a bounds animation, then reset the
+ // activity windowing mode, and set the task bounds to the final bounds
+ wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
+ wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
+ wct.setBounds(taskInfo.token, destinationBounds);
+ } else {
+ wct.setBounds(taskInfo.token, null /* bounds */);
+ }
+ if (tx != null) {
+ wct.setBoundsChangeTransaction(taskInfo.token, tx);
+ }
}
final SurfaceControl leash = mPipOrganizer.getSurfaceControl();
final int displayRotation = taskInfo.getConfiguration().windowConfiguration
@@ -559,92 +611,94 @@ public class PipTransition extends PipTransitionController {
}
/** Whether we should handle the given {@link TransitionInfo} animation as entering PIP. */
- private static boolean isEnteringPip(@NonNull TransitionInfo info,
- @Nullable WindowContainerToken currentPipTaskToken) {
+ private boolean isEnteringPip(@NonNull TransitionInfo info) {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
- && !change.getContainer().equals(currentPipTaskToken)) {
- // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
- // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
- if (info.getType() == TRANSIT_PIP || info.getType() == TRANSIT_OPEN) {
- return true;
- }
- // This can happen if the request to enter PIP happens when we are collecting for
- // another transition, such as TRANSIT_CHANGE (display rotation).
- if (info.getType() == TRANSIT_CHANGE) {
- return true;
- }
+ if (isEnteringPip(change, info.getType())) return true;
+ }
+ return false;
+ }
- // Please file a bug to handle the unexpected transition type.
- throw new IllegalStateException("Entering PIP with unexpected transition type="
- + transitTypeToString(info.getType()));
+ /** Whether a particular change is a window that is entering pip. */
+ @Override
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ if (change.getTaskInfo() != null
+ && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED
+ && !change.getContainer().equals(mCurrentPipTaskToken)) {
+ // We support TRANSIT_PIP type (from RootWindowContainer) or TRANSIT_OPEN (from apps
+ // that enter PiP instantly on opening, mostly from CTS/Flicker tests)
+ if (transitType == TRANSIT_PIP || transitType == TRANSIT_OPEN) {
+ return true;
+ }
+ // This can happen if the request to enter PIP happens when we are collecting for
+ // another transition, such as TRANSIT_CHANGE (display rotation).
+ if (transitType == TRANSIT_CHANGE) {
+ return true;
}
+
+ // Please file a bug to handle the unexpected transition type.
+ throw new IllegalStateException("Entering PIP with unexpected transition type="
+ + transitTypeToString(transitType));
}
return false;
}
- private boolean startEnterAnimation(@NonNull TransitionInfo info,
+ private void startEnterAnimation(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- // Search for an Enter PiP transition (along with a show wallpaper one)
+ // Search for an Enter PiP transition
TransitionInfo.Change enterPip = null;
- TransitionInfo.Change wallpaper = null;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
if (change.getTaskInfo() != null
&& change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_PINNED) {
enterPip = change;
- } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
- wallpaper = change;
}
}
if (enterPip == null) {
- return false;
+ throw new IllegalStateException("Trying to start PiP animation without a pip"
+ + "participant");
}
- // Keep track of the PIP task.
- mCurrentPipTaskToken = enterPip.getContainer();
- mHasFadeOut = false;
- if (mFinishCallback != null) {
- callFinishCallback(null /* wct */);
- mFinishTransaction = null;
- throw new RuntimeException("Previous callback not called, aborting entering PIP.");
- }
-
- // Show the wallpaper if there is a wallpaper change.
- if (wallpaper != null) {
- startTransaction.show(wallpaper.getLeash());
- startTransaction.setAlpha(wallpaper.getLeash(), 1.f);
- }
// Make sure other open changes are visible as entering PIP. Some may be hidden in
// Transitions#setupStartState because the transition type is OPEN (such as auto-enter).
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
- if (change == enterPip || change == wallpaper) {
- continue;
- }
+ if (change == enterPip) continue;
if (isOpeningType(change.getMode())) {
final SurfaceControl leash = change.getLeash();
startTransaction.show(leash).setAlpha(leash, 1.f);
}
}
+ startEnterAnimation(enterPip, startTransaction, finishTransaction, finishCallback);
+ }
+
+ @Override
+ public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
+ @NonNull final SurfaceControl.Transaction startTransaction,
+ @NonNull final SurfaceControl.Transaction finishTransaction,
+ @NonNull final Transitions.TransitionFinishCallback finishCallback) {
+ if (mFinishCallback != null) {
+ callFinishCallback(null /* wct */);
+ mFinishTransaction = null;
+ throw new RuntimeException("Previous callback not called, aborting entering PIP.");
+ }
+
+ // Keep track of the PIP task and animation.
+ mCurrentPipTaskToken = pipChange.getContainer();
+ mHasFadeOut = false;
mPipTransitionState.setTransitionState(PipTransitionState.ENTERING_PIP);
mFinishCallback = finishCallback;
mFinishTransaction = finishTransaction;
- final int endRotation = mInFixedRotation ? mEndFixedRotation : enterPip.getEndRotation();
- return startEnterAnimation(enterPip.getTaskInfo(), enterPip.getLeash(),
- startTransaction, finishTransaction, enterPip.getStartRotation(),
- endRotation);
- }
- private boolean startEnterAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
- final SurfaceControl.Transaction startTransaction,
- final SurfaceControl.Transaction finishTransaction,
- final int startRotation, final int endRotation) {
+ final ActivityManager.RunningTaskInfo taskInfo = pipChange.getTaskInfo();
+ final SurfaceControl leash = pipChange.getLeash();
+ final int startRotation = pipChange.getStartRotation();
+ final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation();
+
setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams,
taskInfo.topActivityInfo);
final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds();
@@ -657,12 +711,11 @@ public class PipTransition extends PipTransitionController {
computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo,
destinationBounds, sourceHintRect);
}
- PipAnimationController.PipTransitionAnimator animator;
// Set corner radius for entering pip.
mSurfaceTransactionHelper
.crop(finishTransaction, leash, destinationBounds)
.round(finishTransaction, leash, true /* applyCornerRadius */);
- mPipMenuController.attach(leash);
+ mTransitions.getMainExecutor().executeDelayed(() -> mPipMenuController.attach(leash), 0);
if (taskInfo.pictureInPictureParams != null
&& taskInfo.pictureInPictureParams.isAutoEnterEnabled()
@@ -694,7 +747,7 @@ public class PipTransition extends PipTransitionController {
null /* callback */, false /* withStartDelay */);
}
mPipTransitionState.setInSwipePipToHomeTransition(false);
- return true;
+ return;
}
if (rotationDelta != Surface.ROTATION_0) {
@@ -702,6 +755,12 @@ public class PipTransition extends PipTransitionController {
tmpTransform.postRotate(rotationDelta);
startTransaction.setMatrix(leash, tmpTransform, new float[9]);
}
+ if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
+ startTransaction.setAlpha(leash, 0f);
+ }
+ startTransaction.apply();
+
+ PipAnimationController.PipTransitionAnimator animator;
if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
animator = mPipAnimationController.getAnimator(taskInfo, leash, currentBounds,
currentBounds, destinationBounds, sourceHintRect, TRANSITION_DIRECTION_TO_PIP,
@@ -712,7 +771,6 @@ public class PipTransition extends PipTransitionController {
animator.setColorContentOverlay(mContext);
}
} else if (mOneShotAnimationType == ANIM_TYPE_ALPHA) {
- startTransaction.setAlpha(leash, 0f);
animator = mPipAnimationController.getAnimator(taskInfo, leash, destinationBounds,
0f, 1f);
mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -720,7 +778,6 @@ public class PipTransition extends PipTransitionController {
throw new RuntimeException("Unrecognized animation type: "
+ mOneShotAnimationType);
}
- startTransaction.apply();
animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP)
.setPipAnimationCallback(mPipAnimationCallback)
.setDuration(mEnterExitAnimationDuration);
@@ -731,8 +788,6 @@ public class PipTransition extends PipTransitionController {
animator.setDestinationBounds(mPipBoundsAlgorithm.getEntryDestinationBounds());
}
animator.start();
-
- return true;
}
/** Computes destination bounds in old rotation and updates source hint rect if available. */
@@ -852,27 +907,4 @@ public class PipTransition extends PipTransitionController {
mPipMenuController.movePipMenu(null, null, destinationBounds);
mPipMenuController.updateMenuBounds(destinationBounds);
}
-
- private void prepareFinishResizeTransaction(TaskInfo taskInfo, Rect destinationBounds,
- @PipAnimationController.TransitionDirection int direction,
- WindowContainerTransaction wct) {
- Rect taskBounds = null;
- if (isInPipDirection(direction)) {
- // If we are animating from fullscreen using a bounds animation, then reset the
- // activity windowing mode set by WM, and set the task bounds to the final bounds
- taskBounds = destinationBounds;
- wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- wct.scheduleFinishEnterPip(taskInfo.token, destinationBounds);
- } else if (isOutPipDirection(direction)) {
- // If we are animating to fullscreen, then we need to reset the override bounds
- // on the task to ensure that the task "matches" the parent's bounds.
- taskBounds = (direction == TRANSITION_DIRECTION_LEAVE_PIP)
- ? null : destinationBounds;
- wct.setWindowingMode(taskInfo.token, getOutPipWindowingMode());
- // Simply reset the activity mode set prior to the animation running.
- wct.setActivityWindowingMode(taskInfo.token, WINDOWING_MODE_UNDEFINED);
- }
-
- wct.setBounds(taskInfo.token, taskBounds);
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
index 54f46e0c9938..90a2695bdf90 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java
@@ -17,6 +17,7 @@
package com.android.wm.shell.pip;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.WindowManager.TRANSIT_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_REMOVE_STACK;
import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
@@ -27,11 +28,15 @@ import android.app.TaskInfo;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
-import android.os.Handler;
-import android.os.Looper;
+import android.os.IBinder;
import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
import android.window.WindowContainerTransaction;
+import androidx.annotation.NonNull;
+
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.transition.Transitions;
@@ -49,7 +54,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
protected final ShellTaskOrganizer mShellTaskOrganizer;
protected final PipMenuController mPipMenuController;
protected final Transitions mTransitions;
- private final Handler mMainHandler;
private final List<PipTransitionCallback> mPipTransitionCallbacks = new ArrayList<>();
protected PipTaskOrganizer mPipOrganizer;
@@ -137,7 +141,6 @@ public abstract class PipTransitionController implements Transitions.TransitionH
mPipBoundsAlgorithm = pipBoundsAlgorithm;
mPipAnimationController = pipAnimationController;
mTransitions = transitions;
- mMainHandler = new Handler(Looper.getMainLooper());
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
transitions.addHandler(this);
}
@@ -206,6 +209,34 @@ public abstract class PipTransitionController implements Transitions.TransitionH
return false;
}
+ /** @return whether the transition-request represents a pip-entry. */
+ public boolean requestHasPipEnter(@NonNull TransitionRequestInfo request) {
+ return request.getType() == TRANSIT_PIP;
+ }
+
+ /** Whether a particular change is a window that is entering pip. */
+ public boolean isEnteringPip(@NonNull TransitionInfo.Change change,
+ @WindowManager.TransitionType int transitType) {
+ return false;
+ }
+
+ /** Add PiP-related changes to `outWCT` for the given request. */
+ public void augmentRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request, @NonNull WindowContainerTransaction outWCT) {
+ throw new IllegalStateException("Request isn't entering PiP");
+ }
+
+ /** Play a transition animation for entering PiP on a specific PiP change. */
+ public void startEnterAnimation(@NonNull final TransitionInfo.Change pipChange,
+ @NonNull final SurfaceControl.Transaction startTransaction,
+ @NonNull final SurfaceControl.Transaction finishTransaction,
+ @NonNull final Transitions.TransitionFinishCallback finishCallback) {
+ }
+
+ /** End the currently-playing PiP animation. */
+ public void end() {
+ }
+
/**
* Callback interface for PiP transitions (both from and to PiP mode)
*/
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
index 85e56b7dd99f..1a4be3b41911 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionState.java
@@ -17,12 +17,15 @@
package com.android.wm.shell.pip;
import android.annotation.IntDef;
+import android.annotation.NonNull;
import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
/**
* Used to keep track of PiP leash state as it appears and animates by {@link PipTaskOrganizer} and
@@ -37,6 +40,9 @@ public class PipTransitionState {
public static final int ENTERED_PIP = 4;
public static final int EXITING_PIP = 5;
+ private final List<OnPipTransitionStateChangedListener> mOnPipTransitionStateChangedListeners =
+ new ArrayList<>();
+
/**
* If set to {@code true}, no entering PiP transition would be kicked off and most likely
* it's due to the fact that Launcher is handling the transition directly when swiping
@@ -65,7 +71,13 @@ public class PipTransitionState {
}
public void setTransitionState(@TransitionState int state) {
- mState = state;
+ if (mState != state) {
+ for (int i = 0; i < mOnPipTransitionStateChangedListeners.size(); i++) {
+ mOnPipTransitionStateChangedListeners.get(i).onPipTransitionStateChanged(
+ mState, state);
+ }
+ mState = state;
+ }
}
public @TransitionState int getTransitionState() {
@@ -73,8 +85,7 @@ public class PipTransitionState {
}
public boolean isInPip() {
- return mState >= TASK_APPEARED
- && mState != EXITING_PIP;
+ return isInPip(mState);
}
public void setInSwipePipToHomeTransition(boolean inSwipePipToHomeTransition) {
@@ -94,4 +105,23 @@ public class PipTransitionState {
return mState < ENTERING_PIP
|| mState == EXITING_PIP;
}
+
+ public void addOnPipTransitionStateChangedListener(
+ @NonNull OnPipTransitionStateChangedListener listener) {
+ mOnPipTransitionStateChangedListeners.add(listener);
+ }
+
+ public void removeOnPipTransitionStateChangedListener(
+ @NonNull OnPipTransitionStateChangedListener listener) {
+ mOnPipTransitionStateChangedListeners.remove(listener);
+ }
+
+ public static boolean isInPip(@TransitionState int state) {
+ return state >= TASK_APPEARED && state != EXITING_PIP;
+ }
+
+ public interface OnPipTransitionStateChangedListener {
+ void onPipTransitionStateChanged(@TransitionState int oldState,
+ @TransitionState int newState);
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
index dc60bcf742ce..29434f73e84b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipUtils.java
@@ -116,7 +116,7 @@ public class PipUtils {
if (taskId <= 0) return null;
try {
return ActivityTaskManager.getService().getTaskSnapshot(
- taskId, isLowResolution);
+ taskId, isLowResolution, false /* takeSnapshotIfNeeded */);
} catch (RemoteException e) {
Log.e(TAG, "Failed to get task snapshot, taskId=" + taskId, e);
return null;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 4b1e5f8c0d7c..420d6067f420 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -84,8 +84,12 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
import com.android.wm.shell.pip.PipUtils;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -99,7 +103,7 @@ import java.util.function.Consumer;
* Manages the picture-in-picture (PIP) UI and states for Phones.
*/
public class PipController implements PipTransitionController.PipTransitionCallback,
- RemoteCallable<PipController> {
+ RemoteCallable<PipController>, ConfigurationChangeListener, KeyguardChangeListener {
private static final String TAG = "PipController";
private Context mContext;
@@ -110,12 +114,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
private PipAppOpsListener mAppOpsListener;
private PipMediaController mMediaController;
private PipBoundsAlgorithm mPipBoundsAlgorithm;
+ private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
private PipBoundsState mPipBoundsState;
+ private PipMotionHelper mPipMotionHelper;
private PipTouchHandler mTouchHandler;
private PipTransitionController mPipTransitionController;
private TaskStackListenerImpl mTaskStackListener;
private PipParamsChangedForwarder mPipParamsChangedForwarder;
private Optional<OneHandedController> mOneHandedController;
+ private final ShellController mShellController;
protected final PipImpl mImpl;
private final Rect mTmpInsetBounds = new Rect();
@@ -126,11 +133,14 @@ public class PipController implements PipTransitionController.PipTransitionCallb
protected PhonePipMenuController mMenuController;
protected PipTaskOrganizer mPipTaskOrganizer;
+ private PipTransitionState mPipTransitionState;
protected PinnedStackListenerForwarder.PinnedTaskListener mPinnedTaskListener =
new PipControllerPinnedTaskListener();
private boolean mIsKeyguardShowingOrAnimating;
+ private Consumer<Boolean> mOnIsInPipStateChangedListener;
+
private interface PipAnimationListener {
/**
* Notifies the listener that the Pip animation is started.
@@ -156,7 +166,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* Handler for display rotation changes.
*/
private final DisplayChangeController.OnDisplayChangingListener mRotationController = (
- int displayId, int fromRotation, int toRotation, WindowContainerTransaction t) -> {
+ displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> {
if (mPipTransitionController.handleRotateDisplay(fromRotation, toRotation, t)) {
return;
}
@@ -284,11 +294,20 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* Instantiates {@link PipController}, returns {@code null} if the feature not supported.
*/
@Nullable
- public static Pip create(Context context, DisplayController displayController,
- PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm,
- PipBoundsState pipBoundsState, PipMediaController pipMediaController,
- PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer,
- PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController,
+ public static Pip create(Context context,
+ ShellController shellController,
+ DisplayController displayController,
+ PipAppOpsListener pipAppOpsListener,
+ PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
+ PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
+ PipMediaController pipMediaController,
+ PhonePipMenuController phonePipMenuController,
+ PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
+ PipTouchHandler pipTouchHandler,
+ PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
TaskStackListenerImpl taskStackListener,
PipParamsChangedForwarder pipParamsChangedForwarder,
@@ -300,22 +319,27 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return null;
}
- return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm,
- pipBoundsState, pipMediaController,
- phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController,
+ return new PipController(context, shellController, displayController, pipAppOpsListener,
+ pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper,
+ pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTransitionState,
+ pipTouchHandler, pipTransitionController,
windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder,
oneHandedController, mainExecutor)
.mImpl;
}
protected PipController(Context context,
+ ShellController shellController,
DisplayController displayController,
PipAppOpsListener pipAppOpsListener,
PipBoundsAlgorithm pipBoundsAlgorithm,
+ PipKeepClearAlgorithm pipKeepClearAlgorithm,
@NonNull PipBoundsState pipBoundsState,
+ PipMotionHelper pipMotionHelper,
PipMediaController pipMediaController,
PhonePipMenuController phonePipMenuController,
PipTaskOrganizer pipTaskOrganizer,
+ PipTransitionState pipTransitionState,
PipTouchHandler pipTouchHandler,
PipTransitionController pipTransitionController,
WindowManagerShellWrapper windowManagerShellWrapper,
@@ -331,12 +355,16 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
mContext = context;
+ mShellController = shellController;
mImpl = new PipImpl();
mWindowManagerShellWrapper = windowManagerShellWrapper;
mDisplayController = displayController;
mPipBoundsAlgorithm = pipBoundsAlgorithm;
+ mPipKeepClearAlgorithm = pipKeepClearAlgorithm;
mPipBoundsState = pipBoundsState;
+ mPipMotionHelper = pipMotionHelper;
mPipTaskOrganizer = pipTaskOrganizer;
+ mPipTransitionState = pipTransitionState;
mMainExecutor = mainExecutor;
mMediaController = pipMediaController;
mMenuController = phonePipMenuController;
@@ -363,6 +391,15 @@ public class PipController implements PipTransitionController.PipTransitionCallb
onDisplayChanged(mDisplayController.getDisplayLayout(displayId),
false /* saveRestoreSnapFraction */);
});
+ mPipTransitionState.addOnPipTransitionStateChangedListener((oldState, newState) -> {
+ if (mOnIsInPipStateChangedListener != null) {
+ final boolean wasInPip = PipTransitionState.isInPip(oldState);
+ final boolean nowInPip = PipTransitionState.isInPip(newState);
+ if (nowInPip != wasInPip) {
+ mOnIsInPipStateChangedListener.accept(nowInPip);
+ }
+ }
+ });
mPipBoundsState.setOnMinimalSizeChangeCallback(
() -> {
// The minimal size drives the normal bounds, so they need to be recalculated.
@@ -489,6 +526,9 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
});
});
+
+ mShellController.addConfigurationChangeListener(this);
+ mShellController.addKeyguardChangeListener(this);
}
@Override
@@ -501,18 +541,21 @@ public class PipController implements PipTransitionController.PipTransitionCallb
return mMainExecutor;
}
- private void onConfigurationChanged(Configuration newConfig) {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
mPipBoundsAlgorithm.onConfigurationChanged(mContext);
mTouchHandler.onConfigurationChanged();
mPipBoundsState.onConfigurationChanged();
}
- private void onDensityOrFontScaleChanged() {
+ @Override
+ public void onDensityOrFontScaleChanged() {
mPipTaskOrganizer.onDensityOrFontScaleChanged(mContext);
onPipResourceDimensionsChanged();
}
- private void onOverlayChanged() {
+ @Override
+ public void onThemeChanged() {
mTouchHandler.onOverlayChanged();
onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()),
false /* saveRestoreSnapFraction */);
@@ -620,21 +663,24 @@ public class PipController implements PipTransitionController.PipTransitionCallb
* finished first to reset the visibility of PiP window.
* See also {@link #onKeyguardDismissAnimationFinished()}
*/
- private void onKeyguardVisibilityChanged(boolean keyguardShowing, boolean animating) {
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
if (!mPipTaskOrganizer.isInPip()) {
return;
}
- if (keyguardShowing) {
+ if (visible) {
mIsKeyguardShowingOrAnimating = true;
hidePipMenu(null /* onStartCallback */, null /* onEndCallback */);
mPipTaskOrganizer.setPipVisibility(false);
- } else if (!animating) {
+ } else if (!animatingDismiss) {
mIsKeyguardShowingOrAnimating = false;
mPipTaskOrganizer.setPipVisibility(true);
}
}
- private void onKeyguardDismissAnimationFinished() {
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
if (mPipTaskOrganizer.isInPip()) {
mIsKeyguardShowingOrAnimating = false;
mPipTaskOrganizer.setPipVisibility(true);
@@ -657,6 +703,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
}
+ private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mOnIsInPipStateChangedListener = callback;
+ if (mOnIsInPipStateChangedListener != null) {
+ callback.accept(mPipTransitionState.isInPip());
+ }
+ }
+
private void setShelfHeightLocked(boolean visible, int height) {
final int shelfHeight = visible ? height : 0;
mPipBoundsState.setShelfVisibility(visible, shelfHeight);
@@ -892,27 +945,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- mMainExecutor.execute(() -> {
- PipController.this.onConfigurationChanged(newConfig);
- });
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- mMainExecutor.execute(() -> {
- PipController.this.onDensityOrFontScaleChanged();
- });
- }
-
- @Override
- public void onOverlayChanged() {
- mMainExecutor.execute(() -> {
- PipController.this.onOverlayChanged();
- });
- }
-
- @Override
public void onSystemUiStateChanged(boolean isSysUiStateValid, int flag) {
mMainExecutor.execute(() -> {
PipController.this.onSystemUiStateChanged(isSysUiStateValid, flag);
@@ -934,6 +966,13 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
+ public void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) {
+ mMainExecutor.execute(() -> {
+ PipController.this.setOnIsInPipStateChangedListener(callback);
+ });
+ }
+
+ @Override
public void setPinnedStackAnimationType(int animationType) {
mMainExecutor.execute(() -> {
PipController.this.setPinnedStackAnimationType(animationType);
@@ -962,18 +1001,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing, boolean animating) {
- mMainExecutor.execute(() -> {
- PipController.this.onKeyguardVisibilityChanged(showing, animating);
- });
- }
-
- @Override
- public void onKeyguardDismissAnimationFinished() {
- mMainExecutor.execute(PipController.this::onKeyguardDismissAnimationFinished);
- }
-
- @Override
public void dump(PrintWriter pw) {
try {
mMainExecutor.executeBlocking(() -> {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
new file mode 100644
index 000000000000..78084fafe197
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.phone;
+
+import android.graphics.Rect;
+import android.util.ArraySet;
+
+import com.android.wm.shell.pip.PipBoundsAlgorithm;
+import com.android.wm.shell.pip.PipBoundsState;
+
+import java.util.Set;
+
+/**
+ * Calculates the adjusted position that does not occlude keep clear areas.
+ */
+public class PipKeepClearAlgorithm {
+
+ /**
+ * Adjusts the current position of PiP to avoid occluding keep clear areas. If the user has
+ * moved PiP manually, the unmodified current position will be returned instead.
+ */
+ public Rect adjust(PipBoundsState boundsState, PipBoundsAlgorithm boundsAlgorithm) {
+ if (boundsState.hasUserResizedPip()) {
+ return boundsState.getBounds();
+ }
+ return adjust(boundsAlgorithm.getEntryDestinationBounds(),
+ boundsState.getRestrictedKeepClearAreas(),
+ boundsState.getUnrestrictedKeepClearAreas(), boundsState.getDisplayBounds());
+ }
+
+ /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */
+ public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas,
+ Set<Rect> unrestrictedKeepClearAreas, Rect displayBounds) {
+ if (restrictedKeepClearAreas.isEmpty()) {
+ return defaultBounds;
+ }
+ Set<Rect> keepClearAreas = new ArraySet<>();
+ if (!restrictedKeepClearAreas.isEmpty()) {
+ keepClearAreas.addAll(restrictedKeepClearAreas);
+ }
+ Rect outBounds = new Rect(defaultBounds);
+ for (Rect r : keepClearAreas) {
+ if (Rect.intersects(r, outBounds)) {
+ if (tryOffsetUp(outBounds, r, displayBounds)) continue;
+ if (tryOffsetLeft(outBounds, r, displayBounds)) continue;
+ if (tryOffsetDown(outBounds, r, displayBounds)) continue;
+ if (tryOffsetRight(outBounds, r, displayBounds)) continue;
+ }
+ }
+ return outBounds;
+ }
+
+ private boolean tryOffsetLeft(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ rectToAvoid.left - rectToMove.right, 0);
+ }
+
+ private boolean tryOffsetRight(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ rectToAvoid.right - rectToMove.left, 0);
+ }
+
+ private boolean tryOffsetUp(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ 0, rectToAvoid.top - rectToMove.bottom);
+ }
+
+ private boolean tryOffsetDown(Rect rectToMove, Rect rectToAvoid, Rect displayBounds) {
+ return tryOffset(rectToMove, rectToAvoid, displayBounds,
+ 0, rectToAvoid.bottom - rectToMove.top);
+ }
+
+ private boolean tryOffset(Rect rectToMove, Rect rectToAvoid, Rect displayBounds,
+ int dx, int dy) {
+ Rect tmp = new Rect(rectToMove);
+ tmp.offset(dx, dy);
+ if (!Rect.intersects(rectToAvoid, tmp) && displayBounds.contains(tmp)) {
+ rectToMove.offsetTo(tmp.left, tmp.top);
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 6390c8984dac..1958157fc319 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -282,7 +282,7 @@ public class PipMenuView extends FrameLayout {
&& mSplitScreenControllerOptional.get().isTaskInSplitScreen(taskInfo.taskId);
mFocusedTaskAllowSplitScreen = isSplitScreen
|| (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && taskInfo.supportsSplitScreenMultiWindow
+ && taskInfo.supportsMultiWindow
&& taskInfo.topActivityType != WindowConfiguration.ACTIVITY_TYPE_HOME);
}
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 ac7b9033b2b9..a2ff97247189 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
@@ -54,6 +54,7 @@ import com.android.wm.shell.pip.PipAnimationController;
import com.android.wm.shell.pip.PipBoundsAlgorithm;
import com.android.wm.shell.pip.PipBoundsState;
import com.android.wm.shell.pip.PipTaskOrganizer;
+import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.pip.PipUiEventLogger;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
@@ -250,6 +251,10 @@ public class PipTouchHandler {
});
}
+ public PipTransitionController getTransitionHandler() {
+ return mPipTaskOrganizer.getTransitionController();
+ }
+
private void reloadResources() {
final Resources res = mContext.getResources();
mBottomOffsetBufferPx = res.getDimensionPixelSize(R.dimen.pip_bottom_offset_buffer);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
index fa48def9c7d7..a24d9618032d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java
@@ -49,6 +49,8 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -61,7 +63,8 @@ import java.util.Set;
*/
public class TvPipController implements PipTransitionController.PipTransitionCallback,
TvPipBoundsController.PipBoundsListener, TvPipMenuController.Delegate,
- TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener {
+ TvPipNotificationController.Delegate, DisplayController.OnDisplaysChangedListener,
+ ConfigurationChangeListener {
private static final String TAG = "TvPipController";
static final boolean DEBUG = false;
@@ -93,6 +96,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private final Context mContext;
+ private final ShellController mShellController;
private final TvPipBoundsState mTvPipBoundsState;
private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm;
private final TvPipBoundsController mTvPipBoundsController;
@@ -117,6 +121,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
public static Pip create(
Context context,
+ ShellController shellController,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
@@ -133,6 +138,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellExecutor mainExecutor) {
return new TvPipController(
context,
+ shellController,
tvPipBoundsState,
tvPipBoundsAlgorithm,
tvPipBoundsController,
@@ -151,6 +157,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private TvPipController(
Context context,
+ ShellController shellController,
TvPipBoundsState tvPipBoundsState,
TvPipBoundsAlgorithm tvPipBoundsAlgorithm,
TvPipBoundsController tvPipBoundsController,
@@ -167,6 +174,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
ShellExecutor mainExecutor) {
mContext = context;
mMainExecutor = mainExecutor;
+ mShellController = shellController;
mTvPipBoundsState = tvPipBoundsState;
mTvPipBoundsState.setDisplayId(context.getDisplayId());
@@ -193,9 +201,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
registerTaskStackListenerCallback(taskStackListener);
registerWmShellPinnedStackListener(wmShell);
displayController.addDisplayWindowListener(this);
+
+ mShellController.addConfigurationChangeListener(this);
}
- private void onConfigurationChanged(Configuration newConfig) {
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
if (DEBUG) {
ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
"%s: onConfigurationChanged(), state=%s", TAG, stateToName(mState));
@@ -669,13 +680,6 @@ public class TvPipController implements PipTransitionController.PipTransitionCal
private class TvPipImpl implements Pip {
@Override
- public void onConfigurationChanged(Configuration newConfig) {
- mMainExecutor.execute(() -> {
- TvPipController.this.onConfigurationChanged(newConfig);
- });
- }
-
- @Override
public void registerSessionListenerForCurrentUser() {
mMainExecutor.execute(() -> {
TvPipController.this.registerSessionListenerForCurrentUser();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
index d04c34916256..b2961518b66a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java
@@ -26,6 +26,8 @@ import com.android.internal.protolog.common.IProtoLogGroup;
public enum ShellProtoLogGroup implements IProtoLogGroup {
// NOTE: Since we enable these from the same WM ShellCommand, these names should not conflict
// with those in the framework ProtoLogGroup
+ WM_SHELL_INIT(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
+ Consts.TAG_WM_SHELL),
WM_SHELL_TASK_ORG(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
WM_SHELL_TRANSITIONS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true,
@@ -38,10 +40,12 @@ public enum ShellProtoLogGroup implements IProtoLogGroup {
"ShellBackPreview"),
WM_SHELL_RECENT_TASKS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
- WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG,
- false, Consts.TAG_WM_SHELL),
+ WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
Consts.TAG_WM_SHELL),
+ WM_SHELL_SYSUI_EVENTS(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false,
+ Consts.TAG_WM_SHELL),
TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest");
private final boolean mEnabled;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index c166178e9bbd..0f7a4daf6d08 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -44,7 +44,7 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -69,12 +69,12 @@ public class RecentTasksController implements TaskStackListenerCallback,
// pair, then mSplitTasks[t1] = t2, and mSplitTasks[t2] = t1)
private final SparseIntArray mSplitTasks = new SparseIntArray();
/**
- * Maps taskId to {@link StagedSplitBounds} for both taskIDs.
+ * Maps taskId to {@link SplitBounds} for both taskIDs.
* Meaning there will be two taskId integers mapping to the same object.
* If there's any ordering to the pairing than we can probably just get away with only one
* taskID mapping to it, leaving both for consistency with {@link #mSplitTasks} for now.
*/
- private final Map<Integer, StagedSplitBounds> mTaskSplitBoundsMap = new HashMap<>();
+ private final Map<Integer, SplitBounds> mTaskSplitBoundsMap = new HashMap<>();
/**
* Creates {@link RecentTasksController}, returns {@code null} if the feature is not
@@ -110,7 +110,7 @@ public class RecentTasksController implements TaskStackListenerCallback,
/**
* Adds a split pair. This call does not validate the taskIds, only that they are not the same.
*/
- public void addSplitPair(int taskId1, int taskId2, StagedSplitBounds splitBounds) {
+ public void addSplitPair(int taskId1, int taskId2, SplitBounds splitBounds) {
if (taskId1 == taskId2) {
return;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
index ae5e075c4d3f..b0080b24c609 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/MainStage.java
@@ -16,7 +16,9 @@
package com.android.wm.shell.splitscreen;
-import android.annotation.Nullable;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+
import android.content.Context;
import android.view.SurfaceSession;
import android.window.WindowContainerToken;
@@ -38,10 +40,14 @@ class MainStage extends StageTaskListener {
MainStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
- stageTaskUnfoldController);
+ SurfaceSession surfaceSession, IconProvider iconProvider) {
+ super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ iconProvider);
+ }
+
+ @Override
+ void dismiss(WindowContainerTransaction wct, boolean toTop) {
+ deactivate(wct, toTop);
}
boolean isActive() {
@@ -76,10 +82,10 @@ class MainStage extends StageTaskListener {
if (mRootTaskInfo == null) return;
final WindowContainerToken rootToken = mRootTaskInfo.token;
wct.reparentTasks(
- rootToken,
- null /* newParent */,
- CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
- CONTROLLED_ACTIVITY_TYPES,
- toTop);
+ rootToken,
+ null /* newParent */,
+ null /* windowingModes */,
+ null /* activityTypes */,
+ toTop);
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
index d55619f5e5ed..86efbe0af79c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SideStage.java
@@ -16,7 +16,6 @@
package com.android.wm.shell.splitscreen;
-import android.annotation.Nullable;
import android.app.ActivityManager;
import android.content.Context;
import android.view.SurfaceSession;
@@ -38,10 +37,14 @@ class SideStage extends StageTaskListener {
SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession, iconProvider,
- stageTaskUnfoldController);
+ SurfaceSession surfaceSession, IconProvider iconProvider) {
+ super(context, taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
+ iconProvider);
+ }
+
+ @Override
+ void dismiss(WindowContainerTransaction wct, boolean toTop) {
+ removeAllTasks(wct, toTop);
}
boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
@@ -49,8 +52,8 @@ class SideStage extends StageTaskListener {
wct.reparentTasks(
mRootTaskInfo.token,
null /* newParent */,
- CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
- CONTROLLED_ACTIVITY_TYPES,
+ null /* windowingModes */,
+ null /* activityTypes */,
toTop);
return true;
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 448773ae9ea2..e73b799b7a3d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.graphics.Rect;
import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
@@ -58,6 +59,7 @@ public interface SplitScreen {
interface SplitScreenListener {
default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
+ default void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {}
default void onSplitVisibilityChanged(boolean visible) {}
}
@@ -75,12 +77,6 @@ public interface SplitScreen {
return null;
}
- /**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- void onKeyguardVisibilityChanged(boolean showing);
-
/** Called when device waking up finished. */
void onFinishedWakingUp();
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 31b510c38457..7fb961fc0761 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
@@ -18,6 +18,7 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.ActivityManager.START_TASK_TO_FRONT;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
@@ -58,7 +59,9 @@ import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
+import com.android.internal.protolog.common.ProtoLog;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
import com.android.wm.shell.ShellTaskOrganizer;
@@ -74,8 +77,11 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
+import com.android.wm.shell.sysui.KeyguardChangeListener;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
@@ -83,11 +89,10 @@ import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Arrays;
+import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.Executor;
-import javax.inject.Provider;
-
/**
* Class manages split-screen multitasking mode and implements the main interface
* {@link SplitScreen}.
@@ -96,19 +101,19 @@ import javax.inject.Provider;
*/
// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
public class SplitScreenController implements DragAndDropPolicy.Starter,
- RemoteCallable<SplitScreenController> {
+ RemoteCallable<SplitScreenController>, KeyguardChangeListener {
private static final String TAG = SplitScreenController.class.getSimpleName();
- static final int EXIT_REASON_UNKNOWN = 0;
- static final int EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW = 1;
- static final int EXIT_REASON_APP_FINISHED = 2;
- static final int EXIT_REASON_DEVICE_FOLDED = 3;
- static final int EXIT_REASON_DRAG_DIVIDER = 4;
- static final int EXIT_REASON_RETURN_HOME = 5;
- static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
- static final int EXIT_REASON_SCREEN_LOCKED = 7;
- static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
- static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
+ public static final int EXIT_REASON_UNKNOWN = 0;
+ public static final int EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW = 1;
+ public static final int EXIT_REASON_APP_FINISHED = 2;
+ public static final int EXIT_REASON_DEVICE_FOLDED = 3;
+ public static final int EXIT_REASON_DRAG_DIVIDER = 4;
+ public static final int EXIT_REASON_RETURN_HOME = 5;
+ public static final int EXIT_REASON_ROOT_TASK_VANISHED = 6;
+ public static final int EXIT_REASON_SCREEN_LOCKED = 7;
+ public static final int EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP = 8;
+ public static final int EXIT_REASON_CHILD_TASK_ENTER_PIP = 9;
@IntDef(value = {
EXIT_REASON_UNKNOWN,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW,
@@ -124,6 +129,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
@Retention(RetentionPolicy.SOURCE)
@interface ExitReason{}
+ private final ShellController mShellController;
private final ShellTaskOrganizer mTaskOrganizer;
private final SyncTransactionQueue mSyncQueue;
private final Context mContext;
@@ -138,22 +144,22 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
private final SplitscreenEventLogger mLogger;
private final IconProvider mIconProvider;
private final Optional<RecentTasksController> mRecentTasksOptional;
- private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
private StageCoordinator mStageCoordinator;
// Only used for the legacy recents animation from splitscreen to allow the tasks to be animated
// outside the bounds of the roots by being reparented into a higher level fullscreen container
private SurfaceControl mSplitTasksContainerLayer;
- public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
+ public SplitScreenController(ShellController shellController,
+ ShellTaskOrganizer shellTaskOrganizer,
SyncTransactionQueue syncQueue, Context context,
RootTaskDisplayAreaOrganizer rootTDAOrganizer,
ShellExecutor mainExecutor, DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
Transitions transitions, TransactionPool transactionPool, IconProvider iconProvider,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
+ mShellController = shellController;
mTaskOrganizer = shellTaskOrganizer;
mSyncQueue = syncQueue;
mContext = context;
@@ -164,7 +170,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mDisplayInsetsController = displayInsetsController;
mTransitions = transitions;
mTransactionPool = transactionPool;
- mUnfoldControllerProvider = unfoldControllerProvider;
mLogger = new SplitscreenEventLogger();
mIconProvider = iconProvider;
mRecentTasksOptional = recentTasks;
@@ -185,12 +190,13 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void onOrganizerRegistered() {
+ mShellController.addKeyguardChangeListener(this);
if (mStageCoordinator == null) {
// TODO: Multi-display
mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mIconProvider, mMainExecutor, mRecentTasksOptional, mUnfoldControllerProvider);
+ mIconProvider, mMainExecutor, mRecentTasksOptional);
}
}
@@ -198,6 +204,18 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return mStageCoordinator.isSplitScreenVisible();
}
+ public StageCoordinator getTransitionHandler() {
+ return mStageCoordinator;
+ }
+
+ public ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
+ return mStageCoordinator.getFocusingTaskInfo();
+ }
+
+ public boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ return mStageCoordinator.isValidToEnterSplitScreen(taskInfo);
+ }
+
@Nullable
public ActivityManager.RunningTaskInfo getTaskInfo(@SplitPosition int splitPosition) {
if (!isSplitScreenVisible() || splitPosition == SPLIT_POSITION_UNDEFINED) {
@@ -222,6 +240,14 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
new WindowContainerTransaction());
}
+ /**
+ * Update surfaces of the split screen layout based on the current state
+ * @param transaction to write the updates to
+ */
+ public void updateSplitScreenSurfaces(SurfaceControl.Transaction transaction) {
+ mStageCoordinator.updateSurfaces(transaction);
+ }
+
private boolean moveToStage(int taskId, @StageType int stageType,
@SplitPosition int stagePosition, WindowContainerTransaction wct) {
final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
@@ -263,8 +289,10 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
}
- public void onKeyguardVisibilityChanged(boolean showing) {
- mStageCoordinator.onKeyguardVisibilityChanged(showing);
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mStageCoordinator.onKeyguardVisibilityChanged(visible);
}
public void onFinishedWakingUp() {
@@ -324,31 +352,30 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntent(PendingIntent intent, @Nullable Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
+ if (fillInIntent == null) {
+ fillInIntent = new Intent();
+ }
+ // Flag this as a no-user-action launch to prevent sending user leaving event to the
+ // current top activity since it's going to be put into another side of the split. This
+ // prevents the current top activity from going into pip mode due to user leaving event.
+ fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
+
+ // Flag with MULTIPLE_TASK if this is launching the same activity into both sides of the
+ // split.
+ if (isLaunchingAdjacently(intent.getIntent(), position)) {
+ fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Adding MULTIPLE_TASK");
+ }
+
if (!ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, position, options);
return;
}
- try {
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
-
- // Flag this as a no-user-action launch to prevent sending user leaving event to the
- // current top activity since it's going to be put into another side of the split. This
- // prevents the current top activity from going into pip mode due to user leaving event.
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
-
- intent.send(mContext, 0, fillInIntent, null /* onFinished */, null /* handler */,
- null /* requiredPermission */, options);
- } catch (PendingIntent.CanceledException e) {
- Slog.e(TAG, "Failed to launch task", e);
- }
+ mStageCoordinator.startIntent(intent, fillInIntent, position, options);
}
- private void startIntentLegacy(PendingIntent intent, @Nullable Intent fillInIntent,
+ private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
@SplitPosition int position, @Nullable Bundle options) {
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
mStageCoordinator.prepareEvictChildTasks(position, evictWct);
@@ -360,14 +387,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
if (apps == null || apps.length == 0) {
- final ActivityManager.RunningTaskInfo pairedTaskInfo =
- getTaskInfo(SplitLayout.reversePosition(position));
- final ComponentName pairedActivity =
- pairedTaskInfo != null ? pairedTaskInfo.baseActivity : null;
- final ComponentName intentActivity =
- intent.getIntent() != null ? intent.getIntent().getComponent() : null;
- if (pairedActivity != null && pairedActivity.equals(intentActivity)) {
- // Switch split position if dragging the same activity to another side.
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if ((fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
setSideStagePosition(SplitLayout.reversePosition(
mStageCoordinator.getSideStagePosition()));
}
@@ -377,8 +398,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
return;
}
- mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
- false /* applyResizingOffset */);
for (int i = 0; i < apps.length; ++i) {
if (apps[i].mode == MODE_OPENING) {
t.show(apps[i].leash);
@@ -401,18 +420,39 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
- // Flag this as a no-user-action launch to prevent sending user leaving event to the current
- // top activity since it's going to be put into another side of the split. This prevents the
- // current top activity from going into pip mode due to user leaving event.
- if (fillInIntent == null) {
- fillInIntent = new Intent();
- }
- fillInIntent.addFlags(FLAG_ACTIVITY_NO_USER_ACTION);
-
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
+ /** Returns {@code true} if it's launching the same component on both sides of the split. */
+ @VisibleForTesting
+ boolean isLaunchingAdjacently(@Nullable Intent startIntent,
+ @SplitPosition int position) {
+ if (startIntent == null) {
+ return false;
+ }
+
+ final ComponentName launchingActivity = startIntent.getComponent();
+ if (launchingActivity == null) {
+ return false;
+ }
+
+ if (isSplitScreenVisible()) {
+ final ActivityManager.RunningTaskInfo pairedTaskInfo =
+ getTaskInfo(SplitLayout.reversePosition(position));
+ final ComponentName pairedActivity = pairedTaskInfo != null
+ ? pairedTaskInfo.baseIntent.getComponent() : null;
+ return Objects.equals(launchingActivity, pairedActivity);
+ }
+
+ final ActivityManager.RunningTaskInfo taskInfo = getFocusingTaskInfo();
+ if (taskInfo != null && isValidToEnterSplitScreen(taskInfo)) {
+ return Objects.equals(taskInfo.baseIntent.getComponent(), launchingActivity);
+ }
+
+ return false;
+ }
+
RemoteAnimationTarget[] onGoingToRecentsLegacy(RemoteAnimationTarget[] apps) {
if (isSplitScreenVisible()) {
// Evict child tasks except the top visible one under split root to ensure it could be
@@ -425,7 +465,15 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
RemoteAnimationTarget[] onStartingSplitLegacy(RemoteAnimationTarget[] apps) {
- return reparentSplitTasksForAnimation(apps, false /*splitExpectedToBeVisible*/);
+ try {
+ return reparentSplitTasksForAnimation(apps, false /*splitExpectedToBeVisible*/);
+ } finally {
+ for (RemoteAnimationTarget appTarget : apps) {
+ if (appTarget.leash != null) {
+ appTarget.leash.release();
+ }
+ }
+ }
}
private RemoteAnimationTarget[] reparentSplitTasksForAnimation(RemoteAnimationTarget[] apps,
@@ -535,6 +583,17 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
+ public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {
+ for (int i = 0; i < mExecutors.size(); i++) {
+ final int index = i;
+ mExecutors.valueAt(index).execute(() -> {
+ mExecutors.keyAt(index).onSplitBoundsChanged(rootBounds, mainBounds,
+ sideBounds);
+ });
+ }
+ }
+
+ @Override
public void onSplitVisibilityChanged(boolean visible) {
for (int i = 0; i < mExecutors.size(); i++) {
final int index = i;
@@ -583,13 +642,6 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardVisibilityChanged(showing);
- });
- }
-
- @Override
public void onFinishedWakingUp() {
mMainExecutor.execute(() -> {
SplitScreenController.this.onFinishedWakingUp();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index cd121ed41fdd..83bdf8bfb727 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -21,7 +21,6 @@ import static android.view.WindowManager.TRANSIT_CLOSE;
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.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
@@ -58,19 +57,16 @@ import java.util.ArrayList;
class SplitScreenTransitions {
private static final String TAG = "SplitScreenTransitions";
- /** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
-
private final TransactionPool mTransactionPool;
private final Transitions mTransitions;
private final Runnable mOnFinish;
DismissTransition mPendingDismiss = null;
- IBinder mPendingEnter = null;
- IBinder mPendingRecent = null;
+ TransitSession mPendingEnter = null;
+ TransitSession mPendingRecent = null;
private IBinder mAnimatingTransition = null;
- private OneShotRemoteHandler mPendingRemoteHandler = null;
+ OneShotRemoteHandler mPendingRemoteHandler = null;
private OneShotRemoteHandler mActiveRemoteHandler = null;
private final Transitions.TransitionFinishCallback mRemoteFinishCB = this::onFinish;
@@ -94,7 +90,8 @@ class SplitScreenTransitions {
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
+ @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot,
+ @NonNull WindowContainerToken topRoot) {
mFinishCallback = finishCallback;
mAnimatingTransition = transition;
if (mPendingRemoteHandler != null) {
@@ -104,12 +101,12 @@ class SplitScreenTransitions {
mPendingRemoteHandler = null;
return;
}
- playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
+ playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot, topRoot);
}
private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
- @NonNull WindowContainerToken sideRoot) {
+ @NonNull WindowContainerToken sideRoot, @NonNull WindowContainerToken topRoot) {
mFinishTransaction = mTransactionPool.acquire();
// Play some place-holder fade animations
@@ -140,11 +137,14 @@ class SplitScreenTransitions {
endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
startExampleResizeAnimation(leash, startBounds, endBounds);
}
- if (change.getParent() != null) {
+ boolean isRootOrSplitSideRoot = change.getParent() == null
+ || topRoot.equals(change.getParent());
+ // For enter or exit, we only want to animate the side roots but not the top-root.
+ if (!isRootOrSplitSideRoot || topRoot.equals(change.getContainer())) {
continue;
}
- if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
+ if (isPendingEnter(transition) && (mainRoot.equals(change.getContainer())
|| sideRoot.equals(change.getContainer()))) {
t.setPosition(leash, change.getEndAbsBounds().left, change.getEndAbsBounds().top);
t.setWindowCrop(leash, change.getEndAbsBounds().width(),
@@ -170,12 +170,40 @@ class SplitScreenTransitions {
onFinish(null /* wct */, null /* wctCB */);
}
+ boolean isPendingTransition(IBinder transition) {
+ return isPendingEnter(transition)
+ || isPendingDismiss(transition)
+ || isPendingRecent(transition);
+ }
+
+ boolean isPendingEnter(IBinder transition) {
+ return mPendingEnter != null && mPendingEnter.mTransition == transition;
+ }
+
+ boolean isPendingRecent(IBinder transition) {
+ return mPendingRecent != null && mPendingRecent.mTransition == transition;
+ }
+
+ boolean isPendingDismiss(IBinder transition) {
+ return mPendingDismiss != null && mPendingDismiss.mTransition == transition;
+ }
+
/** Starts a transition to enter split with a remote transition animator. */
- IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
- @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
- @NonNull Transitions.TransitionHandler handler) {
+ IBinder startEnterTransition(
+ @WindowManager.TransitionType int transitType,
+ WindowContainerTransaction wct,
+ @Nullable RemoteTransition remoteTransition,
+ Transitions.TransitionHandler handler,
+ @Nullable TransitionCallback callback) {
final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- mPendingEnter = transition;
+ setEnterTransition(transition, remoteTransition, callback);
+ return transition;
+ }
+
+ /** Sets a transition to enter split. */
+ void setEnterTransition(@NonNull IBinder transition,
+ @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
+ mPendingEnter = new TransitSession(transition, callback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -183,32 +211,35 @@ class SplitScreenTransitions {
mTransitions.getMainExecutor(), remoteTransition);
mPendingRemoteHandler.setTransition(transition);
}
- return transition;
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ + " deduced Enter split screen");
}
/** Starts a transition to dismiss split. */
- IBinder startDismissTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
+ IBinder startDismissTransition(WindowContainerTransaction wct,
Transitions.TransitionHandler handler, @SplitScreen.StageType int dismissTop,
@SplitScreenController.ExitReason int reason) {
final int type = reason == EXIT_REASON_DRAG_DIVIDER
? TRANSIT_SPLIT_DISMISS_SNAP : TRANSIT_SPLIT_DISMISS;
- if (transition == null) {
- transition = mTransitions.startTransition(type, wct, handler);
- }
+ IBinder transition = mTransitions.startTransition(type, wct, handler);
+ setDismissTransition(transition, dismissTop, reason);
+ return transition;
+ }
+
+ /** Sets a transition to dismiss split. */
+ void setDismissTransition(@NonNull IBinder transition, @SplitScreen.StageType int dismissTop,
+ @SplitScreenController.ExitReason int reason) {
mPendingDismiss = new DismissTransition(transition, reason, dismissTop);
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Dismiss due to %s. toTop=%s",
exitReasonToString(reason), stageTypeToString(dismissTop));
- return transition;
}
- IBinder startRecentTransition(@Nullable IBinder transition, WindowContainerTransaction wct,
- Transitions.TransitionHandler handler, @Nullable RemoteTransition remoteTransition) {
- if (transition == null) {
- transition = mTransitions.startTransition(TRANSIT_OPEN, wct, handler);
- }
- mPendingRecent = transition;
+ void setRecentTransition(@NonNull IBinder transition,
+ @Nullable RemoteTransition remoteTransition, @Nullable TransitionCallback callback) {
+ mPendingRecent = new TransitSession(transition, callback);
if (remoteTransition != null) {
// Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
@@ -219,34 +250,90 @@ class SplitScreenTransitions {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
+ " deduced Enter recent panel");
- return transition;
}
void mergeAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
IBinder mergeTarget, Transitions.TransitionFinishCallback finishCallback) {
- if (mergeTarget == mAnimatingTransition && mActiveRemoteHandler != null) {
+ if (mergeTarget != mAnimatingTransition) return;
+
+ if (isPendingEnter(transition) && isPendingRecent(mergeTarget)) {
+ mPendingRecent.mCallback = new TransitionCallback() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Since there's an entering transition merged, recent transition no longer
+ // need to handle entering split screen after the transition finished.
+ }
+ };
+ }
+
+ if (mActiveRemoteHandler != null) {
mActiveRemoteHandler.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
+ } else {
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animator anim = mAnimations.get(i);
+ mTransitions.getAnimExecutor().execute(anim::end);
+ }
+ }
+ }
+
+ boolean end() {
+ // If its remote, there's nothing we can do right now.
+ if (mActiveRemoteHandler != null) return false;
+ for (int i = mAnimations.size() - 1; i >= 0; --i) {
+ final Animator anim = mAnimations.get(i);
+ mTransitions.getAnimExecutor().execute(anim::end);
+ }
+ return true;
+ }
+
+ void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
+ if (isPendingEnter(transition)) {
+ if (!aborted) {
+ // An enter transition got merged, appends the rest operations to finish entering
+ // split screen.
+ // TODO (b/238856352): Passed-in the proper finish transition to merge instead.
+ if (mFinishTransaction == null) {
+ mFinishTransaction = mTransactionPool.acquire();
+ }
+ mStageCoordinator.finishEnterSplitScreen(mFinishTransaction);
+ }
+
+ mPendingEnter.mCallback.onTransitionConsumed(aborted);
+ mPendingEnter = null;
+ mPendingRemoteHandler = null;
+ } else if (isPendingDismiss(transition)) {
+ mPendingDismiss.mCallback.onTransitionConsumed(aborted);
+ mPendingDismiss = null;
+ } else if (isPendingRecent(transition)) {
+ mPendingRecent.mCallback.onTransitionConsumed(aborted);
+ mPendingRecent = null;
+ mPendingRemoteHandler = null;
}
}
void onFinish(WindowContainerTransaction wct, WindowContainerTransactionCallback wctCB) {
if (!mAnimations.isEmpty()) return;
- if (mAnimatingTransition == mPendingEnter) {
+
+ TransitionCallback callback = null;
+ if (isPendingEnter(mAnimatingTransition)) {
+ callback = mPendingEnter.mCallback;
mPendingEnter = null;
}
- if (mPendingDismiss != null && mPendingDismiss.mTransition == mAnimatingTransition) {
+ if (isPendingDismiss(mAnimatingTransition)) {
+ callback = mPendingDismiss.mCallback;
mPendingDismiss = null;
}
- if (mAnimatingTransition == mPendingRecent) {
- // If the clean-up wct is null when finishing recent transition, it indicates it's
- // returning to home and thus no need to reorder tasks.
- final boolean returnToHome = wct == null;
- if (returnToHome) {
- wct = new WindowContainerTransaction();
- }
- mStageCoordinator.onRecentTransitionFinished(returnToHome, wct, mFinishTransaction);
+ if (isPendingRecent(mAnimatingTransition)) {
+ callback = mPendingRecent.mCallback;
mPendingRecent = null;
}
+
+ if (callback != null) {
+ if (wct == null) wct = new WindowContainerTransaction();
+ callback.onTransitionFinished(wct, mFinishTransaction);
+ }
+
mPendingRemoteHandler = null;
mActiveRemoteHandler = null;
mAnimatingTransition = null;
@@ -353,17 +440,34 @@ class SplitScreenTransitions {
|| info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
}
- /** Bundled information of dismiss transition. */
- static class DismissTransition {
- IBinder mTransition;
+ /** Clean-up callbacks for transition. */
+ interface TransitionCallback {
+ /** Calls when the transition got consumed. */
+ default void onTransitionConsumed(boolean aborted) {}
- int mReason;
+ /** Calls when the transition finished. */
+ default void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {}
+ }
+
+ /** Session for a transition and its clean-up callback. */
+ static class TransitSession {
+ final IBinder mTransition;
+ TransitionCallback mCallback;
+
+ TransitSession(IBinder transition, @Nullable TransitionCallback callback) {
+ mTransition = transition;
+ mCallback = callback != null ? callback : new TransitionCallback() {};
+ }
+ }
- @SplitScreen.StageType
- int mDismissTop;
+ /** Bundled information of dismiss transition. */
+ static class DismissTransition extends TransitSession {
+ final int mReason;
+ final @SplitScreen.StageType int mDismissTop;
DismissTransition(IBinder transition, int reason, int dismissTop) {
- this.mTransition = transition;
+ super(transition, null /* callback */);
this.mReason = reason;
this.mDismissTop = dismissTop;
}
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 30f316efb2b3..3c7db33bccf2 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
@@ -18,19 +18,26 @@ package com.android.wm.shell.splitscreen;
import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED;
+import static android.app.ComponentOptions.KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
import static android.view.WindowManager.transitTypeToString;
+import static android.window.TransitionInfo.FLAG_FIRST_CUSTOM;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -47,7 +54,6 @@ import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP;
import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
-import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -84,6 +90,8 @@ import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
import android.view.WindowManager;
+import android.widget.Toast;
+import android.window.DisplayAreaInfo;
import android.window.RemoteTransition;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
@@ -93,7 +101,9 @@ import android.window.WindowContainerTransaction;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayImeController;
@@ -110,15 +120,13 @@ import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
-import javax.inject.Provider;
-
/**
* Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
* {@link SideStage} stages.
@@ -132,20 +140,21 @@ import javax.inject.Provider;
* This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
* {@link #onStageHasChildrenChanged(StageListenerImpl).}
*/
-class StageCoordinator implements SplitLayout.SplitLayoutHandler,
+public class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayController.OnDisplaysChangedListener, Transitions.TransitionHandler,
- ShellTaskOrganizer.TaskListener {
+ ShellTaskOrganizer.TaskListener, ShellTaskOrganizer.FocusListener {
private static final String TAG = StageCoordinator.class.getSimpleName();
+ /** Flag applied to a transition change to identify it as a divider bar for animation. */
+ public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
+
private final SurfaceSession mSurfaceSession = new SurfaceSession();
private final MainStage mMainStage;
private final StageListenerImpl mMainStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mMainUnfoldController;
private final SideStage mSideStage;
private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mSideUnfoldController;
private final DisplayLayout mDisplayLayout;
@SplitPosition
private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
@@ -168,6 +177,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private final ShellExecutor mMainExecutor;
private final Optional<RecentTasksController> mRecentTasks;
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private ActivityManager.RunningTaskInfo mFocusingTaskInfo;
+
/**
* A single-top root task which the split divider attached to.
*/
@@ -181,6 +195,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private boolean mShouldUpdateRecents;
private boolean mExitSplitScreenOnHide;
private boolean mIsDividerRemoteAnimating;
+ private boolean mIsExiting;
private boolean mResizingSplits;
/** The target stage to dismiss to when unlock after folded. */
@@ -200,14 +215,40 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
};
+ private final SplitScreenTransitions.TransitionCallback mRecentTransitionCallback =
+ new SplitScreenTransitions.TransitionCallback() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ // Check if the recent transition is finished by returning to the current split, so we
+ // can restore the divider bar.
+ for (int i = 0; i < finishWct.getHierarchyOps().size(); ++i) {
+ final WindowContainerTransaction.HierarchyOp op =
+ finishWct.getHierarchyOps().get(i);
+ final IBinder container = op.getContainer();
+ if (op.getType() == HIERARCHY_OP_TYPE_REORDER && op.getToTop()
+ && (mMainStage.containsContainer(container)
+ || mSideStage.containsContainer(container))) {
+ setDividerVisibility(true, finishT);
+ return;
+ }
+ }
+
+ // Dismiss the split screen if it's not returning to split.
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, finishWct);
+ setSplitsVisible(false);
+ setDividerVisibility(false, finishT);
+ logExit(EXIT_REASON_UNKNOWN);
+ }
+ };
+
StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
ShellTaskOrganizer taskOrganizer, DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController, Transitions transitions,
TransactionPool transactionPool, SplitscreenEventLogger logger,
IconProvider iconProvider, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -215,8 +256,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger = logger;
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
+
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_FULLSCREEN, this /* listener */);
mMainStage = new MainStage(
@@ -226,8 +266,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mMainStageListener,
mSyncQueue,
mSurfaceSession,
- iconProvider,
- mMainUnfoldController);
+ iconProvider);
mSideStage = new SideStage(
mContext,
mTaskOrganizer,
@@ -235,8 +274,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSideStageListener,
mSyncQueue,
mSurfaceSession,
- iconProvider,
- mSideUnfoldController);
+ iconProvider);
mDisplayController = displayController;
mDisplayImeController = displayImeController;
mDisplayInsetsController = displayInsetsController;
@@ -250,6 +288,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayController.addDisplayWindowListener(this);
mDisplayLayout = new DisplayLayout(displayController.getDisplayLayout(displayId));
transitions.addHandler(this);
+ mTaskOrganizer.addFocusListener(this);
}
@VisibleForTesting
@@ -259,8 +298,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
+ Optional<RecentTasksController> recentTasks) {
mContext = context;
mDisplayId = displayId;
mSyncQueue = syncQueue;
@@ -274,8 +312,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout = splitLayout;
mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
this::onTransitionAnimationComplete, this);
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
mLogger = logger;
mMainExecutor = mainExecutor;
mRecentTasks = recentTasks;
@@ -329,15 +365,23 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction evictWct = new WindowContainerTransaction();
targetStage.evictAllChildren(evictWct);
targetStage.addTask(task, wct);
- if (!evictWct.isEmpty()) {
- wct.merge(evictWct, true /* transfer */);
- }
if (ENABLE_SHELL_TRANSITIONS) {
prepareEnterSplitScreen(wct);
- mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE,
- wct, null, this);
+ mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct,
+ null, this, new SplitScreenTransitions.TransitionCallback() {
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
+ }
+ }
+ });
} else {
+ if (!evictWct.isEmpty()) {
+ wct.merge(evictWct, true /* transfer */);
+ }
mTaskOrganizer.applyTransaction(wct);
}
return true;
@@ -357,6 +401,39 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return result;
}
+ /** Launches an activity into split. */
+ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
+ @Nullable Bundle options) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ final WindowContainerTransaction evictWct = new WindowContainerTransaction();
+ prepareEvictChildTasks(position, evictWct);
+
+ options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */);
+ wct.sendPendingIntent(intent, fillInIntent, options);
+ prepareEnterSplitScreen(wct, null /* taskInfo */, position);
+
+ mSplitTransitions.startEnterTransition(TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, null, this,
+ new SplitScreenTransitions.TransitionCallback() {
+ @Override
+ public void onTransitionConsumed(boolean aborted) {
+ // Switch the split position if launching as MULTIPLE_TASK failed.
+ if (aborted
+ && (fillInIntent.getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0) {
+ setSideStagePositionAnimated(
+ SplitLayout.reversePosition(mSideStagePosition));
+ }
+ }
+
+ @Override
+ public void onTransitionFinished(WindowContainerTransaction finishWct,
+ SurfaceControl.Transaction finishT) {
+ if (!evictWct.isEmpty()) {
+ finishWct.merge(evictWct, true);
+ }
+ }
+ });
+ }
+
/** Starts 2 tasks in one transition. */
void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
@Nullable Bundle sideOptions, @SplitPosition int sidePosition, float splitRatio,
@@ -366,10 +443,15 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition, wct);
+ if (mMainStage.isActive()) {
+ mMainStage.evictAllChildren(wct);
+ mSideStage.evictAllChildren(wct);
+ } else {
+ // Build a request WCT that will launch both apps such that task 0 is on the main stage
+ // while task 1 is on the side stage.
+ mMainStage.activate(wct, false /* reparent */);
+ }
mSplitLayout.setDivideRatio(splitRatio);
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(wct, false /* reparent */);
updateWindowBounds(mSplitLayout, wct);
wct.reorder(mRootTaskInfo.token, true);
@@ -382,7 +464,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.startTask(sideTaskId, sideOptions);
mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
+ TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this, null);
}
/** Starts 2 tasks in one legacy transition. */
@@ -436,7 +518,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
- onRemoteAnimationFinishedOrCancelled(evictWct);
+ onRemoteAnimationFinishedOrCancelled(false /* cancel */, evictWct);
finishedCallback.onAnimationFinished();
}
};
@@ -457,7 +539,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onAnimationCancelled(boolean isKeyguardOccluded) {
- onRemoteAnimationFinishedOrCancelled(evictWct);
+ onRemoteAnimationFinishedOrCancelled(true /* cancel */, evictWct);
try {
adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
} catch (RemoteException e) {
@@ -507,13 +589,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
}
- private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) {
+ private void onRemoteAnimationFinishedOrCancelled(boolean cancel,
+ WindowContainerTransaction evictWct) {
mIsDividerRemoteAnimating = false;
mShouldUpdateRecents = true;
// If any stage has no child after animation finished, it means that split will display
// nothing, such status will happen if task and intent is same app but not support
// multi-instagce, we should exit split and expand that app as full screen.
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
+ if (!cancel && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
mMainExecutor.execute(() ->
exitSplitScreen(mMainStage.getChildCount() == 0
? mSideStage : mMainStage, EXIT_REASON_UNKNOWN));
@@ -603,11 +686,28 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
int getTaskId(@SplitPosition int splitPosition) {
- if (mSideStagePosition == splitPosition) {
- return mSideStage.getTopVisibleChildTaskId();
- } else {
- return mMainStage.getTopVisibleChildTaskId();
+ if (splitPosition == SPLIT_POSITION_UNDEFINED) {
+ return INVALID_TASK_ID;
}
+
+ return mSideStagePosition == splitPosition
+ ? mSideStage.getTopVisibleChildTaskId()
+ : mMainStage.getTopVisibleChildTaskId();
+ }
+
+ void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
+ if (mSideStagePosition == sideStagePosition) return;
+ SurfaceControl.Transaction t = mTransactionPool.acquire();
+ final StageTaskListener topLeftStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+ final StageTaskListener bottomRightStage =
+ mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+ mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
+ () -> {
+ setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition),
+ null /* wct */);
+ mTransactionPool.release(t);
+ });
}
void setSideStagePosition(@SplitPosition int sideStagePosition,
@@ -627,7 +727,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
onLayoutSizeChanged(mSplitLayout);
} else {
updateWindowBounds(mSplitLayout, wct);
- updateUnfoldBounds();
+ sendOnBoundsChanged();
}
}
}
@@ -642,7 +742,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (ENABLE_SHELL_TRANSITIONS) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct);
- mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
+ mSplitTransitions.startDismissTransition(wct, this,
mTopStageAfterFoldDismiss, EXIT_REASON_DEVICE_FOLDED);
} else {
exitSplitScreen(
@@ -675,8 +775,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final int dismissTop = mainStageVisible ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(null /* transition */, wct, this,
- dismissTop, EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
+ mSplitTransitions.startDismissTransition(wct, this, dismissTop,
+ EXIT_REASON_SCREEN_LOCKED_SHOW_ON_TOP);
}
}
}
@@ -712,7 +812,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void applyExitSplitScreen(@Nullable StageTaskListener childrenToTop,
WindowContainerTransaction wct, @ExitReason int exitReason) {
- if (!mMainStage.isActive()) return;
+ if (!mMainStage.isActive() || mIsExiting) return;
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
@@ -724,25 +824,45 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
});
mShouldUpdateRecents = false;
- // When the exit split-screen is caused by one of the task enters auto pip,
- // we want the tasks to be put to bottom instead of top, otherwise it will end up
- // a fullscreen plus a pinned task instead of pinned only at the end of the transition.
- final boolean fromEnteringPip = exitReason == EXIT_REASON_CHILD_TASK_ENTER_PIP;
- mSideStage.removeAllTasks(wct, !fromEnteringPip && mSideStage == childrenToTop);
- mMainStage.deactivate(wct, !fromEnteringPip && mMainStage == childrenToTop);
- wct.reorder(mRootTaskInfo.token, false /* onTop */);
- mTaskOrganizer.applyTransaction(wct);
+ if (childrenToTop == null) {
+ mSideStage.removeAllTasks(wct, false /* toTop */);
+ mMainStage.deactivate(wct, false /* toTop */);
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ onTransitionAnimationComplete();
+ } else {
+ // Expand to top side split as full screen for fading out decor animation and dismiss
+ // another side split(Moving its children to bottom).
+ mIsExiting = true;
+ final StageTaskListener tempFullStage = childrenToTop;
+ final StageTaskListener dismissStage = mMainStage == childrenToTop
+ ? mSideStage : mMainStage;
+ tempFullStage.resetBounds(wct);
+ wct.setSmallestScreenWidthDp(tempFullStage.mRootTaskInfo.token,
+ mRootTaskInfo.configuration.smallestScreenWidthDp);
+ dismissStage.dismiss(wct, false /* toTop */);
+ }
+ mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
- setResizingSplits(false /* resizing */);
t.setWindowCrop(mMainStage.mRootLeash, null)
.setWindowCrop(mSideStage.mRootLeash, null);
+ t.setPosition(mMainStage.mRootLeash, 0, 0)
+ .setPosition(mSideStage.mRootLeash, 0, 0);
setDividerVisibility(false, t);
+
+ // In this case, exit still under progress, fade out the split decor after first WCT
+ // done and do remaining WCT after animation finished.
+ if (childrenToTop != null) {
+ childrenToTop.fadeOutDecor(() -> {
+ WindowContainerTransaction finishedWCT = new WindowContainerTransaction();
+ mIsExiting = false;
+ childrenToTop.dismiss(finishedWCT, true /* toTop */);
+ wct.reorder(mRootTaskInfo.token, false /* toTop */);
+ mTaskOrganizer.applyTransaction(finishedWCT);
+ onTransitionAnimationComplete();
+ });
+ }
});
- // Hide divider and reset its position.
- mSplitLayout.resetDividerPosition();
- mSplitLayout.release();
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
Slog.i(TAG, "applyExitSplitScreen, reason = " + exitReasonToString(exitReason));
// Log the exit
if (childrenToTop != null) {
@@ -812,6 +932,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.init();
setDividerVisibility(true, t);
updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
+ t.show(mRootTaskLeash);
setSplitsVisible(true);
mShouldUpdateRecents = true;
updateRecentTasksSplitPair();
@@ -840,6 +961,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void addActivityOptions(Bundle opts, StageTaskListener stage) {
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
+ // Put BAL flags to avoid activity start aborted.
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED, true);
+ opts.putBoolean(KEY_PENDING_INTENT_BACKGROUND_ACTIVITY_ALLOWED_BY_PERMISSION, true);
}
void updateActivityOptions(Bundle opts, @SplitPosition int position) {
@@ -860,6 +984,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
listener.onSplitVisibilityChanged(isSplitScreenVisible());
+ if (mSplitLayout != null) {
+ listener.onSplitBoundsChanged(mSplitLayout.getRootBounds(), getMainStageBounds(),
+ getSideStageBounds());
+ }
mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
}
@@ -872,6 +1000,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ private void sendOnBoundsChanged() {
+ if (mSplitLayout == null) return;
+ for (int i = mListeners.size() - 1; i >= 0; --i) {
+ mListeners.get(i).onSplitBoundsChanged(mSplitLayout.getRootBounds(),
+ getMainStageBounds(), getSideStageBounds());
+ }
+ }
+
private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
boolean present, boolean visible) {
int stage;
@@ -897,9 +1033,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
- private void onStageChildTaskEnterPip(StageListenerImpl stageListener, int taskId) {
- exitSplitScreen(stageListener == mMainStageListener ? mMainStage : mSideStage,
- EXIT_REASON_CHILD_TASK_ENTER_PIP);
+ private void onStageChildTaskEnterPip() {
+ // When the exit split-screen is caused by one of the task enters auto pip,
+ // we want both tasks to be put to bottom instead of top, otherwise it will end up
+ // a fullscreen plus a pinned task instead of pinned only at the end of the transition.
+ exitSplitScreen(null, EXIT_REASON_CHILD_TASK_ENTER_PIP);
}
private void updateRecentTasksSplitPair() {
@@ -921,7 +1059,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
leftTopTaskId = mainStageTopTaskId;
rightBottomTaskId = sideStageTopTaskId;
}
- StagedSplitBounds splitBounds = new StagedSplitBounds(topLeftBounds, bottomRightBounds,
+ SplitBounds splitBounds = new SplitBounds(topLeftBounds, bottomRightBounds,
leftTopTaskId, rightBottomTaskId);
if (mainStageTopTaskId != INVALID_TASK_ID && sideStageTopTaskId != INVALID_TASK_ID) {
// Update the pair for the top tasks
@@ -935,12 +1073,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final SplitScreen.SplitScreenListener l = mListeners.get(i);
l.onSplitVisibilityChanged(mDividerVisible);
}
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- updateUnfoldBounds();
- }
+ sendOnBoundsChanged();
}
@Override
@@ -961,11 +1094,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
}
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.init();
- mSideUnfoldController.init();
- }
-
onRootTaskAppeared();
}
@@ -979,13 +1107,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRootTaskInfo = taskInfo;
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
- && mMainStage.isActive()) {
- // TODO(b/204925795): With Shell transition, We are handling split bounds rotation at
- // onRotateDisplay. But still need to handle unfold case.
- if (ENABLE_SHELL_TRANSITIONS) {
- updateUnfoldBounds();
- return;
- }
+ && mMainStage.isActive()
+ && !ENABLE_SHELL_TRANSITIONS) {
// Clear the divider remote animating flag as the divider will be re-rendered to apply
// the new rotation config.
mIsDividerRemoteAnimating = false;
@@ -1009,6 +1132,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
mRootTaskInfo = null;
+ mRootTaskLeash = null;
}
@@ -1025,8 +1149,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
wct.reparent(mMainStage.mRootTaskInfo.token, mRootTaskInfo.token, true);
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,
- true /* moveTogether */);
+ wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
mTaskOrganizer.applyTransaction(wct);
}
@@ -1136,12 +1259,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDividerFadeInAnimator.cancel();
return;
}
+ mSplitLayout.getRefDividerBounds(mTempRect1);
transaction.show(dividerLeash);
transaction.setAlpha(dividerLeash, 0);
transaction.setLayer(dividerLeash, Integer.MAX_VALUE);
- transaction.setPosition(dividerLeash,
- mSplitLayout.getRefDividerBounds().left,
- mSplitLayout.getRefDividerBounds().top);
+ transaction.setPosition(dividerLeash, mTempRect1.left, mTempRect1.top);
transaction.apply();
}
@@ -1161,21 +1283,42 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
final boolean hasChildren = stageListener.mHasChildren;
final boolean isSideStage = stageListener == mSideStageListener;
- if (!hasChildren) {
+ if (!hasChildren && !mIsExiting && mMainStage.isActive()) {
if (isSideStage && mMainStageListener.mVisible) {
// Exit to main stage if side stage no longer has children.
- exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ exitSplitScreen(mMainStage, EXIT_REASON_APP_FINISHED);
+ } else {
+ mSplitLayout.flingDividerToDismiss(
+ mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
+ }
} else if (!isSideStage && mSideStageListener.mVisible) {
// Exit to side stage if main stage no longer has children.
- exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
+ if (ENABLE_SHELL_TRANSITIONS) {
+ exitSplitScreen(mSideStage, EXIT_REASON_APP_FINISHED);
+ } else {
+ mSplitLayout.flingDividerToDismiss(
+ mSideStagePosition != SPLIT_POSITION_BOTTOM_OR_RIGHT,
+ EXIT_REASON_APP_FINISHED);
+ }
+ }
+ } else if (isSideStage && hasChildren && !mMainStage.isActive()) {
+ if (mFocusingTaskInfo != null && !isValidToEnterSplitScreen(mFocusingTaskInfo)) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSideStage.removeAllTasks(wct, true);
+ wct.reorder(mRootTaskInfo.token, false /* onTop */);
+ mTaskOrganizer.applyTransaction(wct);
+ Slog.i(TAG, "cancel entering split screen, reason = "
+ + exitReasonToString(EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW));
+ } else {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ mSplitLayout.init();
+ prepareEnterSplitScreen(wct);
+ mSyncQueue.queue(wct);
+ mSyncQueue.runInSync(t ->
+ updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
- } else if (isSideStage && !mMainStage.isActive()) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSplitLayout.init();
- prepareEnterSplitScreen(wct);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t ->
- updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */));
}
if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) {
mShouldUpdateRecents = true;
@@ -1190,27 +1333,43 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
}
+ boolean isValidToEnterSplitScreen(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+ return taskInfo.supportsMultiWindow
+ && ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
+ && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode());
+ }
+
+ ActivityManager.RunningTaskInfo getFocusingTaskInfo() {
+ return mFocusingTaskInfo;
+ }
+
+ @Override
+ public void onFocusTaskChanged(ActivityManager.RunningTaskInfo taskInfo) {
+ mFocusingTaskInfo = taskInfo;
+ }
+
@Override
- public void onSnappedToDismiss(boolean bottomOrRight) {
+ public void onSnappedToDismiss(boolean bottomOrRight, int reason) {
final boolean mainStageToTop =
bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
: mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
if (!ENABLE_SHELL_TRANSITIONS) {
- exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, EXIT_REASON_DRAG_DIVIDER);
+ exitSplitScreen(mainStageToTop ? mMainStage : mSideStage, reason);
return;
}
- setResizingSplits(false /* resizing */);
final int dismissTop = mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(dismissTop, wct);
- mSplitTransitions.startDismissTransition(
- null /* transition */, wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
+ if (mRootTaskInfo != null) {
+ wct.setDoNotPip(mRootTaskInfo.token);
+ }
+ mSplitTransitions.startDismissTransition(wct, this, dismissTop, EXIT_REASON_DRAG_DIVIDER);
}
@Override
public void onDoubleTappedDivider() {
- setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), null /* wct */);
+ setSideStagePositionAnimated(SplitLayout.reversePosition(mSideStagePosition));
mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
getSideStagePosition(), mSideStage.getTopChildTaskUid(),
mSplitLayout.isLandscape());
@@ -1229,10 +1388,11 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onLayoutSizeChanging(SplitLayout layout) {
final SurfaceControl.Transaction t = mTransactionPool.acquire();
t.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
- setResizingSplits(true /* resizing */);
updateSurfaceBounds(layout, t, true /* applyResizingOffset */);
- mMainStage.onResizing(getMainStageBounds(), t);
- mSideStage.onResizing(getSideStageBounds(), t);
+ getMainStageBounds(mTempRect1);
+ getSideStageBounds(mTempRect2);
+ mMainStage.onResizing(mTempRect1, mTempRect2, t);
+ mSideStage.onResizing(mTempRect2, mTempRect1, t);
t.apply();
mTransactionPool.release(t);
}
@@ -1241,10 +1401,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
public void onLayoutSizeChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
- updateUnfoldBounds();
+ sendOnBoundsChanged();
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> {
- setResizingSplits(false /* resizing */);
updateSurfaceBounds(layout, t, false /* applyResizingOffset */);
mMainStage.onResized(t);
mSideStage.onResized(t);
@@ -1252,15 +1411,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
}
- private void updateUnfoldBounds() {
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onLayoutChanged(getMainStageBounds(), getMainStagePosition(),
- isLandscape());
- mSideUnfoldController.onLayoutChanged(getSideStageBounds(), getSideStagePosition(),
- isLandscape());
- }
- }
-
private boolean isLandscape() {
return mSplitLayout.isLandscape();
}
@@ -1288,16 +1438,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
applyResizingOffset);
}
- void setResizingSplits(boolean resizing) {
- if (resizing == mResizingSplits) return;
- try {
- ActivityTaskManager.getService().setSplitScreenResizing(resizing);
- mResizingSplits = resizing;
- } catch (RemoteException e) {
- Slog.w(TAG, "Error calling setSplitScreenResizing", e);
- }
- }
-
@Override
public int getSplitItemPosition(WindowContainerToken token) {
if (token == null) {
@@ -1329,7 +1469,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
if (displayId != DEFAULT_DISPLAY) {
return;
}
- mDisplayController.addDisplayChangingController(this::onRotateDisplay);
+ mDisplayController.addDisplayChangingController(this::onDisplayChange);
}
@Override
@@ -1340,16 +1480,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mDisplayLayout.set(mDisplayController.getDisplayLayout(displayId));
}
- private void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- WindowContainerTransaction wct) {
+ void updateSurfaces(SurfaceControl.Transaction transaction) {
+ updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
+ mSplitLayout.update(transaction);
+ }
+
+ private void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction wct) {
if (!mMainStage.isActive()) return;
- // Only do this when shell transition
- if (!ENABLE_SHELL_TRANSITIONS) return;
mDisplayLayout.rotateTo(mContext.getResources(), toRotation);
mSplitLayout.rotateTo(toRotation, mDisplayLayout.stableInsets());
+ if (newDisplayAreaInfo != null) {
+ mSplitLayout.updateConfiguration(newDisplayAreaInfo.configuration);
+ }
updateWindowBounds(mSplitLayout, wct);
- updateUnfoldBounds();
+ sendOnBoundsChanged();
}
private void onFoldedStateChanged(boolean folded) {
@@ -1373,6 +1519,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
}
+ private void getSideStageBounds(Rect rect) {
+ if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ mSplitLayout.getBounds1(rect);
+ } else {
+ mSplitLayout.getBounds2(rect);
+ }
+ }
+
+ private void getMainStageBounds(Rect rect) {
+ if (mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ mSplitLayout.getBounds2(rect);
+ } else {
+ mSplitLayout.getBounds1(rect);
+ }
+ }
+
/**
* Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
* this task (yet) so this can also be used to identify which stage to put a task into.
@@ -1400,7 +1562,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Nullable TransitionRequestInfo request) {
final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
if (triggerTask == null) {
- if (mMainStage.isActive()) {
+ if (isSplitActive()) {
+ // Check if the display is rotating.
final TransitionRequestInfo.DisplayChange displayChange =
request.getDisplayChange();
if (request.getType() == TRANSIT_CHANGE && displayChange != null
@@ -1427,7 +1590,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks.ifPresent(recentTasks -> recentTasks.removeSplitPair(triggerTask.taskId));
}
- if (mMainStage.isActive()) {
+ if (isSplitActive()) {
// Try to handle everything while in split-screen, so return a WCT even if it's empty.
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
+ "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
@@ -1442,7 +1605,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
int dismissTop = getStageType(stage) == STAGE_TYPE_MAIN ? STAGE_TYPE_SIDE
: STAGE_TYPE_MAIN;
prepareExitSplitScreen(dismissTop, out);
- mSplitTransitions.startDismissTransition(transition, out, this, dismissTop,
+ mSplitTransitions.setDismissTransition(transition, dismissTop,
EXIT_REASON_APP_FINISHED);
}
} else if (isOpening && inFullscreen) {
@@ -1452,13 +1615,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
} else if (activityType == ACTIVITY_TYPE_HOME
|| activityType == ACTIVITY_TYPE_RECENTS) {
// Enter overview panel, so start recent transition.
- mSplitTransitions.startRecentTransition(transition, out, this,
- request.getRemoteTransition());
- } else {
- // Occluded by the other fullscreen task, so dismiss both.
+ mSplitTransitions.setRecentTransition(transition, request.getRemoteTransition(),
+ mRecentTransitionCallback);
+ } else if (mSplitTransitions.mPendingRecent == null) {
+ // If split-task is not controlled by recents animation
+ // and occluded by the other fullscreen task, dismiss both.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, out);
- mSplitTransitions.startDismissTransition(transition, out, this,
- STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
+ mSplitTransitions.setDismissTransition(
+ transition, STAGE_TYPE_UNDEFINED, EXIT_REASON_UNKNOWN);
}
}
} else {
@@ -1466,12 +1630,40 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// One task is appearing into split, prepare to enter split screen.
out = new WindowContainerTransaction();
prepareEnterSplitScreen(out);
- mSplitTransitions.mPendingEnter = transition;
+ mSplitTransitions.setEnterTransition(
+ transition, request.getRemoteTransition(), null /* callback */);
}
}
return out;
}
+ /**
+ * This is used for mixed scenarios. For such scenarios, just make sure to include exiting
+ * split or entering split when appropriate.
+ */
+ public void addEnterOrExitIfNeeded(@Nullable TransitionRequestInfo request,
+ @NonNull WindowContainerTransaction outWCT) {
+ final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
+ if (triggerTask != null && triggerTask.displayId != mDisplayId) {
+ // Skip handling task on the other display.
+ return;
+ }
+ final @WindowManager.TransitionType int type = request.getType();
+ if (isSplitActive() && !isOpeningType(type)
+ && (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0)) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " One of the splits became "
+ + "empty during a mixed transition (one not handled by split),"
+ + " so make sure split-screen state is cleaned-up. "
+ + "mainStageCount=%d sideStageCount=%d", mMainStage.getChildCount(),
+ mSideStage.getChildCount());
+ prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, outWCT);
+ }
+ }
+
+ public boolean isSplitActive() {
+ return mMainStage.isActive();
+ }
+
@Override
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
@@ -1479,17 +1671,14 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitTransitions.mergeAnimation(transition, info, t, mergeTarget, finishCallback);
}
+ /** Jump the current transition animation to the end. */
+ public boolean end() {
+ return mSplitTransitions.end();
+ }
+
@Override
- public void onTransitionMerged(@NonNull IBinder transition) {
- // Once the pending enter transition got merged, make sure to bring divider bar visible and
- // clear the pending transition from cache to prevent mess-up the following state.
- if (transition == mSplitTransitions.mPendingEnter) {
- final SurfaceControl.Transaction t = mTransactionPool.acquire();
- finishEnterSplitScreen(t);
- mSplitTransitions.mPendingEnter = null;
- t.apply();
- mTransactionPool.release(t);
- }
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
+ mSplitTransitions.onTransitionConsumed(transition, aborted);
}
@Override
@@ -1498,10 +1687,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mSplitTransitions.mPendingEnter
- && transition != mSplitTransitions.mPendingRecent
- && (mSplitTransitions.mPendingDismiss == null
- || mSplitTransitions.mPendingDismiss.mTransition != transition)) {
+ if (!mSplitTransitions.isPendingTransition(transition)) {
// Not entering or exiting, so just do some house-keeping and validation.
// If we're not in split-mode, just abort so something else can handle it.
@@ -1548,25 +1734,26 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
boolean shouldAnimate = true;
- if (mSplitTransitions.mPendingEnter == transition) {
+ if (mSplitTransitions.isPendingEnter(transition)) {
shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingRecent == transition) {
+ } else if (mSplitTransitions.isPendingRecent(transition)) {
shouldAnimate = startPendingRecentAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingDismiss != null
- && mSplitTransitions.mPendingDismiss.mTransition == transition) {
+ } else if (mSplitTransitions.isPendingDismiss(transition)) {
shouldAnimate = startPendingDismissAnimation(
mSplitTransitions.mPendingDismiss, info, startTransaction, finishTransaction);
}
if (!shouldAnimate) return false;
mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
- finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
+ finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
+ mRootTaskInfo.token);
return true;
}
- void onTransitionAnimationComplete() {
+ /** Called to clean-up state and do house-keeping after the animation is done. */
+ public void onTransitionAnimationComplete() {
// If still playing, let it finish.
- if (!mMainStage.isActive()) {
+ if (!mMainStage.isActive() && !mIsExiting) {
// Update divider state after animation so that it is still around and positioned
// properly for the animation itself.
mSplitLayout.release();
@@ -1628,8 +1815,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- private boolean startPendingDismissAnimation(
- @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ /** Synchronize split-screen state with transition and make appropriate preparations. */
+ public void prepareDismissAnimation(@StageType int toStage, @ExitReason int dismissReason,
@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
@NonNull SurfaceControl.Transaction finishT) {
// Make some noise if things aren't totally expected. These states shouldn't effect
@@ -1662,7 +1849,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mRecentTasks.ifPresent(recentTasks -> {
// Notify recents if we are exiting in a way that breaks the pair, and disable further
// updates to splits in the recents until we enter split again
- if (shouldBreakPairedTaskInRecents(dismissTransition.mReason) && mShouldUpdateRecents) {
+ if (shouldBreakPairedTaskInRecents(dismissReason) && mShouldUpdateRecents) {
for (TransitionInfo.Change change : info.getChanges()) {
final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
if (taskInfo != null
@@ -1679,30 +1866,37 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Wait until after animation to update divider
// Reset crops so they don't interfere with subsequent launches
- t.setWindowCrop(mMainStage.mRootLeash, null);
- t.setWindowCrop(mSideStage.mRootLeash, null);
+ t.setCrop(mMainStage.mRootLeash, null);
+ t.setCrop(mSideStage.mRootLeash, null);
+ if (toStage == STAGE_TYPE_UNDEFINED) {
+ logExit(dismissReason);
+ } else {
+ logExitToStage(dismissReason, toStage == STAGE_TYPE_MAIN);
+ }
+
+ // Hide divider and dim layer on transition finished.
+ setDividerVisibility(false, finishT);
+ finishT.hide(mMainStage.mDimLayer);
+ finishT.hide(mSideStage.mDimLayer);
+ }
+
+ private boolean startPendingDismissAnimation(
+ @NonNull SplitScreenTransitions.DismissTransition dismissTransition,
+ @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t,
+ @NonNull SurfaceControl.Transaction finishT) {
+ prepareDismissAnimation(dismissTransition.mDismissTop, dismissTransition.mReason, info,
+ t, finishT);
if (dismissTransition.mDismissTop == STAGE_TYPE_UNDEFINED) {
- logExit(dismissTransition.mReason);
// TODO: Have a proper remote for this. Until then, though, reset state and use the
// normal animation stuff (which falls back to the normal launcher remote).
+ t.hide(mSplitLayout.getDividerLeash());
mSplitLayout.release(t);
mSplitTransitions.mPendingDismiss = null;
return false;
- } else {
- logExitToStage(dismissTransition.mReason,
- dismissTransition.mDismissTop == STAGE_TYPE_MAIN);
}
addDividerBarToTransition(info, t, false /* show */);
- // We're dismissing split by moving the other one to fullscreen.
- // Since we don't have any animations for this yet, just use the internal example
- // animations.
-
- // Hide divider and dim layer on transition finished.
- setDividerVisibility(false, finishT);
- finishT.hide(mMainStage.mDimLayer);
- finishT.hide(mSideStage.mDimLayer);
return true;
}
@@ -1712,28 +1906,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
return true;
}
- void onRecentTransitionFinished(boolean returnToHome, WindowContainerTransaction wct,
- SurfaceControl.Transaction finishT) {
- // Exclude the case that the split screen has been dismissed already.
- if (!mMainStage.isActive()) {
- // The latest split dismissing transition might be a no-op transition and thus won't
- // callback startAnimation, update split visibility here to cover this kind of no-op
- // transition case.
- setSplitsVisible(false);
- return;
- }
-
- if (returnToHome) {
- // When returning to home from recent apps, the splitting tasks are already hidden, so
- // append the reset of dismissing operations into the clean-up wct.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- setSplitsVisible(false);
- logExit(EXIT_REASON_RETURN_HOME);
- } else {
- setDividerVisibility(true, finishT);
- }
- }
-
private void addDividerBarToTransition(@NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, boolean show) {
final SurfaceControl leash = mSplitLayout.getDividerLeash();
@@ -1855,8 +2027,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
@Override
- public void onChildTaskEnterPip(int taskId) {
- StageCoordinator.this.onStageChildTaskEnterPip(this, taskId);
+ public void onChildTaskEnterPip() {
+ StageCoordinator.this.onStageChildTaskEnterPip();
}
@Override
@@ -1868,19 +2040,22 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onNoLongerSupportMultiWindow() {
if (mMainStage.isActive()) {
+ final Toast splitUnsupportedToast = Toast.makeText(mContext,
+ R.string.dock_non_resizeble_failed_to_dock_text, Toast.LENGTH_SHORT);
final boolean isMainStage = mMainStageListener == this;
if (!ENABLE_SHELL_TRANSITIONS) {
StageCoordinator.this.exitSplitScreen(isMainStage ? mMainStage : mSideStage,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ splitUnsupportedToast.show();
return;
}
final int stageType = isMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
final WindowContainerTransaction wct = new WindowContainerTransaction();
prepareExitSplitScreen(stageType, wct);
- mSplitTransitions.startDismissTransition(null /* transition */, wct,
- StageCoordinator.this, stageType,
+ mSplitTransitions.startDismissTransition(wct,StageCoordinator.this, stageType,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW);
+ splitUnsupportedToast.show();
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index 949bf5f55808..f6dc68be946a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -17,12 +17,12 @@
package com.android.wm.shell.splitscreen;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import android.annotation.CallSuper;
@@ -31,6 +31,7 @@ import android.app.ActivityManager;
import android.content.Context;
import android.graphics.Point;
import android.graphics.Rect;
+import android.os.IBinder;
import android.util.SparseArray;
import android.view.SurfaceControl;
import android.view.SurfaceSession;
@@ -39,6 +40,7 @@ import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
+import com.android.internal.util.ArrayUtils;
import com.android.launcher3.icons.IconProvider;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.SurfaceUtils;
@@ -47,6 +49,7 @@ import com.android.wm.shell.common.split.SplitDecorManager;
import com.android.wm.shell.splitscreen.SplitScreen.StageType;
import java.io.PrintWriter;
+import java.util.function.Predicate;
/**
* Base class that handle common task org. related for split-screen stages.
@@ -60,12 +63,6 @@ import java.io.PrintWriter;
class StageTaskListener implements ShellTaskOrganizer.TaskListener {
private static final String TAG = StageTaskListener.class.getSimpleName();
- protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
- protected static final int[] CONTROLLED_WINDOWING_MODES =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
- protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
-
/** Callback interface for listening to changes in a split-screen stage. */
public interface StageListenerCallbacks {
void onRootTaskAppeared();
@@ -74,7 +71,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
- void onChildTaskEnterPip(int taskId);
+ void onChildTaskEnterPip();
void onRootTaskVanished();
@@ -95,21 +92,22 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
// TODO(b/204308910): Extracts SplitDecorManager related code to common package.
private SplitDecorManager mSplitDecorManager;
- private final StageTaskUnfoldController mStageTaskUnfoldController;
-
StageTaskListener(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession, IconProvider iconProvider,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
+ SurfaceSession surfaceSession, IconProvider iconProvider) {
mContext = context;
mCallbacks = callbacks;
mSyncQueue = syncQueue;
mSurfaceSession = surfaceSession;
mIconProvider = iconProvider;
- mStageTaskUnfoldController = stageTaskUnfoldController;
taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
}
+ /**
+ * General function for dismiss this stage.
+ */
+ void dismiss(WindowContainerTransaction wct, boolean toTop) {}
+
int getChildCount() {
return mChildrenTaskInfo.size();
}
@@ -119,63 +117,53 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
boolean containsToken(WindowContainerToken token) {
- if (token.equals(mRootTaskInfo.token)) {
- return true;
- }
-
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (token.equals(mChildrenTaskInfo.valueAt(i).token)) {
- return true;
- }
- }
+ return contains(t -> t.token.equals(token));
+ }
- return false;
+ boolean containsContainer(IBinder binder) {
+ return contains(t -> t.token.asBinder() == binder);
}
/**
* Returns the top visible child task's id.
*/
int getTopVisibleChildTaskId() {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
- if (info.isVisible) {
- return info.taskId;
- }
- }
- return INVALID_TASK_ID;
+ final ActivityManager.RunningTaskInfo taskInfo = getChildTaskInfo(t -> t.isVisible);
+ return taskInfo != null ? taskInfo.taskId : INVALID_TASK_ID;
}
/**
* Returns the top activity uid for the top child task.
*/
int getTopChildTaskUid() {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
- if (info.topActivityInfo == null) {
- continue;
- }
- return info.topActivityInfo.applicationInfo.uid;
- }
- return 0;
+ final ActivityManager.RunningTaskInfo taskInfo =
+ getChildTaskInfo(t -> t.topActivityInfo != null);
+ return taskInfo != null ? taskInfo.topActivityInfo.applicationInfo.uid : 0;
}
/** @return {@code true} if this listener contains the currently focused task. */
boolean isFocused() {
- if (mRootTaskInfo == null) {
- return false;
- }
+ return contains(t -> t.isFocused);
+ }
- if (mRootTaskInfo.isFocused) {
+ private boolean contains(Predicate<ActivityManager.RunningTaskInfo> predicate) {
+ if (mRootTaskInfo != null && predicate.test(mRootTaskInfo)) {
return true;
}
+ return getChildTaskInfo(predicate) != null;
+ }
+
+ @Nullable
+ private ActivityManager.RunningTaskInfo getChildTaskInfo(
+ Predicate<ActivityManager.RunningTaskInfo> predicate) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (mChildrenTaskInfo.valueAt(i).isFocused) {
- return true;
+ final ActivityManager.RunningTaskInfo taskInfo = mChildrenTaskInfo.valueAt(i);
+ if (predicate.test(taskInfo)) {
+ return taskInfo;
}
}
-
- return false;
+ return null;
}
@Override
@@ -207,20 +195,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
- }
}
@Override
@CallSuper
public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.supportsMultiWindow) {
- // Leave split screen if the task no longer supports multi window.
- mCallbacks.onNoLongerSupportMultiWindow();
- return;
- }
if (mRootTaskInfo.taskId == taskInfo.taskId) {
// Inflates split decor view only when the root task is visible.
if (mRootTaskInfo.isVisible != taskInfo.isVisible) {
@@ -233,6 +212,15 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
mRootTaskInfo = taskInfo;
} else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
+ if (!taskInfo.supportsMultiWindow
+ || !ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType())
+ || !ArrayUtils.contains(CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
+ taskInfo.getWindowingMode())) {
+ // Leave split screen if the task no longer supports multi window or have
+ // uncontrolled task.
+ mCallbacks.onNoLongerSupportMultiWindow();
+ return;
+ }
mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
taskInfo.isVisible);
@@ -258,6 +246,7 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
if (mRootTaskInfo.taskId == taskId) {
mCallbacks.onRootTaskVanished();
mRootTaskInfo = null;
+ mRootLeash = null;
mSyncQueue.runInSync(t -> {
t.remove(mDimLayer);
mSplitDecorManager.release(t);
@@ -266,22 +255,18 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
mChildrenTaskInfo.remove(taskId);
mChildrenLeashes.remove(taskId);
mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
- if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
- mCallbacks.onChildTaskEnterPip(taskId);
- }
if (ENABLE_SHELL_TRANSITIONS) {
// Status is managed/synchronized by the transition lifecycle.
return;
}
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ mCallbacks.onChildTaskEnterPip();
+ }
sendStatusChanged();
} else {
throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
+ "\n mRootTaskInfo: " + mRootTaskInfo);
}
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskVanished(taskInfo);
- }
}
@Override
@@ -305,9 +290,9 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
- void onResizing(Rect newBounds, SurfaceControl.Transaction t) {
+ void onResizing(Rect newBounds, Rect sideBounds, SurfaceControl.Transaction t) {
if (mSplitDecorManager != null && mRootTaskInfo != null) {
- mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, t);
+ mSplitDecorManager.onResizing(mRootTaskInfo, newBounds, sideBounds, t);
}
}
@@ -317,6 +302,14 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ void fadeOutDecor(Runnable finishedCallback) {
+ if (mSplitDecorManager != null) {
+ mSplitDecorManager.fadeOutDecor(finishedCallback);
+ } else {
+ finishedCallback.run();
+ }
+ }
+
void addTask(ActivityManager.RunningTaskInfo task, WindowContainerTransaction wct) {
// Clear overridden bounds and windowing mode to make sure the child task can inherit
// windowing mode and bounds from split root.
@@ -350,6 +343,11 @@ class StageTaskListener implements ShellTaskOrganizer.TaskListener {
}
}
+ void resetBounds(WindowContainerTransaction wct) {
+ wct.setBounds(mRootTaskInfo.token, null);
+ wct.setAppBounds(mRootTaskInfo.token, null);
+ }
+
void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
@StageType int stage) {
for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
deleted file mode 100644
index 45f6d3c8b154..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreen.aidl
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.os.Bundle;
-import android.os.UserHandle;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.window.RemoteTransition;
-
-import com.android.wm.shell.stagesplit.ISplitScreenListener;
-
-/**
- * Interface that is exposed to remote callers to manipulate the splitscreen feature.
- */
-interface ISplitScreen {
-
- /**
- * Registers a split screen listener.
- */
- oneway void registerSplitScreenListener(in ISplitScreenListener listener) = 1;
-
- /**
- * Unregisters a split screen listener.
- */
- oneway void unregisterSplitScreenListener(in ISplitScreenListener listener) = 2;
-
- /**
- * Hides the side-stage if it is currently visible.
- */
- oneway void setSideStageVisibility(boolean visible) = 3;
-
- /**
- * Removes a task from the side stage.
- */
- oneway void removeFromSideStage(int taskId) = 4;
-
- /**
- * Removes the split-screen stages and leaving indicated task to top. Passing INVALID_TASK_ID
- * to indicate leaving no top task after leaving split-screen.
- */
- oneway void exitSplitScreen(int toTopTaskId) = 5;
-
- /**
- * @param exitSplitScreenOnHide if to exit split-screen if both stages are not visible.
- */
- oneway void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) = 6;
-
- /**
- * Starts a task in a stage.
- */
- oneway void startTask(int taskId, int stage, int position, in Bundle options) = 7;
-
- /**
- * Starts a shortcut in a stage.
- */
- oneway void startShortcut(String packageName, String shortcutId, int stage, int position,
- in Bundle options, in UserHandle user) = 8;
-
- /**
- * Starts an activity in a stage.
- */
- oneway void startIntent(in PendingIntent intent, in Intent fillInIntent, int stage,
- int position, in Bundle options) = 9;
-
- /**
- * Starts tasks simultaneously in one transition.
- */
- oneway void startTasks(int mainTaskId, in Bundle mainOptions, int sideTaskId,
- in Bundle sideOptions, int sidePosition, in RemoteTransition remoteTransition) = 10;
-
- /**
- * Version of startTasks using legacy transition system.
- */
- oneway void startTasksWithLegacyTransition(int mainTaskId, in Bundle mainOptions,
- int sideTaskId, in Bundle sideOptions, int sidePosition,
- in RemoteAnimationAdapter adapter) = 11;
-
- /**
- * Blocking call that notifies and gets additional split-screen targets when entering
- * recents (for example: the dividerBar).
- * @param cancel is true if leaving recents back to split (eg. the gesture was cancelled).
- * @param appTargets apps that will be re-parented to display area
- */
- RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- in RemoteAnimationTarget[] appTargets) = 12;
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
deleted file mode 100644
index 83855be91e04..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/MainStage.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Main stage for split-screen mode. When split-screen is active all standard activity types launch
- * on the main stage, except for task that are explicitly pinned to the {@link SideStage}.
- * @see StageCoordinator
- */
-class MainStage extends StageTaskListener {
- private static final String TAG = MainStage.class.getSimpleName();
-
- private boolean mIsActive = false;
-
- MainStage(ShellTaskOrganizer taskOrganizer, int displayId,
- StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
- stageTaskUnfoldController);
- }
-
- boolean isActive() {
- return mIsActive;
- }
-
- void activate(Rect rootBounds, WindowContainerTransaction wct) {
- if (mIsActive) return;
-
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- .setWindowingMode(rootToken, WINDOWING_MODE_MULTI_WINDOW)
- .setLaunchRoot(
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES)
- .reparentTasks(
- null /* currentParent */,
- rootToken,
- CONTROLLED_WINDOWING_MODES,
- CONTROLLED_ACTIVITY_TYPES,
- true /* onTop */)
- // Moving the root task to top after the child tasks were re-parented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
-
- mIsActive = true;
- }
-
- void deactivate(WindowContainerTransaction wct) {
- deactivate(wct, false /* toTop */);
- }
-
- void deactivate(WindowContainerTransaction wct, boolean toTop) {
- if (!mIsActive) return;
- mIsActive = false;
-
- if (mRootTaskInfo == null) return;
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setLaunchRoot(
- rootToken,
- null,
- null)
- .reparentTasks(
- rootToken,
- null /* newParent */,
- CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
- CONTROLLED_ACTIVITY_TYPES,
- toTop)
- // We want this re-order to the bottom regardless since we are re-parenting
- // all its tasks.
- .reorder(rootToken, false /* onTop */);
- }
-
- void updateConfiguration(int windowingMode, Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds)
- .setWindowingMode(mRootTaskInfo.token, windowingMode);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS
deleted file mode 100644
index 264e88f32bff..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# WM shell sub-modules stagesplit owner
-chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
deleted file mode 100644
index 8fbad52c630f..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineManager.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
-
-import android.annotation.Nullable;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.Binder;
-import android.view.IWindow;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.LayoutInflater;
-import android.view.SurfaceControl;
-import android.view.SurfaceControlViewHost;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.WindowManager;
-import android.view.WindowlessWindowManager;
-import android.widget.FrameLayout;
-
-import com.android.wm.shell.R;
-
-/**
- * Handles drawing outline of the bounds of provided root surface. The outline will be drown with
- * the consideration of display insets like status bar, navigation bar and display cutout.
- */
-class OutlineManager extends WindowlessWindowManager {
- private static final String WINDOW_NAME = "SplitOutlineLayer";
- private final Context mContext;
- private final Rect mRootBounds = new Rect();
- private final Rect mTempRect = new Rect();
- private final Rect mLastOutlineBounds = new Rect();
- private final InsetsState mInsetsState = new InsetsState();
- private final int mExpandedTaskBarHeight;
- private OutlineView mOutlineView;
- private SurfaceControlViewHost mViewHost;
- private SurfaceControl mHostLeash;
- private SurfaceControl mLeash;
-
- OutlineManager(Context context, Configuration configuration) {
- super(configuration, null /* rootSurface */, null /* hostInputToken */);
- mContext = context.createWindowContext(context.getDisplay(), TYPE_APPLICATION_OVERLAY,
- null /* options */);
- mExpandedTaskBarHeight = mContext.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
- }
-
- @Override
- protected void attachToParentSurface(IWindow window, SurfaceControl.Builder b) {
- b.setParent(mHostLeash);
- }
-
- void inflate(SurfaceControl rootLeash, Rect rootBounds) {
- if (mLeash != null || mViewHost != null) return;
-
- mHostLeash = rootLeash;
- mRootBounds.set(rootBounds);
- mViewHost = new SurfaceControlViewHost(mContext, mContext.getDisplay(), this);
-
- final FrameLayout rootLayout = (FrameLayout) LayoutInflater.from(mContext)
- .inflate(R.layout.split_outline, null);
- mOutlineView = rootLayout.findViewById(R.id.split_outline);
-
- final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
- 0 /* width */, 0 /* height */, TYPE_APPLICATION_OVERLAY,
- FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT);
- lp.width = mRootBounds.width();
- lp.height = mRootBounds.height();
- lp.token = new Binder();
- lp.setTitle(WINDOW_NAME);
- lp.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION | PRIVATE_FLAG_TRUSTED_OVERLAY;
- // TODO(b/189839391): Set INPUT_FEATURE_NO_INPUT_CHANNEL after WM supports
- // TRUSTED_OVERLAY for windowless window without input channel.
- mViewHost.setView(rootLayout, lp);
- mLeash = getSurfaceControl(mViewHost.getWindowToken());
-
- drawOutline();
- }
-
- void release() {
- if (mViewHost != null) {
- mViewHost.release();
- mViewHost = null;
- }
- mRootBounds.setEmpty();
- mLastOutlineBounds.setEmpty();
- mOutlineView = null;
- mHostLeash = null;
- mLeash = null;
- }
-
- @Nullable
- SurfaceControl getOutlineLeash() {
- return mLeash;
- }
-
- void setVisibility(boolean visible) {
- if (mOutlineView != null) {
- mOutlineView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
- }
- }
-
- void setRootBounds(Rect rootBounds) {
- if (mViewHost == null || mViewHost.getView() == null) {
- return;
- }
-
- if (!mRootBounds.equals(rootBounds)) {
- WindowManager.LayoutParams lp =
- (WindowManager.LayoutParams) mViewHost.getView().getLayoutParams();
- lp.width = rootBounds.width();
- lp.height = rootBounds.height();
- mViewHost.relayout(lp);
- mRootBounds.set(rootBounds);
- drawOutline();
- }
- }
-
- void onInsetsChanged(InsetsState insetsState) {
- if (!mInsetsState.equals(insetsState)) {
- mInsetsState.set(insetsState);
- drawOutline();
- }
- }
-
- private void computeOutlineBounds(Rect rootBounds, InsetsState insetsState, Rect outBounds) {
- outBounds.set(rootBounds);
- final InsetsSource taskBarInsetsSource =
- insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- // Only insets the divider bar with task bar when it's expanded so that the rounded corners
- // will be drawn against task bar.
- if (taskBarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- outBounds.inset(taskBarInsetsSource.calculateVisibleInsets(outBounds));
- }
-
- // Offset the coordinate from screen based to surface based.
- outBounds.offset(-rootBounds.left, -rootBounds.top);
- }
-
- void drawOutline() {
- if (mOutlineView == null) {
- return;
- }
-
- computeOutlineBounds(mRootBounds, mInsetsState, mTempRect);
- if (mTempRect.equals(mLastOutlineBounds)) {
- return;
- }
-
- ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) mOutlineView.getLayoutParams();
- lp.leftMargin = mTempRect.left;
- lp.topMargin = mTempRect.top;
- lp.width = mTempRect.width();
- lp.height = mTempRect.height();
- mOutlineView.setLayoutParams(lp);
- mLastOutlineBounds.set(mTempRect);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
deleted file mode 100644
index 92b1381fc808..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/OutlineView.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.RoundedCorner.POSITION_BOTTOM_LEFT;
-import static android.view.RoundedCorner.POSITION_BOTTOM_RIGHT;
-import static android.view.RoundedCorner.POSITION_TOP_LEFT;
-import static android.view.RoundedCorner.POSITION_TOP_RIGHT;
-
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.Path;
-import android.util.AttributeSet;
-import android.view.RoundedCorner;
-import android.view.View;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.R;
-
-/** View for drawing split outline. */
-public class OutlineView extends View {
- private final Paint mPaint = new Paint();
- private final Path mPath = new Path();
- private final float[] mRadii = new float[8];
-
- public OutlineView(@NonNull Context context, @Nullable AttributeSet attrs) {
- super(context, attrs);
- mPaint.setStyle(Paint.Style.STROKE);
- mPaint.setStrokeWidth(
- getResources().getDimension(R.dimen.accessibility_focus_highlight_stroke_width));
- mPaint.setColor(getResources().getColor(R.color.system_accent1_100, null));
- }
-
- @Override
- protected void onAttachedToWindow() {
- // TODO(b/200850654): match the screen corners with the actual display decor.
- mRadii[0] = mRadii[1] = getCornerRadius(POSITION_TOP_LEFT);
- mRadii[2] = mRadii[3] = getCornerRadius(POSITION_TOP_RIGHT);
- mRadii[4] = mRadii[5] = getCornerRadius(POSITION_BOTTOM_RIGHT);
- mRadii[6] = mRadii[7] = getCornerRadius(POSITION_BOTTOM_LEFT);
- }
-
- private int getCornerRadius(@RoundedCorner.Position int position) {
- final RoundedCorner roundedCorner = getDisplay().getRoundedCorner(position);
- return roundedCorner == null ? 0 : roundedCorner.getRadius();
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- if (changed) {
- mPath.reset();
- mPath.addRoundRect(0, 0, getWidth(), getHeight(), mRadii, Path.Direction.CW);
- }
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- canvas.drawPath(mPath, mPaint);
- }
-
- @Override
- public boolean hasOverlappingRendering() {
- return false;
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
deleted file mode 100644
index 55c4f3aea19a..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SideStage.java
+++ /dev/null
@@ -1,144 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Rect;
-import android.view.InsetsSourceControl;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-/**
- * Side stage for split-screen mode. Only tasks that are explicitly pinned to this stage show up
- * here. All other task are launch in the {@link MainStage}.
- *
- * @see StageCoordinator
- */
-class SideStage extends StageTaskListener implements
- DisplayInsetsController.OnInsetsChangedListener {
- private static final String TAG = SideStage.class.getSimpleName();
- private final Context mContext;
- private OutlineManager mOutlineManager;
-
- SideStage(Context context, ShellTaskOrganizer taskOrganizer, int displayId,
- StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- super(taskOrganizer, displayId, callbacks, syncQueue, surfaceSession,
- stageTaskUnfoldController);
- mContext = context;
- }
-
- void addTask(ActivityManager.RunningTaskInfo task, Rect rootBounds,
- WindowContainerTransaction wct) {
- final WindowContainerToken rootToken = mRootTaskInfo.token;
- wct.setBounds(rootToken, rootBounds)
- .reparent(task.token, rootToken, true /* onTop*/)
- // Moving the root task to top after the child tasks were reparented , or the root
- // task cannot be visible and focused.
- .reorder(rootToken, true /* onTop */);
- }
-
- boolean removeAllTasks(WindowContainerTransaction wct, boolean toTop) {
- // No matter if the root task is empty or not, moving the root to bottom because it no
- // longer preserves visible child task.
- wct.reorder(mRootTaskInfo.token, false /* onTop */);
- if (mChildrenTaskInfo.size() == 0) return false;
- wct.reparentTasks(
- mRootTaskInfo.token,
- null /* newParent */,
- CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE,
- CONTROLLED_ACTIVITY_TYPES,
- toTop);
- return true;
- }
-
- boolean removeTask(int taskId, WindowContainerToken newParent, WindowContainerTransaction wct) {
- final ActivityManager.RunningTaskInfo task = mChildrenTaskInfo.get(taskId);
- if (task == null) return false;
- wct.reparent(task.token, newParent, false /* onTop */);
- return true;
- }
-
- @Nullable
- public SurfaceControl getOutlineLeash() {
- return mOutlineManager.getOutlineLeash();
- }
-
- @Override
- @CallSuper
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- super.onTaskAppeared(taskInfo, leash);
- if (isRootTask(taskInfo)) {
- mOutlineManager = new OutlineManager(mContext, taskInfo.configuration);
- enableOutline(true);
- }
- }
-
- @Override
- @CallSuper
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- super.onTaskInfoChanged(taskInfo);
- if (isRootTask(taskInfo)) {
- mOutlineManager.setRootBounds(taskInfo.configuration.windowConfiguration.getBounds());
- }
- }
-
- private boolean isRootTask(ActivityManager.RunningTaskInfo taskInfo) {
- return mRootTaskInfo != null && mRootTaskInfo.taskId == taskInfo.taskId;
- }
-
- void enableOutline(boolean enable) {
- if (mOutlineManager == null) {
- return;
- }
-
- if (enable) {
- if (mRootTaskInfo != null) {
- mOutlineManager.inflate(mRootLeash,
- mRootTaskInfo.configuration.windowConfiguration.getBounds());
- }
- } else {
- mOutlineManager.release();
- }
- }
-
- void setOutlineVisibility(boolean visible) {
- mOutlineManager.setVisibility(visible);
- }
-
- @Override
- public void insetsChanged(InsetsState insetsState) {
- mOutlineManager.onInsetsChanged(insetsState);
- }
-
- @Override
- public void insetsControlChanged(InsetsState insetsState,
- InsetsSourceControl[] activeControls) {
- insetsChanged(insetsState);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
deleted file mode 100644
index c5d231262cd2..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreen.java
+++ /dev/null
@@ -1,99 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-
-import java.util.concurrent.Executor;
-
-/**
- * Interface to engage split-screen feature.
- * TODO: Figure out which of these are actually needed outside of the Shell
- */
-@ExternalThread
-public interface SplitScreen {
- /**
- * Stage type isn't specified normally meaning to use what ever the default is.
- * E.g. exit split-screen and launch the app in fullscreen.
- */
- int STAGE_TYPE_UNDEFINED = -1;
- /**
- * The main stage type.
- * @see MainStage
- */
- int STAGE_TYPE_MAIN = 0;
-
- /**
- * The side stage type.
- * @see SideStage
- */
- int STAGE_TYPE_SIDE = 1;
-
- @IntDef(prefix = { "STAGE_TYPE_" }, value = {
- STAGE_TYPE_UNDEFINED,
- STAGE_TYPE_MAIN,
- STAGE_TYPE_SIDE
- })
- @interface StageType {}
-
- /** Callback interface for listening to changes in a split-screen stage. */
- interface SplitScreenListener {
- default void onStagePositionChanged(@StageType int stage, @SplitPosition int position) {}
- default void onTaskStageChanged(int taskId, @StageType int stage, boolean visible) {}
- default void onSplitVisibilityChanged(boolean visible) {}
- }
-
- /** Registers listener that gets split screen callback. */
- void registerSplitScreenListener(@NonNull SplitScreenListener listener,
- @NonNull Executor executor);
-
- /** Unregisters listener that gets split screen callback. */
- void unregisterSplitScreenListener(@NonNull SplitScreenListener listener);
-
- /**
- * Returns a binder that can be passed to an external process to manipulate SplitScreen.
- */
- default ISplitScreen createExternalInterface() {
- return null;
- }
-
- /**
- * Called when the keyguard occluded state changes.
- * @param occluded Indicates if the keyguard is now occluded.
- */
- void onKeyguardOccludedChanged(boolean occluded);
-
- /**
- * Called when the visibility of the keyguard changes.
- * @param showing Indicates if the keyguard is now visible.
- */
- void onKeyguardVisibilityChanged(boolean showing);
-
- /** Get a string representation of a stage type */
- static String stageTypeToString(@StageType int stage) {
- switch (stage) {
- case STAGE_TYPE_UNDEFINED: return "UNDEFINED";
- case STAGE_TYPE_MAIN: return "MAIN";
- case STAGE_TYPE_SIDE: return "SIDE";
- default: return "UNKNOWN(" + stage + ")";
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
deleted file mode 100644
index 07174051a344..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenController.java
+++ /dev/null
@@ -1,595 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.RemoteAnimationTarget.MODE_OPENING;
-
-import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-
-import android.app.ActivityManager;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.content.ActivityNotFoundException;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.UserHandle;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.BinderThread;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.RemoteCallable;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.annotations.ExternalThread;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.draganddrop.DragAndDropPolicy;
-import com.android.wm.shell.transition.LegacyTransitions;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
-/**
- * Class manages split-screen multitasking mode and implements the main interface
- * {@link SplitScreen}.
- * @see StageCoordinator
- */
-// TODO(b/198577848): Implement split screen flicker test to consolidate CUJ of split screen.
-public class SplitScreenController implements DragAndDropPolicy.Starter,
- RemoteCallable<SplitScreenController> {
- private static final String TAG = SplitScreenController.class.getSimpleName();
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final SyncTransactionQueue mSyncQueue;
- private final Context mContext;
- private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
- private final ShellExecutor mMainExecutor;
- private final SplitScreenImpl mImpl = new SplitScreenImpl();
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private final Transitions mTransitions;
- private final TransactionPool mTransactionPool;
- private final SplitscreenEventLogger mLogger;
- private final Provider<Optional<StageTaskUnfoldController>> mUnfoldControllerProvider;
-
- private StageCoordinator mStageCoordinator;
-
- public SplitScreenController(ShellTaskOrganizer shellTaskOrganizer,
- SyncTransactionQueue syncQueue, Context context,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer,
- ShellExecutor mainExecutor, DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController,
- Transitions transitions, TransactionPool transactionPool,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- mTaskOrganizer = shellTaskOrganizer;
- mSyncQueue = syncQueue;
- mContext = context;
- mRootTDAOrganizer = rootTDAOrganizer;
- mMainExecutor = mainExecutor;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mTransitions = transitions;
- mTransactionPool = transactionPool;
- mUnfoldControllerProvider = unfoldControllerProvider;
- mLogger = new SplitscreenEventLogger();
- }
-
- public SplitScreen asSplitScreen() {
- return mImpl;
- }
-
- @Override
- public Context getContext() {
- return mContext;
- }
-
- @Override
- public ShellExecutor getRemoteCallExecutor() {
- return mMainExecutor;
- }
-
- public void onOrganizerRegistered() {
- if (mStageCoordinator == null) {
- // TODO: Multi-display
- mStageCoordinator = new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
- mRootTDAOrganizer, mTaskOrganizer, mDisplayImeController,
- mDisplayInsetsController, mTransitions, mTransactionPool, mLogger,
- mUnfoldControllerProvider);
- }
- }
-
- public boolean isSplitScreenVisible() {
- return mStageCoordinator.isSplitScreenVisible();
- }
-
- public boolean moveToSideStage(int taskId, @SplitPosition int sideStagePosition) {
- final ActivityManager.RunningTaskInfo task = mTaskOrganizer.getRunningTaskInfo(taskId);
- if (task == null) {
- throw new IllegalArgumentException("Unknown taskId" + taskId);
- }
- return moveToSideStage(task, sideStagePosition);
- }
-
- public boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SplitPosition int sideStagePosition) {
- return mStageCoordinator.moveToSideStage(task, sideStagePosition);
- }
-
- public boolean removeFromSideStage(int taskId) {
- return mStageCoordinator.removeFromSideStage(taskId);
- }
-
- public void setSideStageOutline(boolean enable) {
- mStageCoordinator.setSideStageOutline(enable);
- }
-
- public void setSideStagePosition(@SplitPosition int sideStagePosition) {
- mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
- }
-
- public void setSideStageVisibility(boolean visible) {
- mStageCoordinator.setSideStageVisibility(visible);
- }
-
- public void enterSplitScreen(int taskId, boolean leftOrTop) {
- moveToSideStage(taskId,
- leftOrTop ? SPLIT_POSITION_TOP_OR_LEFT : SPLIT_POSITION_BOTTOM_OR_RIGHT);
- }
-
- public void exitSplitScreen(int toTopTaskId, int exitReason) {
- mStageCoordinator.exitSplitScreen(toTopTaskId, exitReason);
- }
-
- public void onKeyguardOccludedChanged(boolean occluded) {
- mStageCoordinator.onKeyguardOccludedChanged(occluded);
- }
-
- public void onKeyguardVisibilityChanged(boolean showing) {
- mStageCoordinator.onKeyguardVisibilityChanged(showing);
- }
-
- public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- mStageCoordinator.exitSplitScreenOnHide(exitSplitScreenOnHide);
- }
-
- public void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- mStageCoordinator.getStageBounds(outTopOrLeftBounds, outBottomOrRightBounds);
- }
-
- public void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- mStageCoordinator.registerSplitScreenListener(listener);
- }
-
- public void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- mStageCoordinator.unregisterSplitScreenListener(listener);
- }
-
- public void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) {
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
-
- try {
- ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to launch task", e);
- }
- }
-
- public void startShortcut(String packageName, String shortcutId, @SplitPosition int position,
- @Nullable Bundle options, UserHandle user) {
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options,
- null /* wct */);
-
- try {
- LauncherApps launcherApps =
- mContext.getSystemService(LauncherApps.class);
- launcherApps.startShortcut(packageName, shortcutId, null /* sourceBounds */,
- options, user);
- } catch (ActivityNotFoundException e) {
- Slog.e(TAG, "Failed to launch shortcut", e);
- }
- }
-
- public void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position,
- @Nullable Bundle options) {
- if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
- startIntentLegacy(intent, fillInIntent, position, options);
- return;
- }
- mStageCoordinator.startIntent(intent, fillInIntent, STAGE_TYPE_UNDEFINED, position, options,
- null /* remote */);
- }
-
- private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
- @SplitPosition int position, @Nullable Bundle options) {
- LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
- @Override
- public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
- IRemoteAnimationFinishedCallback finishedCallback,
- SurfaceControl.Transaction t) {
- mStageCoordinator.updateSurfaceBounds(null /* layout */, t,
- false /* applyResizingOffset */);
-
- if (apps != null) {
- for (int i = 0; i < apps.length; ++i) {
- if (apps[i].mode == MODE_OPENING) {
- t.show(apps[i].leash);
- }
- }
- }
-
- t.apply();
- if (finishedCallback != null) {
- try {
- finishedCallback.onAnimationFinished();
- } catch (RemoteException e) {
- Slog.e(TAG, "Error finishing legacy transition: ", e);
- }
- }
- }
- };
- WindowContainerTransaction wct = new WindowContainerTransaction();
- options = mStageCoordinator.resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, wct);
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
- }
-
- RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel, RemoteAnimationTarget[] apps) {
- if (!isSplitScreenVisible()) return null;
- final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
- .setContainerLayer()
- .setName("RecentsAnimationSplitTasks")
- .setHidden(false)
- .setCallsite("SplitScreenController#onGoingtoRecentsLegacy");
- mRootTDAOrganizer.attachToDisplayArea(DEFAULT_DISPLAY, builder);
- SurfaceControl sc = builder.build();
- SurfaceControl.Transaction transaction = new SurfaceControl.Transaction();
-
- // Ensure that we order these in the parent in the right z-order as their previous order
- Arrays.sort(apps, (a1, a2) -> a1.prefixOrderIndex - a2.prefixOrderIndex);
- int layer = 1;
- for (RemoteAnimationTarget appTarget : apps) {
- transaction.reparent(appTarget.leash, sc);
- transaction.setPosition(appTarget.leash, appTarget.screenSpaceBounds.left,
- appTarget.screenSpaceBounds.top);
- transaction.setLayer(appTarget.leash, layer++);
- }
- transaction.apply();
- transaction.close();
- return new RemoteAnimationTarget[]{
- mStageCoordinator.getDividerBarLegacyTarget(),
- mStageCoordinator.getOutlineLegacyTarget()};
- }
-
- /**
- * Sets drag info to be logged when splitscreen is entered.
- */
- public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
- mStageCoordinator.logOnDroppedToSplit(position, dragSessionId);
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + TAG);
- if (mStageCoordinator != null) {
- mStageCoordinator.dump(pw, prefix);
- }
- }
-
- /**
- * The interface for calls from outside the Shell, within the host process.
- */
- @ExternalThread
- private class SplitScreenImpl implements SplitScreen {
- private ISplitScreenImpl mISplitScreen;
- private final ArrayMap<SplitScreenListener, Executor> mExecutors = new ArrayMap<>();
- private final SplitScreenListener mListener = new SplitScreenListener() {
- @Override
- public void onStagePositionChanged(int stage, int position) {
- for (int i = 0; i < mExecutors.size(); i++) {
- final int index = i;
- mExecutors.valueAt(index).execute(() -> {
- mExecutors.keyAt(index).onStagePositionChanged(stage, position);
- });
- }
- }
-
- @Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
- for (int i = 0; i < mExecutors.size(); i++) {
- final int index = i;
- mExecutors.valueAt(index).execute(() -> {
- mExecutors.keyAt(index).onTaskStageChanged(taskId, stage, visible);
- });
- }
- }
-
- @Override
- public void onSplitVisibilityChanged(boolean visible) {
- for (int i = 0; i < mExecutors.size(); i++) {
- final int index = i;
- mExecutors.valueAt(index).execute(() -> {
- mExecutors.keyAt(index).onSplitVisibilityChanged(visible);
- });
- }
- }
- };
-
- @Override
- public ISplitScreen createExternalInterface() {
- if (mISplitScreen != null) {
- mISplitScreen.invalidate();
- }
- mISplitScreen = new ISplitScreenImpl(SplitScreenController.this);
- return mISplitScreen;
- }
-
- @Override
- public void onKeyguardOccludedChanged(boolean occluded) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardOccludedChanged(occluded);
- });
- }
-
- @Override
- public void registerSplitScreenListener(SplitScreenListener listener, Executor executor) {
- if (mExecutors.containsKey(listener)) return;
-
- mMainExecutor.execute(() -> {
- if (mExecutors.size() == 0) {
- SplitScreenController.this.registerSplitScreenListener(mListener);
- }
-
- mExecutors.put(listener, executor);
- });
-
- executor.execute(() -> {
- mStageCoordinator.sendStatusToListener(listener);
- });
- }
-
- @Override
- public void unregisterSplitScreenListener(SplitScreenListener listener) {
- mMainExecutor.execute(() -> {
- mExecutors.remove(listener);
-
- if (mExecutors.size() == 0) {
- SplitScreenController.this.unregisterSplitScreenListener(mListener);
- }
- });
- }
-
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- mMainExecutor.execute(() -> {
- SplitScreenController.this.onKeyguardVisibilityChanged(showing);
- });
- }
- }
-
- /**
- * The interface for calls from outside the host process.
- */
- @BinderThread
- private static class ISplitScreenImpl extends ISplitScreen.Stub {
- private SplitScreenController mController;
- private ISplitScreenListener mListener;
- private final SplitScreen.SplitScreenListener mSplitScreenListener =
- new SplitScreen.SplitScreenListener() {
- @Override
- public void onStagePositionChanged(int stage, int position) {
- try {
- if (mListener != null) {
- mListener.onStagePositionChanged(stage, position);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "onStagePositionChanged", e);
- }
- }
-
- @Override
- public void onTaskStageChanged(int taskId, int stage, boolean visible) {
- try {
- if (mListener != null) {
- mListener.onTaskStageChanged(taskId, stage, visible);
- }
- } catch (RemoteException e) {
- Slog.e(TAG, "onTaskStageChanged", e);
- }
- }
- };
- private final IBinder.DeathRecipient mListenerDeathRecipient =
- new IBinder.DeathRecipient() {
- @Override
- @BinderThread
- public void binderDied() {
- final SplitScreenController controller = mController;
- controller.getRemoteCallExecutor().execute(() -> {
- mListener = null;
- controller.unregisterSplitScreenListener(mSplitScreenListener);
- });
- }
- };
-
- public ISplitScreenImpl(SplitScreenController controller) {
- mController = controller;
- }
-
- /**
- * Invalidates this instance, preventing future calls from updating the controller.
- */
- void invalidate() {
- mController = null;
- }
-
- @Override
- public void registerSplitScreenListener(ISplitScreenListener listener) {
- executeRemoteCallWithTaskPermission(mController, "registerSplitScreenListener",
- (controller) -> {
- if (mListener != null) {
- mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
- 0 /* flags */);
- }
- if (listener != null) {
- try {
- listener.asBinder().linkToDeath(mListenerDeathRecipient,
- 0 /* flags */);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to link to death");
- return;
- }
- }
- mListener = listener;
- controller.registerSplitScreenListener(mSplitScreenListener);
- });
- }
-
- @Override
- public void unregisterSplitScreenListener(ISplitScreenListener listener) {
- executeRemoteCallWithTaskPermission(mController, "unregisterSplitScreenListener",
- (controller) -> {
- if (mListener != null) {
- mListener.asBinder().unlinkToDeath(mListenerDeathRecipient,
- 0 /* flags */);
- }
- mListener = null;
- controller.unregisterSplitScreenListener(mSplitScreenListener);
- });
- }
-
- @Override
- public void exitSplitScreen(int toTopTaskId) {
- executeRemoteCallWithTaskPermission(mController, "exitSplitScreen",
- (controller) -> {
- controller.exitSplitScreen(toTopTaskId,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT);
- });
- }
-
- @Override
- public void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- executeRemoteCallWithTaskPermission(mController, "exitSplitScreenOnHide",
- (controller) -> {
- controller.exitSplitScreenOnHide(exitSplitScreenOnHide);
- });
- }
-
- @Override
- public void setSideStageVisibility(boolean visible) {
- executeRemoteCallWithTaskPermission(mController, "setSideStageVisibility",
- (controller) -> {
- controller.setSideStageVisibility(visible);
- });
- }
-
- @Override
- public void removeFromSideStage(int taskId) {
- executeRemoteCallWithTaskPermission(mController, "removeFromSideStage",
- (controller) -> {
- controller.removeFromSideStage(taskId);
- });
- }
-
- @Override
- public void startTask(int taskId, int stage, int position, @Nullable Bundle options) {
- executeRemoteCallWithTaskPermission(mController, "startTask",
- (controller) -> {
- controller.startTask(taskId, position, options);
- });
- }
-
- @Override
- public void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- RemoteAnimationAdapter adapter) {
- executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasksWithLegacyTransition(
- mainTaskId, mainOptions, sideTaskId, sideOptions, sidePosition,
- adapter));
- }
-
- @Override
- public void startTasks(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions,
- @SplitPosition int sidePosition,
- @Nullable RemoteTransition remoteTransition) {
- executeRemoteCallWithTaskPermission(mController, "startTasks",
- (controller) -> controller.mStageCoordinator.startTasks(mainTaskId, mainOptions,
- sideTaskId, sideOptions, sidePosition, remoteTransition));
- }
-
- @Override
- public void startShortcut(String packageName, String shortcutId, int stage, int position,
- @Nullable Bundle options, UserHandle user) {
- executeRemoteCallWithTaskPermission(mController, "startShortcut",
- (controller) -> {
- controller.startShortcut(packageName, shortcutId, position,
- options, user);
- });
- }
-
- @Override
- public void startIntent(PendingIntent intent, Intent fillInIntent, int stage, int position,
- @Nullable Bundle options) {
- executeRemoteCallWithTaskPermission(mController, "startIntent",
- (controller) -> {
- controller.startIntent(intent, fillInIntent, position, options);
- });
- }
-
- @Override
- public RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel,
- RemoteAnimationTarget[] apps) {
- final RemoteAnimationTarget[][] out = new RemoteAnimationTarget[][]{null};
- executeRemoteCallWithTaskPermission(mController, "onGoingToRecentsLegacy",
- (controller) -> out[0] = controller.onGoingToRecentsLegacy(cancel, apps),
- true /* blocking */);
- return out[0];
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
deleted file mode 100644
index 018365420177..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitScreenTransitions.java
+++ /dev/null
@@ -1,292 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.WindowManager.TRANSIT_CHANGE;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-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.window.TransitionInfo.FLAG_FIRST_CUSTOM;
-
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.Rect;
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.view.WindowManager;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.transition.OneShotRemoteHandler;
-import com.android.wm.shell.transition.Transitions;
-
-import java.util.ArrayList;
-
-/** Manages transition animations for split-screen. */
-class SplitScreenTransitions {
- private static final String TAG = "SplitScreenTransitions";
-
- /** Flag applied to a transition change to identify it as a divider bar for animation. */
- public static final int FLAG_IS_DIVIDER_BAR = FLAG_FIRST_CUSTOM;
-
- private final TransactionPool mTransactionPool;
- private final Transitions mTransitions;
- private final Runnable mOnFinish;
-
- IBinder mPendingDismiss = null;
- IBinder mPendingEnter = null;
-
- private IBinder mAnimatingTransition = null;
- private OneShotRemoteHandler mRemoteHandler = null;
-
- private Transitions.TransitionFinishCallback mRemoteFinishCB = (wct, wctCB) -> {
- if (wct != null || wctCB != null) {
- throw new UnsupportedOperationException("finish transactions not supported yet.");
- }
- onFinish();
- };
-
- /** Keeps track of currently running animations */
- private final ArrayList<Animator> mAnimations = new ArrayList<>();
-
- private Transitions.TransitionFinishCallback mFinishCallback = null;
- private SurfaceControl.Transaction mFinishTransaction;
-
- SplitScreenTransitions(@NonNull TransactionPool pool, @NonNull Transitions transitions,
- @NonNull Runnable onFinishCallback) {
- mTransactionPool = pool;
- mTransitions = transitions;
- mOnFinish = onFinishCallback;
- }
-
- void playAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback,
- @NonNull WindowContainerToken mainRoot, @NonNull WindowContainerToken sideRoot) {
- mFinishCallback = finishCallback;
- mAnimatingTransition = transition;
- if (mRemoteHandler != null) {
- mRemoteHandler.startAnimation(transition, info, startTransaction, finishTransaction,
- mRemoteFinishCB);
- mRemoteHandler = null;
- return;
- }
- playInternalAnimation(transition, info, startTransaction, mainRoot, sideRoot);
- }
-
- private void playInternalAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, @NonNull WindowContainerToken mainRoot,
- @NonNull WindowContainerToken sideRoot) {
- mFinishTransaction = mTransactionPool.acquire();
-
- // Play some place-holder fade animations
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
-
- if (mode == TRANSIT_CHANGE) {
- if (change.getParent() != null) {
- // This is probably reparented, so we want the parent to be immediately visible
- final TransitionInfo.Change parentChange = info.getChange(change.getParent());
- t.show(parentChange.getLeash());
- t.setAlpha(parentChange.getLeash(), 1.f);
- // and then animate this layer outside the parent (since, for example, this is
- // the home task animating from fullscreen to part-screen).
- t.reparent(leash, info.getRootLeash());
- t.setLayer(leash, info.getChanges().size() - i);
- // build the finish reparent/reposition
- mFinishTransaction.reparent(leash, parentChange.getLeash());
- mFinishTransaction.setPosition(leash,
- change.getEndRelOffset().x, change.getEndRelOffset().y);
- }
- // TODO(shell-transitions): screenshot here
- final Rect startBounds = new Rect(change.getStartAbsBounds());
- final Rect endBounds = new Rect(change.getEndAbsBounds());
- startBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- endBounds.offset(-info.getRootOffset().x, -info.getRootOffset().y);
- startExampleResizeAnimation(leash, startBounds, endBounds);
- }
- if (change.getParent() != null) {
- continue;
- }
-
- if (transition == mPendingEnter && (mainRoot.equals(change.getContainer())
- || sideRoot.equals(change.getContainer()))) {
- t.setWindowCrop(leash, change.getStartAbsBounds().width(),
- change.getStartAbsBounds().height());
- }
- boolean isOpening = isOpeningType(info.getType());
- if (isOpening && (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT)) {
- // fade in
- startExampleAnimation(leash, true /* show */);
- } else if (!isOpening && (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK)) {
- // fade out
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Dismissing via snap-to-top/bottom means that the dismissed task is already
- // not-visible (usually cropped to oblivion) so immediately set its alpha to 0
- // and don't animate it so it doesn't pop-in when reparented.
- t.setAlpha(leash, 0.f);
- } else {
- startExampleAnimation(leash, false /* show */);
- }
- }
- }
- t.apply();
- onFinish();
- }
-
- /** Starts a transition to enter split with a remote transition animator. */
- IBinder startEnterTransition(@WindowManager.TransitionType int transitType,
- @NonNull WindowContainerTransaction wct, @Nullable RemoteTransition remoteTransition,
- @NonNull Transitions.TransitionHandler handler) {
- if (remoteTransition != null) {
- // Wrapping it for ease-of-use (OneShot handles all the binder linking/death stuff)
- mRemoteHandler = new OneShotRemoteHandler(
- mTransitions.getMainExecutor(), remoteTransition);
- }
- final IBinder transition = mTransitions.startTransition(transitType, wct, handler);
- mPendingEnter = transition;
- if (mRemoteHandler != null) {
- mRemoteHandler.setTransition(transition);
- }
- return transition;
- }
-
- /** Starts a transition for dismissing split after dragging the divider to a screen edge */
- IBinder startSnapToDismiss(@NonNull WindowContainerTransaction wct,
- @NonNull Transitions.TransitionHandler handler) {
- final IBinder transition = mTransitions.startTransition(
- TRANSIT_SPLIT_DISMISS_SNAP, wct, handler);
- mPendingDismiss = transition;
- return transition;
- }
-
- void onFinish() {
- if (!mAnimations.isEmpty()) return;
- mOnFinish.run();
- if (mFinishTransaction != null) {
- mFinishTransaction.apply();
- mTransactionPool.release(mFinishTransaction);
- mFinishTransaction = null;
- }
- mFinishCallback.onTransitionFinished(null /* wct */, null /* wctCB */);
- mFinishCallback = null;
- if (mAnimatingTransition == mPendingEnter) {
- mPendingEnter = null;
- }
- if (mAnimatingTransition == mPendingDismiss) {
- mPendingDismiss = null;
- }
- mAnimatingTransition = null;
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleAnimation(@NonNull SurfaceControl leash, boolean show) {
- final float end = show ? 1.f : 0.f;
- final float start = 1.f - end;
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(start, end);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setAlpha(leash, start * (1.f - fraction) + end * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setAlpha(leash, end);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) { }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) { }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-
- // TODO(shell-transitions): real animations
- private void startExampleResizeAnimation(@NonNull SurfaceControl leash,
- @NonNull Rect startBounds, @NonNull Rect endBounds) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- final ValueAnimator va = ValueAnimator.ofFloat(0.f, 1.f);
- va.setDuration(500);
- va.addUpdateListener(animation -> {
- float fraction = animation.getAnimatedFraction();
- transaction.setWindowCrop(leash,
- (int) (startBounds.width() * (1.f - fraction) + endBounds.width() * fraction),
- (int) (startBounds.height() * (1.f - fraction)
- + endBounds.height() * fraction));
- transaction.setPosition(leash,
- startBounds.left * (1.f - fraction) + endBounds.left * fraction,
- startBounds.top * (1.f - fraction) + endBounds.top * fraction);
- transaction.apply();
- });
- final Runnable finisher = () -> {
- transaction.setWindowCrop(leash, 0, 0);
- transaction.setPosition(leash, endBounds.left, endBounds.top);
- transaction.apply();
- mTransactionPool.release(transaction);
- mTransitions.getMainExecutor().execute(() -> {
- mAnimations.remove(va);
- onFinish();
- });
- };
- va.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- finisher.run();
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- finisher.run();
- }
- });
- mAnimations.add(va);
- mTransitions.getAnimExecutor().execute(va::start);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
deleted file mode 100644
index e1850396a5c0..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/SplitscreenEventLogger.java
+++ /dev/null
@@ -1,324 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.logging.InstanceIdSequence;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-
-/**
- * Helper class that to log Drag & Drop UIEvents for a single session, see also go/uievent
- */
-public class SplitscreenEventLogger {
-
- // Used to generate instance ids for this drag if one is not provided
- private final InstanceIdSequence mIdSequence;
-
- // The instance id for the current splitscreen session (from start to end)
- private InstanceId mLoggerSessionId;
-
- // Drag info
- private @SplitPosition int mDragEnterPosition;
- private InstanceId mDragEnterSessionId;
-
- // For deduping async events
- private int mLastMainStagePosition = -1;
- private int mLastMainStageUid = -1;
- private int mLastSideStagePosition = -1;
- private int mLastSideStageUid = -1;
- private float mLastSplitRatio = -1f;
-
- public SplitscreenEventLogger() {
- mIdSequence = new InstanceIdSequence(Integer.MAX_VALUE);
- }
-
- /**
- * Return whether a splitscreen session has started.
- */
- public boolean hasStartedSession() {
- return mLoggerSessionId != null;
- }
-
- /**
- * May be called before logEnter() to indicate that the session was started from a drag.
- */
- public void enterRequestedByDrag(@SplitPosition int position, InstanceId dragSessionId) {
- mDragEnterPosition = position;
- mDragEnterSessionId = dragSessionId;
- }
-
- /**
- * Logs when the user enters splitscreen.
- */
- public void logEnter(float splitRatio,
- @SplitPosition int mainStagePosition, int mainStageUid,
- @SplitPosition int sideStagePosition, int sideStageUid,
- boolean isLandscape) {
- mLoggerSessionId = mIdSequence.newInstanceId();
- int enterReason = mDragEnterPosition != SPLIT_POSITION_UNDEFINED
- ? getDragEnterReasonFromSplitPosition(mDragEnterPosition, isLandscape)
- : SPLITSCREEN_UICHANGED__ENTER_REASON__OVERVIEW;
- updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
- mainStageUid);
- updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
- sideStageUid);
- updateSplitRatioState(splitRatio);
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__ENTER,
- enterReason,
- 0 /* exitReason */,
- splitRatio,
- mLastMainStagePosition,
- mLastMainStageUid,
- mLastSideStagePosition,
- mLastSideStageUid,
- mDragEnterSessionId != null ? mDragEnterSessionId.getId() : 0,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when the user exits splitscreen. Only one of the main or side stages should be
- * specified to indicate which position was focused as a part of exiting (both can be unset).
- */
- public void logExit(int exitReason, @SplitPosition int mainStagePosition, int mainStageUid,
- @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if ((mainStagePosition != SPLIT_POSITION_UNDEFINED
- && sideStagePosition != SPLIT_POSITION_UNDEFINED)
- || (mainStageUid != 0 && sideStageUid != 0)) {
- throw new IllegalArgumentException("Only main or side stage should be set");
- }
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__EXIT,
- 0 /* enterReason */,
- exitReason,
- 0f /* splitRatio */,
- getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
- mainStageUid,
- getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
- sideStageUid,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
-
- // Reset states
- mLoggerSessionId = null;
- mDragEnterPosition = SPLIT_POSITION_UNDEFINED;
- mDragEnterSessionId = null;
- mLastMainStagePosition = -1;
- mLastMainStageUid = -1;
- mLastSideStagePosition = -1;
- mLastSideStageUid = -1;
- }
-
- /**
- * Logs when an app in the main stage changes.
- */
- public void logMainStageAppChange(@SplitPosition int mainStagePosition, int mainStageUid,
- boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if (!updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition,
- isLandscape), mainStageUid)) {
- // Ignore if there are no user perceived changes
- return;
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
- 0 /* enterReason */,
- 0 /* exitReason */,
- 0f /* splitRatio */,
- mLastMainStagePosition,
- mLastMainStageUid,
- 0 /* sideStagePosition */,
- 0 /* sideStageUid */,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when an app in the side stage changes.
- */
- public void logSideStageAppChange(@SplitPosition int sideStagePosition, int sideStageUid,
- boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if (!updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition,
- isLandscape), sideStageUid)) {
- // Ignore if there are no user perceived changes
- return;
- }
-
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__APP_CHANGE,
- 0 /* enterReason */,
- 0 /* exitReason */,
- 0f /* splitRatio */,
- 0 /* mainStagePosition */,
- 0 /* mainStageUid */,
- mLastSideStagePosition,
- mLastSideStageUid,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when the splitscreen ratio changes.
- */
- public void logResize(float splitRatio) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
- if (splitRatio <= 0f || splitRatio >= 1f) {
- // Don't bother reporting resizes that end up dismissing the split, that will be logged
- // via the exit event
- return;
- }
- if (!updateSplitRatioState(splitRatio)) {
- // Ignore if there are no user perceived changes
- return;
- }
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__RESIZE,
- 0 /* enterReason */,
- 0 /* exitReason */,
- mLastSplitRatio,
- 0 /* mainStagePosition */, 0 /* mainStageUid */,
- 0 /* sideStagePosition */, 0 /* sideStageUid */,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- /**
- * Logs when the apps in splitscreen are swapped.
- */
- public void logSwap(@SplitPosition int mainStagePosition, int mainStageUid,
- @SplitPosition int sideStagePosition, int sideStageUid, boolean isLandscape) {
- if (mLoggerSessionId == null) {
- // Ignore changes until we've started logging the session
- return;
- }
-
- updateMainStageState(getMainStagePositionFromSplitPosition(mainStagePosition, isLandscape),
- mainStageUid);
- updateSideStageState(getSideStagePositionFromSplitPosition(sideStagePosition, isLandscape),
- sideStageUid);
- FrameworkStatsLog.write(FrameworkStatsLog.SPLITSCREEN_UI_CHANGED,
- FrameworkStatsLog.SPLITSCREEN_UICHANGED__ACTION__SWAP,
- 0 /* enterReason */,
- 0 /* exitReason */,
- 0f /* splitRatio */,
- mLastMainStagePosition,
- mLastMainStageUid,
- mLastSideStagePosition,
- mLastSideStageUid,
- 0 /* dragInstanceId */,
- mLoggerSessionId.getId());
- }
-
- private boolean updateMainStageState(int mainStagePosition, int mainStageUid) {
- boolean changed = (mLastMainStagePosition != mainStagePosition)
- || (mLastMainStageUid != mainStageUid);
- if (!changed) {
- return false;
- }
-
- mLastMainStagePosition = mainStagePosition;
- mLastMainStageUid = mainStageUid;
- return true;
- }
-
- private boolean updateSideStageState(int sideStagePosition, int sideStageUid) {
- boolean changed = (mLastSideStagePosition != sideStagePosition)
- || (mLastSideStageUid != sideStageUid);
- if (!changed) {
- return false;
- }
-
- mLastSideStagePosition = sideStagePosition;
- mLastSideStageUid = sideStageUid;
- return true;
- }
-
- private boolean updateSplitRatioState(float splitRatio) {
- boolean changed = Float.compare(mLastSplitRatio, splitRatio) != 0;
- if (!changed) {
- return false;
- }
-
- mLastSplitRatio = splitRatio;
- return true;
- }
-
- public int getDragEnterReasonFromSplitPosition(@SplitPosition int position,
- boolean isLandscape) {
- if (isLandscape) {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_LEFT
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_RIGHT;
- } else {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_TOP
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__ENTER_REASON__DRAG_BOTTOM;
- }
- }
-
- private int getMainStagePositionFromSplitPosition(@SplitPosition int position,
- boolean isLandscape) {
- if (position == SPLIT_POSITION_UNDEFINED) {
- return 0;
- }
- if (isLandscape) {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__LEFT
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__RIGHT;
- } else {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__TOP
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__MAIN_STAGE_POSITION__BOTTOM;
- }
- }
-
- private int getSideStagePositionFromSplitPosition(@SplitPosition int position,
- boolean isLandscape) {
- if (position == SPLIT_POSITION_UNDEFINED) {
- return 0;
- }
- if (isLandscape) {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__LEFT
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__RIGHT;
- } else {
- return position == SPLIT_POSITION_TOP_OR_LEFT
- ? FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__TOP
- : FrameworkStatsLog.SPLITSCREEN_UICHANGED__SIDE_STAGE_POSITION__BOTTOM;
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
deleted file mode 100644
index de0feeecad4b..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageCoordinator.java
+++ /dev/null
@@ -1,1333 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.app.ActivityOptions.KEY_LAUNCH_ROOT_TASK_TOKEN;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.transitTypeToString;
-
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME;
-import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_MAIN;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_SIDE;
-import static com.android.wm.shell.stagesplit.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.stagesplit.SplitScreen.stageTypeToString;
-import static com.android.wm.shell.stagesplit.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
-import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
-import static com.android.wm.shell.transition.Transitions.isClosingType;
-import static com.android.wm.shell.transition.Transitions.isOpeningType;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
-import android.app.ActivityTaskManager;
-import android.app.PendingIntent;
-import android.app.WindowConfiguration;
-import android.content.Context;
-import android.content.Intent;
-import android.graphics.Rect;
-import android.hardware.devicestate.DeviceStateManager;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.view.WindowManager;
-import android.window.DisplayAreaInfo;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-import android.window.TransitionRequestInfo;
-import android.window.WindowContainerToken;
-import android.window.WindowContainerTransaction;
-
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.logging.InstanceId;
-import com.android.internal.protolog.common.ProtoLog;
-import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.DisplayImeController;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.common.split.SplitWindowManager;
-import com.android.wm.shell.protolog.ShellProtoLogGroup;
-import com.android.wm.shell.transition.Transitions;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-import javax.inject.Provider;
-
-/**
- * Coordinates the staging (visibility, sizing, ...) of the split-screen {@link MainStage} and
- * {@link SideStage} stages.
- * Some high-level rules:
- * - The {@link StageCoordinator} is only considered active if the {@link SideStage} contains at
- * least one child task.
- * - The {@link MainStage} should only have children if the coordinator is active.
- * - The {@link SplitLayout} divider is only visible if both the {@link MainStage}
- * and {@link SideStage} are visible.
- * - The {@link MainStage} configuration is fullscreen when the {@link SideStage} isn't visible.
- * This rules are mostly implemented in {@link #onStageVisibilityChanged(StageListenerImpl)} and
- * {@link #onStageHasChildrenChanged(StageListenerImpl).}
- */
-class StageCoordinator implements SplitLayout.SplitLayoutHandler,
- RootTaskDisplayAreaOrganizer.RootTaskDisplayAreaListener, Transitions.TransitionHandler {
-
- private static final String TAG = StageCoordinator.class.getSimpleName();
-
- /** internal value for mDismissTop that represents no dismiss */
- private static final int NO_DISMISS = -2;
-
- private final SurfaceSession mSurfaceSession = new SurfaceSession();
-
- private final MainStage mMainStage;
- private final StageListenerImpl mMainStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mMainUnfoldController;
- private final SideStage mSideStage;
- private final StageListenerImpl mSideStageListener = new StageListenerImpl();
- private final StageTaskUnfoldController mSideUnfoldController;
- @SplitPosition
- private int mSideStagePosition = SPLIT_POSITION_BOTTOM_OR_RIGHT;
-
- private final int mDisplayId;
- private SplitLayout mSplitLayout;
- private boolean mDividerVisible;
- private final SyncTransactionQueue mSyncQueue;
- private final RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
- private final ShellTaskOrganizer mTaskOrganizer;
- private DisplayAreaInfo mDisplayAreaInfo;
- private final Context mContext;
- private final List<SplitScreen.SplitScreenListener> mListeners = new ArrayList<>();
- private final DisplayImeController mDisplayImeController;
- private final DisplayInsetsController mDisplayInsetsController;
- private final SplitScreenTransitions mSplitTransitions;
- private final SplitscreenEventLogger mLogger;
- private boolean mExitSplitScreenOnHide;
- private boolean mKeyguardOccluded;
-
- // TODO(b/187041611): remove this flag after totally deprecated legacy split
- /** Whether the device is supporting legacy split or not. */
- private boolean mUseLegacySplit;
-
- @SplitScreen.StageType private int mDismissTop = NO_DISMISS;
-
- /** The target stage to dismiss to when unlock after folded. */
- @SplitScreen.StageType private int mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
-
- private final Runnable mOnTransitionAnimationComplete = () -> {
- // If still playing, let it finish.
- if (!isSplitScreenVisible()) {
- // Update divider state after animation so that it is still around and positioned
- // properly for the animation itself.
- setDividerVisibility(false);
- mSplitLayout.resetDividerPosition();
- }
- mDismissTop = NO_DISMISS;
- };
-
- private final SplitWindowManager.ParentContainerCallbacks mParentContainerCallbacks =
- new SplitWindowManager.ParentContainerCallbacks() {
- @Override
- public void attachToParentSurface(SurfaceControl.Builder b) {
- mRootTDAOrganizer.attachToDisplayArea(mDisplayId, b);
- }
-
- @Override
- public void onLeashReady(SurfaceControl leash) {
- mSyncQueue.runInSync(t -> applyDividerVisibility(t));
- }
- };
-
- StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, Transitions transitions,
- TransactionPool transactionPool, SplitscreenEventLogger logger,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- mContext = context;
- mDisplayId = displayId;
- mSyncQueue = syncQueue;
- mRootTDAOrganizer = rootTDAOrganizer;
- mTaskOrganizer = taskOrganizer;
- mLogger = logger;
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
-
- mMainStage = new MainStage(
- mTaskOrganizer,
- mDisplayId,
- mMainStageListener,
- mSyncQueue,
- mSurfaceSession,
- mMainUnfoldController);
- mSideStage = new SideStage(
- mContext,
- mTaskOrganizer,
- mDisplayId,
- mSideStageListener,
- mSyncQueue,
- mSurfaceSession,
- mSideUnfoldController);
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSideStage);
- mRootTDAOrganizer.registerListener(displayId, this);
- final DeviceStateManager deviceStateManager =
- mContext.getSystemService(DeviceStateManager.class);
- deviceStateManager.registerCallback(taskOrganizer.getExecutor(),
- new DeviceStateManager.FoldStateListener(mContext, this::onFoldedStateChanged));
- mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
- mOnTransitionAnimationComplete);
- transitions.addHandler(this);
- }
-
- @VisibleForTesting
- StageCoordinator(Context context, int displayId, SyncTransactionQueue syncQueue,
- RootTaskDisplayAreaOrganizer rootTDAOrganizer, ShellTaskOrganizer taskOrganizer,
- MainStage mainStage, SideStage sideStage, DisplayImeController displayImeController,
- DisplayInsetsController displayInsetsController, SplitLayout splitLayout,
- Transitions transitions, TransactionPool transactionPool,
- SplitscreenEventLogger logger,
- Provider<Optional<StageTaskUnfoldController>> unfoldControllerProvider) {
- mContext = context;
- mDisplayId = displayId;
- mSyncQueue = syncQueue;
- mRootTDAOrganizer = rootTDAOrganizer;
- mTaskOrganizer = taskOrganizer;
- mMainStage = mainStage;
- mSideStage = sideStage;
- mDisplayImeController = displayImeController;
- mDisplayInsetsController = displayInsetsController;
- mRootTDAOrganizer.registerListener(displayId, this);
- mSplitLayout = splitLayout;
- mSplitTransitions = new SplitScreenTransitions(transactionPool, transitions,
- mOnTransitionAnimationComplete);
- mMainUnfoldController = unfoldControllerProvider.get().orElse(null);
- mSideUnfoldController = unfoldControllerProvider.get().orElse(null);
- mLogger = logger;
- transitions.addHandler(this);
- }
-
- @VisibleForTesting
- SplitScreenTransitions getSplitTransitions() {
- return mSplitTransitions;
- }
-
- boolean isSplitScreenVisible() {
- return mSideStageListener.mVisible && mMainStageListener.mVisible;
- }
-
- boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
- @SplitPosition int sideStagePosition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- setSideStagePosition(sideStagePosition, wct);
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.addTask(task, getSideStageBounds(), wct);
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(
- t -> updateSurfaceBounds(null /* layout */, t, false /* applyResizingOffset */));
- return true;
- }
-
- boolean removeFromSideStage(int taskId) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
-
- /**
- * {@link MainStage} will be deactivated in {@link #onStageHasChildrenChanged} if the
- * {@link SideStage} no longer has children.
- */
- final boolean result = mSideStage.removeTask(taskId,
- mMainStage.isActive() ? mMainStage.mRootTaskInfo.token : null,
- wct);
- mTaskOrganizer.applyTransaction(wct);
- return result;
- }
-
- void setSideStageOutline(boolean enable) {
- mSideStage.enableOutline(enable);
- }
-
- /** Starts 2 tasks in one transition. */
- void startTasks(int mainTaskId, @Nullable Bundle mainOptions, int sideTaskId,
- @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- @Nullable RemoteTransition remoteTransition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mainOptions = mainOptions != null ? mainOptions : new Bundle();
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
-
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
-
- // Make sure the launch options will put tasks in the corresponding split roots
- addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
-
- // Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
-
- mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_PAIR_OPEN, wct, remoteTransition, this);
- }
-
- /** Starts 2 tasks in one legacy transition. */
- void startTasksWithLegacyTransition(int mainTaskId, @Nullable Bundle mainOptions,
- int sideTaskId, @Nullable Bundle sideOptions, @SplitPosition int sidePosition,
- RemoteAnimationAdapter adapter) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Need to add another wrapper here in shell so that we can inject the divider bar
- // and also manage the process elevation via setRunningRemote
- IRemoteAnimationRunner wrapper = new IRemoteAnimationRunner.Stub() {
- @Override
- public void onAnimationStart(@WindowManager.TransitionOldType int transit,
- RemoteAnimationTarget[] apps,
- RemoteAnimationTarget[] wallpapers,
- RemoteAnimationTarget[] nonApps,
- final IRemoteAnimationFinishedCallback finishedCallback) {
- RemoteAnimationTarget[] augmentedNonApps =
- new RemoteAnimationTarget[nonApps.length + 1];
- for (int i = 0; i < nonApps.length; ++i) {
- augmentedNonApps[i] = nonApps[i];
- }
- augmentedNonApps[augmentedNonApps.length - 1] = getDividerBarLegacyTarget();
- try {
- ActivityTaskManager.getService().setRunningRemoteTransitionDelegate(
- adapter.getCallingApplication());
- adapter.getRunner().onAnimationStart(transit, apps, wallpapers, nonApps,
- finishedCallback);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
-
- @Override
- public void onAnimationCancelled(boolean isKeyguardOccluded) {
- try {
- adapter.getRunner().onAnimationCancelled(isKeyguardOccluded);
- } catch (RemoteException e) {
- Slog.e(TAG, "Error starting remote animation", e);
- }
- }
- };
- RemoteAnimationAdapter wrappedAdapter = new RemoteAnimationAdapter(
- wrapper, adapter.getDuration(), adapter.getStatusBarTransitionDelay());
-
- if (mainOptions == null) {
- mainOptions = ActivityOptions.makeRemoteAnimation(wrappedAdapter).toBundle();
- } else {
- ActivityOptions mainActivityOptions = ActivityOptions.fromBundle(mainOptions);
- mainActivityOptions.update(ActivityOptions.makeRemoteAnimation(wrappedAdapter));
- }
-
- sideOptions = sideOptions != null ? sideOptions : new Bundle();
- setSideStagePosition(sidePosition, wct);
-
- // Build a request WCT that will launch both apps such that task 0 is on the main stage
- // while task 1 is on the side stage.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
-
- // Make sure the launch options will put tasks in the corresponding split roots
- addActivityOptions(mainOptions, mMainStage);
- addActivityOptions(sideOptions, mSideStage);
-
- // Add task launch requests
- wct.startTask(mainTaskId, mainOptions);
- wct.startTask(sideTaskId, sideOptions);
-
- // Using legacy transitions, so we can't use blast sync since it conflicts.
- mTaskOrganizer.applyTransaction(wct);
- }
-
- public void startIntent(PendingIntent intent, Intent fillInIntent,
- @SplitScreen.StageType int stage, @SplitPosition int position,
- @androidx.annotation.Nullable Bundle options,
- @Nullable RemoteTransition remoteTransition) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- options = resolveStartStage(stage, position, options, wct);
- wct.sendPendingIntent(intent, fillInIntent, options);
- mSplitTransitions.startEnterTransition(
- TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
- }
-
- Bundle resolveStartStage(@SplitScreen.StageType int stage,
- @SplitPosition int position, @androidx.annotation.Nullable Bundle options,
- @androidx.annotation.Nullable WindowContainerTransaction wct) {
- switch (stage) {
- case STAGE_TYPE_UNDEFINED: {
- // Use the stage of the specified position is valid.
- if (position != SPLIT_POSITION_UNDEFINED) {
- if (position == getSideStagePosition()) {
- options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
- } else {
- options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
- }
- } else {
- // Exit split-screen and launch fullscreen since stage wasn't specified.
- prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
- }
- break;
- }
- case STAGE_TYPE_SIDE: {
- if (position != SPLIT_POSITION_UNDEFINED) {
- setSideStagePosition(position, wct);
- } else {
- position = getSideStagePosition();
- }
- if (options == null) {
- options = new Bundle();
- }
- updateActivityOptions(options, position);
- break;
- }
- case STAGE_TYPE_MAIN: {
- if (position != SPLIT_POSITION_UNDEFINED) {
- // Set the side stage opposite of what we want to the main stage.
- final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- setSideStagePosition(sideStagePosition, wct);
- } else {
- position = getMainStagePosition();
- }
- if (options == null) {
- options = new Bundle();
- }
- updateActivityOptions(options, position);
- break;
- }
- default:
- throw new IllegalArgumentException("Unknown stage=" + stage);
- }
-
- return options;
- }
-
- @SplitPosition
- int getSideStagePosition() {
- return mSideStagePosition;
- }
-
- @SplitPosition
- int getMainStagePosition() {
- return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
- }
-
- void setSideStagePosition(@SplitPosition int sideStagePosition,
- @Nullable WindowContainerTransaction wct) {
- setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
- }
-
- private void setSideStagePosition(@SplitPosition int sideStagePosition, boolean updateBounds,
- @Nullable WindowContainerTransaction wct) {
- if (mSideStagePosition == sideStagePosition) return;
- mSideStagePosition = sideStagePosition;
- sendOnStagePositionChanged();
-
- if (mSideStageListener.mVisible && updateBounds) {
- if (wct == null) {
- // onLayoutSizeChanged builds/applies a wct with the contents of updateWindowBounds.
- onLayoutSizeChanged(mSplitLayout);
- } else {
- updateWindowBounds(mSplitLayout, wct);
- updateUnfoldBounds();
- }
- }
- }
-
- void setSideStageVisibility(boolean visible) {
- if (mSideStageListener.mVisible == visible) return;
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- mSideStage.setVisibility(visible, wct);
- mTaskOrganizer.applyTransaction(wct);
- }
-
- void onKeyguardOccludedChanged(boolean occluded) {
- // Do not exit split directly, because it needs to wait for task info update to determine
- // which task should remain on top after split dismissed.
- mKeyguardOccluded = occluded;
- }
-
- void onKeyguardVisibilityChanged(boolean showing) {
- if (!showing && mMainStage.isActive()
- && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) {
- exitSplitScreen(mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage,
- SPLITSCREEN_UICHANGED__EXIT_REASON__DEVICE_FOLDED);
- }
- }
-
- void exitSplitScreenOnHide(boolean exitSplitScreenOnHide) {
- mExitSplitScreenOnHide = exitSplitScreenOnHide;
- }
-
- void exitSplitScreen(int toTopTaskId, int exitReason) {
- StageTaskListener childrenToTop = null;
- if (mMainStage.containsTask(toTopTaskId)) {
- childrenToTop = mMainStage;
- } else if (mSideStage.containsTask(toTopTaskId)) {
- childrenToTop = mSideStage;
- }
-
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- if (childrenToTop != null) {
- childrenToTop.reorderChild(toTopTaskId, true /* onTop */, wct);
- }
- applyExitSplitScreen(childrenToTop, wct, exitReason);
- }
-
- private void exitSplitScreen(StageTaskListener childrenToTop, int exitReason) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- applyExitSplitScreen(childrenToTop, wct, exitReason);
- }
-
- private void applyExitSplitScreen(
- StageTaskListener childrenToTop,
- WindowContainerTransaction wct, int exitReason) {
- mSideStage.removeAllTasks(wct, childrenToTop == mSideStage);
- mMainStage.deactivate(wct, childrenToTop == mMainStage);
- mTaskOrganizer.applyTransaction(wct);
- mSyncQueue.runInSync(t -> t
- .setWindowCrop(mMainStage.mRootLeash, null)
- .setWindowCrop(mSideStage.mRootLeash, null));
- // Hide divider and reset its position.
- setDividerVisibility(false);
- mSplitLayout.resetDividerPosition();
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
- if (childrenToTop != null) {
- logExitToStage(exitReason, childrenToTop == mMainStage);
- } else {
- logExit(exitReason);
- }
- }
-
- /**
- * Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
- * an existing WindowContainerTransaction (rather than applying immediately). This is intended
- * to be used when exiting split might be bundled with other window operations.
- */
- void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
- @NonNull WindowContainerTransaction wct) {
- mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
- mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
- }
-
- void getStageBounds(Rect outTopOrLeftBounds, Rect outBottomOrRightBounds) {
- outTopOrLeftBounds.set(mSplitLayout.getBounds1());
- outBottomOrRightBounds.set(mSplitLayout.getBounds2());
- }
-
- private void addActivityOptions(Bundle opts, StageTaskListener stage) {
- opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
- }
-
- void updateActivityOptions(Bundle opts, @SplitPosition int position) {
- addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
- }
-
- void registerSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- if (mListeners.contains(listener)) return;
- mListeners.add(listener);
- sendStatusToListener(listener);
- }
-
- void unregisterSplitScreenListener(SplitScreen.SplitScreenListener listener) {
- mListeners.remove(listener);
- }
-
- void sendStatusToListener(SplitScreen.SplitScreenListener listener) {
- listener.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
- listener.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
- listener.onSplitVisibilityChanged(isSplitScreenVisible());
- mSideStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_SIDE);
- mMainStage.onSplitScreenListenerRegistered(listener, STAGE_TYPE_MAIN);
- }
-
- private void sendOnStagePositionChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- final SplitScreen.SplitScreenListener l = mListeners.get(i);
- l.onStagePositionChanged(STAGE_TYPE_MAIN, getMainStagePosition());
- l.onStagePositionChanged(STAGE_TYPE_SIDE, getSideStagePosition());
- }
- }
-
- private void onStageChildTaskStatusChanged(StageListenerImpl stageListener, int taskId,
- boolean present, boolean visible) {
- int stage;
- if (present) {
- stage = stageListener == mSideStageListener ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
- } else {
- // No longer on any stage
- stage = STAGE_TYPE_UNDEFINED;
- }
- if (stage == STAGE_TYPE_MAIN) {
- mLogger.logMainStageAppChange(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- } else {
- mLogger.logSideStageAppChange(getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- }
-
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- mListeners.get(i).onTaskStageChanged(taskId, stage, visible);
- }
- }
-
- private void sendSplitVisibilityChanged() {
- for (int i = mListeners.size() - 1; i >= 0; --i) {
- final SplitScreen.SplitScreenListener l = mListeners.get(i);
- l.onSplitVisibilityChanged(mDividerVisible);
- }
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- mSideUnfoldController.onSplitVisibilityChanged(mDividerVisible);
- }
- }
-
- private void onStageRootTaskAppeared(StageListenerImpl stageListener) {
- if (mMainStageListener.mHasRootTask && mSideStageListener.mHasRootTask) {
- mUseLegacySplit = mContext.getResources().getBoolean(R.bool.config_useLegacySplit);
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Make the stages adjacent to each other so they occlude what's behind them.
- wct.setAdjacentRoots(mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token,
- true /* moveTogether */);
-
- // Only sets side stage as launch-adjacent-flag-root when the device is not using legacy
- // split to prevent new split behavior confusing users.
- if (!mUseLegacySplit) {
- wct.setLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- }
-
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- private void onStageRootTaskVanished(StageListenerImpl stageListener) {
- if (stageListener == mMainStageListener || stageListener == mSideStageListener) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Deactivate the main stage if it no longer has a root task.
- mMainStage.deactivate(wct);
-
- if (!mUseLegacySplit) {
- wct.clearLaunchAdjacentFlagRoot(mSideStage.mRootTaskInfo.token);
- }
-
- mTaskOrganizer.applyTransaction(wct);
- }
- }
-
- private void setDividerVisibility(boolean visible) {
- if (mDividerVisible == visible) return;
- mDividerVisible = visible;
- if (visible) {
- mSplitLayout.init();
- updateUnfoldBounds();
- } else {
- mSplitLayout.release();
- }
- sendSplitVisibilityChanged();
- }
-
- private void onStageVisibilityChanged(StageListenerImpl stageListener) {
- final boolean sideStageVisible = mSideStageListener.mVisible;
- final boolean mainStageVisible = mMainStageListener.mVisible;
- final boolean bothStageVisible = sideStageVisible && mainStageVisible;
- final boolean bothStageInvisible = !sideStageVisible && !mainStageVisible;
- final boolean sameVisibility = sideStageVisible == mainStageVisible;
- // Only add or remove divider when both visible or both invisible to avoid sometimes we only
- // got one stage visibility changed for a moment and it will cause flicker.
- if (sameVisibility) {
- setDividerVisibility(bothStageVisible);
- }
-
- if (bothStageInvisible) {
- if (mExitSplitScreenOnHide
- // Don't dismiss staged split when both stages are not visible due to sleeping display,
- // like the cases keyguard showing or screen off.
- || (!mMainStage.mRootTaskInfo.isSleeping && !mSideStage.mRootTaskInfo.isSleeping)) {
- exitSplitScreen(null /* childrenToTop */,
- SPLITSCREEN_UICHANGED__EXIT_REASON__RETURN_HOME);
- }
- } else if (mKeyguardOccluded) {
- // At least one of the stages is visible while keyguard occluded. Dismiss split because
- // there's show-when-locked activity showing on top of keyguard. Also make sure the
- // task contains show-when-locked activity remains on top after split dismissed.
- final StageTaskListener toTop =
- mainStageVisible ? mMainStage : (sideStageVisible ? mSideStage : null);
- exitSplitScreen(toTop, SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP);
- }
-
- mSyncQueue.runInSync(t -> {
- // Same above, we only set root tasks and divider leash visibility when both stage
- // change to visible or invisible to avoid flicker.
- if (sameVisibility) {
- t.setVisibility(mSideStage.mRootLeash, bothStageVisible)
- .setVisibility(mMainStage.mRootLeash, bothStageVisible);
- applyDividerVisibility(t);
- applyOutlineVisibility(t);
- }
- });
- }
-
- private void applyDividerVisibility(SurfaceControl.Transaction t) {
- final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash();
- if (dividerLeash == null) {
- return;
- }
-
- if (mDividerVisible) {
- t.show(dividerLeash)
- .setLayer(dividerLeash, Integer.MAX_VALUE)
- .setPosition(dividerLeash,
- mSplitLayout.getDividerBounds().left,
- mSplitLayout.getDividerBounds().top);
- } else {
- t.hide(dividerLeash);
- }
- }
-
- private void applyOutlineVisibility(SurfaceControl.Transaction t) {
- final SurfaceControl outlineLeash = mSideStage.getOutlineLeash();
- if (outlineLeash == null) {
- return;
- }
-
- if (mDividerVisible) {
- t.show(outlineLeash).setLayer(outlineLeash, Integer.MAX_VALUE);
- } else {
- t.hide(outlineLeash);
- }
- }
-
- private void onStageHasChildrenChanged(StageListenerImpl stageListener) {
- final boolean hasChildren = stageListener.mHasChildren;
- final boolean isSideStage = stageListener == mSideStageListener;
- if (!hasChildren) {
- if (isSideStage && mMainStageListener.mVisible) {
- // Exit to main stage if side stage no longer has children.
- exitSplitScreen(mMainStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
- } else if (!isSideStage && mSideStageListener.mVisible) {
- // Exit to side stage if main stage no longer has children.
- exitSplitScreen(mSideStage, SPLITSCREEN_UICHANGED__EXIT_REASON__APP_FINISHED);
- }
- } else if (isSideStage) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- // Make sure the main stage is active.
- mMainStage.activate(getMainStageBounds(), wct);
- mSideStage.setBounds(getSideStageBounds(), wct);
- mTaskOrganizer.applyTransaction(wct);
- }
- if (!mLogger.hasStartedSession() && mMainStageListener.mHasChildren
- && mSideStageListener.mHasChildren) {
- mLogger.logEnter(mSplitLayout.getDividerPositionAsFraction(),
- getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- }
- }
-
- @VisibleForTesting
- IBinder onSnappedToDismissTransition(boolean mainStageToTop) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- prepareExitSplitScreen(mainStageToTop ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE, wct);
- return mSplitTransitions.startSnapToDismiss(wct, this);
- }
-
- @Override
- public void onSnappedToDismiss(boolean bottomOrRight) {
- final boolean mainStageToTop =
- bottomOrRight ? mSideStagePosition == SPLIT_POSITION_BOTTOM_OR_RIGHT
- : mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT;
- if (ENABLE_SHELL_TRANSITIONS) {
- onSnappedToDismissTransition(mainStageToTop);
- return;
- }
- exitSplitScreen(mainStageToTop ? mMainStage : mSideStage,
- SPLITSCREEN_UICHANGED__EXIT_REASON__DRAG_DIVIDER);
- }
-
- @Override
- public void onDoubleTappedDivider() {
- setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
- mLogger.logSwap(getMainStagePosition(), mMainStage.getTopChildTaskUid(),
- getSideStagePosition(), mSideStage.getTopChildTaskUid(),
- mSplitLayout.isLandscape());
- }
-
- @Override
- public void onLayoutPositionChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */));
- }
-
- @Override
- public void onLayoutSizeChanging(SplitLayout layout) {
- mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, true /* applyResizingOffset */));
- mSideStage.setOutlineVisibility(false);
- }
-
- @Override
- public void onLayoutSizeChanged(SplitLayout layout) {
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- updateWindowBounds(layout, wct);
- updateUnfoldBounds();
- mSyncQueue.queue(wct);
- mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t, false /* applyResizingOffset */));
- mSideStage.setOutlineVisibility(true);
- mLogger.logResize(mSplitLayout.getDividerPositionAsFraction());
- }
-
- private void updateUnfoldBounds() {
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.onLayoutChanged(getMainStageBounds());
- mSideUnfoldController.onLayoutChanged(getSideStageBounds());
- }
- }
-
- /**
- * Populates `wct` with operations that match the split windows to the current layout.
- * To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
- */
- private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
- }
-
- void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t,
- boolean applyResizingOffset) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- (layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
- bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer,
- applyResizingOffset);
- }
-
- @Override
- public int getSplitItemPosition(WindowContainerToken token) {
- if (token == null) {
- return SPLIT_POSITION_UNDEFINED;
- }
-
- if (token.equals(mMainStage.mRootTaskInfo.getToken())) {
- return getMainStagePosition();
- } else if (token.equals(mSideStage.mRootTaskInfo.getToken())) {
- return getSideStagePosition();
- }
-
- return SPLIT_POSITION_UNDEFINED;
- }
-
- @Override
- public void setLayoutOffsetTarget(int offsetX, int offsetY, SplitLayout layout) {
- final StageTaskListener topLeftStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
- final StageTaskListener bottomRightStage =
- mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
- final WindowContainerTransaction wct = new WindowContainerTransaction();
- layout.applyLayoutOffsetTarget(wct, offsetX, offsetY, topLeftStage.mRootTaskInfo,
- bottomRightStage.mRootTaskInfo);
- mTaskOrganizer.applyTransaction(wct);
- }
-
- @Override
- public void onDisplayAreaAppeared(DisplayAreaInfo displayAreaInfo) {
- mDisplayAreaInfo = displayAreaInfo;
- if (mSplitLayout == null) {
- mSplitLayout = new SplitLayout(TAG + "SplitDivider", mContext,
- mDisplayAreaInfo.configuration, this, mParentContainerCallbacks,
- mDisplayImeController, mTaskOrganizer, SplitLayout.PARALLAX_DISMISSING);
- mDisplayInsetsController.addInsetsChangedListener(mDisplayId, mSplitLayout);
-
- if (mMainUnfoldController != null && mSideUnfoldController != null) {
- mMainUnfoldController.init();
- mSideUnfoldController.init();
- }
- }
- }
-
- @Override
- public void onDisplayAreaVanished(DisplayAreaInfo displayAreaInfo) {
- throw new IllegalStateException("Well that was unexpected...");
- }
-
- @Override
- public void onDisplayAreaInfoChanged(DisplayAreaInfo displayAreaInfo) {
- mDisplayAreaInfo = displayAreaInfo;
- if (mSplitLayout != null
- && mSplitLayout.updateConfiguration(mDisplayAreaInfo.configuration)
- && mMainStage.isActive()) {
- onLayoutSizeChanged(mSplitLayout);
- }
- }
-
- private void onFoldedStateChanged(boolean folded) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_UNDEFINED;
- if (!folded) return;
-
- if (mMainStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_MAIN;
- } else if (mSideStage.isFocused()) {
- mTopStageAfterFoldDismiss = STAGE_TYPE_SIDE;
- }
- }
-
- private Rect getSideStageBounds() {
- return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? mSplitLayout.getBounds1() : mSplitLayout.getBounds2();
- }
-
- private Rect getMainStageBounds() {
- return mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
- ? mSplitLayout.getBounds2() : mSplitLayout.getBounds1();
- }
-
- /**
- * Get the stage that should contain this `taskInfo`. The stage doesn't necessarily contain
- * this task (yet) so this can also be used to identify which stage to put a task into.
- */
- private StageTaskListener getStageOfTask(ActivityManager.RunningTaskInfo taskInfo) {
- // TODO(b/184679596): Find a way to either include task-org information in the transition,
- // or synchronize task-org callbacks so we can use stage.containsTask
- if (mMainStage.mRootTaskInfo != null
- && taskInfo.parentTaskId == mMainStage.mRootTaskInfo.taskId) {
- return mMainStage;
- } else if (mSideStage.mRootTaskInfo != null
- && taskInfo.parentTaskId == mSideStage.mRootTaskInfo.taskId) {
- return mSideStage;
- }
- return null;
- }
-
- @SplitScreen.StageType
- private int getStageType(StageTaskListener stage) {
- return stage == mMainStage ? STAGE_TYPE_MAIN : STAGE_TYPE_SIDE;
- }
-
- @Override
- public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
- @Nullable TransitionRequestInfo request) {
- final ActivityManager.RunningTaskInfo triggerTask = request.getTriggerTask();
- if (triggerTask == null) {
- // still want to monitor everything while in split-screen, so return non-null.
- return isSplitScreenVisible() ? new WindowContainerTransaction() : null;
- }
-
- WindowContainerTransaction out = null;
- final @WindowManager.TransitionType int type = request.getType();
- if (isSplitScreenVisible()) {
- // try to handle everything while in split-screen, so return a WCT even if it's empty.
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " split is active so using split"
- + "Transition to handle request. triggerTask=%d type=%s mainChildren=%d"
- + " sideChildren=%d", triggerTask.taskId, transitTypeToString(type),
- mMainStage.getChildCount(), mSideStage.getChildCount());
- out = new WindowContainerTransaction();
- final StageTaskListener stage = getStageOfTask(triggerTask);
- if (stage != null) {
- // dismiss split if the last task in one of the stages is going away
- if (isClosingType(type) && stage.getChildCount() == 1) {
- // The top should be the opposite side that is closing:
- mDismissTop = getStageType(stage) == STAGE_TYPE_MAIN
- ? STAGE_TYPE_SIDE : STAGE_TYPE_MAIN;
- }
- } else {
- if (triggerTask.getActivityType() == ACTIVITY_TYPE_HOME && isOpeningType(type)) {
- // Going home so dismiss both.
- mDismissTop = STAGE_TYPE_UNDEFINED;
- }
- }
- if (mDismissTop != NO_DISMISS) {
- ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " splitTransition "
- + " deduced Dismiss from request. toTop=%s",
- stageTypeToString(mDismissTop));
- prepareExitSplitScreen(mDismissTop, out);
- mSplitTransitions.mPendingDismiss = transition;
- }
- } else {
- // Not in split mode, so look for an open into a split stage just so we can whine and
- // complain about how this isn't a supported operation.
- if ((type == TRANSIT_OPEN || type == TRANSIT_TO_FRONT)) {
- if (getStageOfTask(triggerTask) != null) {
- throw new IllegalStateException("Entering split implicitly with only one task"
- + " isn't supported.");
- }
- }
- }
- return out;
- }
-
- @Override
- public boolean startAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction startTransaction,
- @NonNull SurfaceControl.Transaction finishTransaction,
- @NonNull Transitions.TransitionFinishCallback finishCallback) {
- if (transition != mSplitTransitions.mPendingDismiss
- && transition != mSplitTransitions.mPendingEnter) {
- // Not entering or exiting, so just do some house-keeping and validation.
-
- // If we're not in split-mode, just abort so something else can handle it.
- if (!isSplitScreenVisible()) return false;
-
- for (int iC = 0; iC < info.getChanges().size(); ++iC) {
- final TransitionInfo.Change change = info.getChanges().get(iC);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null || !taskInfo.hasParentTask()) continue;
- final StageTaskListener stage = getStageOfTask(taskInfo);
- if (stage == null) continue;
- if (isOpeningType(change.getMode())) {
- if (!stage.containsTask(taskInfo.taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + stage + " to have been called"
- + " with " + taskInfo.taskId + " before startAnimation().");
- }
- } else if (isClosingType(change.getMode())) {
- if (stage.containsTask(taskInfo.taskId)) {
- Log.w(TAG, "Expected onTaskVanished on " + stage + " to have been called"
- + " with " + taskInfo.taskId + " before startAnimation().");
- }
- }
- }
- if (mMainStage.getChildCount() == 0 || mSideStage.getChildCount() == 0) {
- // TODO(shell-transitions): Implement a fallback behavior for now.
- throw new IllegalStateException("Somehow removed the last task in a stage"
- + " outside of a proper transition");
- // 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
- // In this case, the result *should* be that we leave split.
- // 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;
- }
-
- boolean shouldAnimate = true;
- if (mSplitTransitions.mPendingEnter == transition) {
- shouldAnimate = startPendingEnterAnimation(transition, info, startTransaction);
- } else if (mSplitTransitions.mPendingDismiss == transition) {
- shouldAnimate = startPendingDismissAnimation(transition, info, startTransaction);
- }
- if (!shouldAnimate) return false;
-
- mSplitTransitions.playAnimation(transition, info, startTransaction, finishTransaction,
- finishCallback, mMainStage.mRootTaskInfo.token, mSideStage.mRootTaskInfo.token);
- return true;
- }
-
- private boolean startPendingEnterAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
- if (info.getType() == TRANSIT_SPLIT_SCREEN_PAIR_OPEN) {
- // First, verify that we actually have opened 2 apps in split.
- TransitionInfo.Change mainChild = null;
- TransitionInfo.Change sideChild = null;
- for (int iC = 0; iC < info.getChanges().size(); ++iC) {
- final TransitionInfo.Change change = info.getChanges().get(iC);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo == null || !taskInfo.hasParentTask()) continue;
- final @SplitScreen.StageType int stageType = getStageType(getStageOfTask(taskInfo));
- if (stageType == STAGE_TYPE_MAIN) {
- mainChild = change;
- } else if (stageType == STAGE_TYPE_SIDE) {
- sideChild = change;
- }
- }
- if (mainChild == null || sideChild == null) {
- throw new IllegalStateException("Launched 2 tasks in split, but didn't receive"
- + " 2 tasks in transition. Possibly one of them failed to launch");
- // TODO: fallback logic. Probably start a new transition to exit split before
- // applying anything here. Ideally consolidate with transition-merging.
- }
-
- // Update local states (before animating).
- setDividerVisibility(true);
- setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
- null /* wct */);
- setSplitsVisible(true);
-
- addDividerBarToTransition(info, t, true /* show */);
-
- // Make some noise if things aren't totally expected. These states shouldn't effect
- // transitions locally, but remotes (like Launcher) may get confused if they were
- // depending on listener callbacks. This can happen because task-organizer callbacks
- // aren't serialized with transition callbacks.
- // TODO(b/184679596): Find a way to either include task-org information in
- // the transition, or synchronize task-org callbacks.
- if (!mMainStage.containsTask(mainChild.getTaskInfo().taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + mMainStage
- + " to have been called with " + mainChild.getTaskInfo().taskId
- + " before startAnimation().");
- }
- if (!mSideStage.containsTask(sideChild.getTaskInfo().taskId)) {
- Log.w(TAG, "Expected onTaskAppeared on " + mSideStage
- + " to have been called with " + sideChild.getTaskInfo().taskId
- + " before startAnimation().");
- }
- return true;
- } else {
- // TODO: other entry method animations
- throw new RuntimeException("Unsupported split-entry");
- }
- }
-
- private boolean startPendingDismissAnimation(@NonNull IBinder transition,
- @NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction t) {
- // Make some noise if things aren't totally expected. These states shouldn't effect
- // transitions locally, but remotes (like Launcher) may get confused if they were
- // depending on listener callbacks. This can happen because task-organizer callbacks
- // aren't serialized with transition callbacks.
- // TODO(b/184679596): Find a way to either include task-org information in
- // the transition, or synchronize task-org callbacks.
- if (mMainStage.getChildCount() != 0) {
- final StringBuilder tasksLeft = new StringBuilder();
- for (int i = 0; i < mMainStage.getChildCount(); ++i) {
- tasksLeft.append(i != 0 ? ", " : "");
- tasksLeft.append(mMainStage.mChildrenTaskInfo.keyAt(i));
- }
- Log.w(TAG, "Expected onTaskVanished on " + mMainStage
- + " to have been called with [" + tasksLeft.toString()
- + "] before startAnimation().");
- }
- if (mSideStage.getChildCount() != 0) {
- final StringBuilder tasksLeft = new StringBuilder();
- for (int i = 0; i < mSideStage.getChildCount(); ++i) {
- tasksLeft.append(i != 0 ? ", " : "");
- tasksLeft.append(mSideStage.mChildrenTaskInfo.keyAt(i));
- }
- Log.w(TAG, "Expected onTaskVanished on " + mSideStage
- + " to have been called with [" + tasksLeft.toString()
- + "] before startAnimation().");
- }
-
- // Update local states.
- setSplitsVisible(false);
- // Wait until after animation to update divider
-
- if (info.getType() == TRANSIT_SPLIT_DISMISS_SNAP) {
- // Reset crops so they don't interfere with subsequent launches
- t.setWindowCrop(mMainStage.mRootLeash, null);
- t.setWindowCrop(mSideStage.mRootLeash, null);
- }
-
- if (mDismissTop == STAGE_TYPE_UNDEFINED) {
- // Going home (dismissing both splits)
-
- // TODO: Have a proper remote for this. Until then, though, reset state and use the
- // normal animation stuff (which falls back to the normal launcher remote).
- t.hide(mSplitLayout.getDividerLeash());
- setDividerVisibility(false);
- mSplitTransitions.mPendingDismiss = null;
- return false;
- }
-
- addDividerBarToTransition(info, t, false /* show */);
- // We're dismissing split by moving the other one to fullscreen.
- // Since we don't have any animations for this yet, just use the internal example
- // animations.
- return true;
- }
-
- private void addDividerBarToTransition(@NonNull TransitionInfo info,
- @NonNull SurfaceControl.Transaction t, boolean show) {
- final SurfaceControl leash = mSplitLayout.getDividerLeash();
- final TransitionInfo.Change barChange = new TransitionInfo.Change(null /* token */, leash);
- final Rect bounds = mSplitLayout.getDividerBounds();
- barChange.setStartAbsBounds(bounds);
- barChange.setEndAbsBounds(bounds);
- barChange.setMode(show ? TRANSIT_TO_FRONT : TRANSIT_TO_BACK);
- barChange.setFlags(FLAG_IS_DIVIDER_BAR);
- // Technically this should be order-0, but this is running after layer assignment
- // and it's a special case, so just add to end.
- info.addChange(barChange);
- // Be default, make it visible. The remote animator can adjust alpha if it plans to animate.
- if (show) {
- t.setAlpha(leash, 1.f);
- t.setLayer(leash, Integer.MAX_VALUE);
- t.setPosition(leash, bounds.left, bounds.top);
- t.show(leash);
- }
- }
-
- RemoteAnimationTarget getDividerBarLegacyTarget() {
- final Rect bounds = mSplitLayout.getDividerBounds();
- return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
- mSplitLayout.getDividerLeash(), false /* isTranslucent */, null /* clipRect */,
- null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
- new android.graphics.Point(0, 0) /* position */, bounds, bounds,
- new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
- null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
- }
-
- RemoteAnimationTarget getOutlineLegacyTarget() {
- final Rect bounds = mSideStage.mRootTaskInfo.configuration.windowConfiguration.getBounds();
- // Leverage TYPE_DOCK_DIVIDER type when wrapping outline remote animation target in order to
- // distinguish as a split auxiliary target in Launcher.
- return new RemoteAnimationTarget(-1 /* taskId */, -1 /* mode */,
- mSideStage.getOutlineLeash(), false /* isTranslucent */, null /* clipRect */,
- null /* contentInsets */, Integer.MAX_VALUE /* prefixOrderIndex */,
- new android.graphics.Point(0, 0) /* position */, bounds, bounds,
- new WindowConfiguration(), true, null /* startLeash */, null /* startBounds */,
- null /* taskInfo */, false /* allowEnterPip */, TYPE_DOCK_DIVIDER);
- }
-
- @Override
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + TAG + " mDisplayId=" + mDisplayId);
- pw.println(innerPrefix + "mDividerVisible=" + mDividerVisible);
- pw.println(innerPrefix + "MainStage");
- pw.println(childPrefix + "isActive=" + mMainStage.isActive());
- mMainStageListener.dump(pw, childPrefix);
- pw.println(innerPrefix + "SideStage");
- mSideStageListener.dump(pw, childPrefix);
- pw.println(innerPrefix + "mSplitLayout=" + mSplitLayout);
- }
-
- /**
- * Directly set the visibility of both splits. This assumes hasChildren matches visibility.
- * This is intended for batch use, so it assumes other state management logic is already
- * handled.
- */
- private void setSplitsVisible(boolean visible) {
- mMainStageListener.mVisible = mSideStageListener.mVisible = visible;
- mMainStageListener.mHasChildren = mSideStageListener.mHasChildren = visible;
- }
-
- /**
- * Sets drag info to be logged when splitscreen is next entered.
- */
- public void logOnDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
- mLogger.enterRequestedByDrag(position, dragSessionId);
- }
-
- /**
- * Logs the exit of splitscreen.
- */
- private void logExit(int exitReason) {
- mLogger.logExit(exitReason,
- SPLIT_POSITION_UNDEFINED, 0 /* mainStageUid */,
- SPLIT_POSITION_UNDEFINED, 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
- }
-
- /**
- * Logs the exit of splitscreen to a specific stage. This must be called before the exit is
- * executed.
- */
- private void logExitToStage(int exitReason, boolean toMainStage) {
- mLogger.logExit(exitReason,
- toMainStage ? getMainStagePosition() : SPLIT_POSITION_UNDEFINED,
- toMainStage ? mMainStage.getTopChildTaskUid() : 0 /* mainStageUid */,
- !toMainStage ? getSideStagePosition() : SPLIT_POSITION_UNDEFINED,
- !toMainStage ? mSideStage.getTopChildTaskUid() : 0 /* sideStageUid */,
- mSplitLayout.isLandscape());
- }
-
- class StageListenerImpl implements StageTaskListener.StageListenerCallbacks {
- boolean mHasRootTask = false;
- boolean mVisible = false;
- boolean mHasChildren = false;
-
- @Override
- public void onRootTaskAppeared() {
- mHasRootTask = true;
- StageCoordinator.this.onStageRootTaskAppeared(this);
- }
-
- @Override
- public void onStatusChanged(boolean visible, boolean hasChildren) {
- if (!mHasRootTask) return;
-
- if (mHasChildren != hasChildren) {
- mHasChildren = hasChildren;
- StageCoordinator.this.onStageHasChildrenChanged(this);
- }
- if (mVisible != visible) {
- mVisible = visible;
- StageCoordinator.this.onStageVisibilityChanged(this);
- }
- }
-
- @Override
- public void onChildTaskStatusChanged(int taskId, boolean present, boolean visible) {
- StageCoordinator.this.onStageChildTaskStatusChanged(this, taskId, present, visible);
- }
-
- @Override
- public void onRootTaskVanished() {
- reset();
- StageCoordinator.this.onStageRootTaskVanished(this);
- }
-
- @Override
- public void onNoLongerSupportMultiWindow() {
- if (mMainStage.isActive()) {
- StageCoordinator.this.exitSplitScreen(null /* childrenToTop */,
- SPLITSCREEN_UICHANGED__EXIT_REASON__APP_DOES_NOT_SUPPORT_MULTIWINDOW);
- }
- }
-
- private void reset() {
- mHasRootTask = false;
- mVisible = false;
- mHasChildren = false;
- }
-
- public void dump(@NonNull PrintWriter pw, String prefix) {
- pw.println(prefix + "mHasRootTask=" + mHasRootTask);
- pw.println(prefix + "mVisible=" + mVisible);
- pw.println(prefix + "mHasChildren=" + mHasChildren);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
deleted file mode 100644
index 7b679580fa87..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskListener.java
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-
-import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
-
-import android.annotation.CallSuper;
-import android.annotation.Nullable;
-import android.app.ActivityManager;
-import android.graphics.Point;
-import android.graphics.Rect;
-import android.util.SparseArray;
-import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-import android.window.WindowContainerTransaction;
-
-import androidx.annotation.NonNull;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.SurfaceUtils;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-import java.io.PrintWriter;
-
-/**
- * Base class that handle common task org. related for split-screen stages.
- * Note that this class and its sub-class do not directly perform hierarchy operations.
- * They only serve to hold a collection of tasks and provide APIs like
- * {@link #setBounds(Rect, WindowContainerTransaction)} for the centralized {@link StageCoordinator}
- * to perform operations in-sync with other containers.
- *
- * @see StageCoordinator
- */
-class StageTaskListener implements ShellTaskOrganizer.TaskListener {
- private static final String TAG = StageTaskListener.class.getSimpleName();
-
- protected static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
- protected static final int[] CONTROLLED_WINDOWING_MODES =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
- protected static final int[] CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE =
- {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED, WINDOWING_MODE_MULTI_WINDOW};
-
- /** Callback interface for listening to changes in a split-screen stage. */
- public interface StageListenerCallbacks {
- void onRootTaskAppeared();
-
- void onStatusChanged(boolean visible, boolean hasChildren);
-
- void onChildTaskStatusChanged(int taskId, boolean present, boolean visible);
-
- void onRootTaskVanished();
- void onNoLongerSupportMultiWindow();
- }
-
- private final StageListenerCallbacks mCallbacks;
- private final SurfaceSession mSurfaceSession;
- protected final SyncTransactionQueue mSyncQueue;
-
- protected ActivityManager.RunningTaskInfo mRootTaskInfo;
- protected SurfaceControl mRootLeash;
- protected SurfaceControl mDimLayer;
- protected SparseArray<ActivityManager.RunningTaskInfo> mChildrenTaskInfo = new SparseArray<>();
- private final SparseArray<SurfaceControl> mChildrenLeashes = new SparseArray<>();
-
- private final StageTaskUnfoldController mStageTaskUnfoldController;
-
- StageTaskListener(ShellTaskOrganizer taskOrganizer, int displayId,
- StageListenerCallbacks callbacks, SyncTransactionQueue syncQueue,
- SurfaceSession surfaceSession,
- @Nullable StageTaskUnfoldController stageTaskUnfoldController) {
- mCallbacks = callbacks;
- mSyncQueue = syncQueue;
- mSurfaceSession = surfaceSession;
- mStageTaskUnfoldController = stageTaskUnfoldController;
- taskOrganizer.createRootTask(displayId, WINDOWING_MODE_MULTI_WINDOW, this);
- }
-
- int getChildCount() {
- return mChildrenTaskInfo.size();
- }
-
- boolean containsTask(int taskId) {
- return mChildrenTaskInfo.contains(taskId);
- }
-
- /**
- * Returns the top activity uid for the top child task.
- */
- int getTopChildTaskUid() {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- final ActivityManager.RunningTaskInfo info = mChildrenTaskInfo.valueAt(i);
- if (info.topActivityInfo == null) {
- continue;
- }
- return info.topActivityInfo.applicationInfo.uid;
- }
- return 0;
- }
-
- /** @return {@code true} if this listener contains the currently focused task. */
- boolean isFocused() {
- if (mRootTaskInfo == null) {
- return false;
- }
-
- if (mRootTaskInfo.isFocused) {
- return true;
- }
-
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- if (mChildrenTaskInfo.valueAt(i).isFocused) {
- return true;
- }
- }
-
- return false;
- }
-
- @Override
- @CallSuper
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- if (mRootTaskInfo == null && !taskInfo.hasParentTask()) {
- mRootLeash = leash;
- mRootTaskInfo = taskInfo;
- mCallbacks.onRootTaskAppeared();
- sendStatusChanged();
- mSyncQueue.runInSync(t -> {
- t.hide(mRootLeash);
- mDimLayer =
- SurfaceUtils.makeDimLayer(t, mRootLeash, "Dim layer", mSurfaceSession);
- });
- } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
- final int taskId = taskInfo.taskId;
- mChildrenLeashes.put(taskId, leash);
- mChildrenTaskInfo.put(taskId, taskInfo);
- updateChildTaskSurface(taskInfo, leash, true /* firstAppeared */);
- mCallbacks.onChildTaskStatusChanged(taskId, true /* present */, taskInfo.isVisible);
- if (ENABLE_SHELL_TRANSITIONS) {
- // Status is managed/synchronized by the transition lifecycle.
- return;
- }
- sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
- }
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskAppeared(taskInfo, leash);
- }
- }
-
- @Override
- @CallSuper
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.supportsMultiWindow) {
- // Leave split screen if the task no longer supports multi window.
- mCallbacks.onNoLongerSupportMultiWindow();
- return;
- }
- if (mRootTaskInfo.taskId == taskInfo.taskId) {
- mRootTaskInfo = taskInfo;
- } else if (taskInfo.parentTaskId == mRootTaskInfo.taskId) {
- mChildrenTaskInfo.put(taskInfo.taskId, taskInfo);
- mCallbacks.onChildTaskStatusChanged(taskInfo.taskId, true /* present */,
- taskInfo.isVisible);
- if (!ENABLE_SHELL_TRANSITIONS) {
- updateChildTaskSurface(
- taskInfo, mChildrenLeashes.get(taskInfo.taskId), false /* firstAppeared */);
- }
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
- }
- if (ENABLE_SHELL_TRANSITIONS) {
- // Status is managed/synchronized by the transition lifecycle.
- return;
- }
- sendStatusChanged();
- }
-
- @Override
- @CallSuper
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- final int taskId = taskInfo.taskId;
- if (mRootTaskInfo.taskId == taskId) {
- mCallbacks.onRootTaskVanished();
- mSyncQueue.runInSync(t -> t.remove(mDimLayer));
- mRootTaskInfo = null;
- } else if (mChildrenTaskInfo.contains(taskId)) {
- mChildrenTaskInfo.remove(taskId);
- mChildrenLeashes.remove(taskId);
- mCallbacks.onChildTaskStatusChanged(taskId, false /* present */, taskInfo.isVisible);
- if (ENABLE_SHELL_TRANSITIONS) {
- // Status is managed/synchronized by the transition lifecycle.
- return;
- }
- sendStatusChanged();
- } else {
- throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo
- + "\n mRootTaskInfo: " + mRootTaskInfo);
- }
-
- if (mStageTaskUnfoldController != null) {
- mStageTaskUnfoldController.onTaskVanished(taskInfo);
- }
- }
-
- @Override
- public void attachChildSurfaceToTask(int taskId, SurfaceControl.Builder b) {
- b.setParent(findTaskSurface(taskId));
- }
-
- @Override
- public void reparentChildSurfaceToTask(int taskId, SurfaceControl sc,
- SurfaceControl.Transaction t) {
- t.reparent(sc, findTaskSurface(taskId));
- }
-
- private SurfaceControl findTaskSurface(int taskId) {
- if (mRootTaskInfo.taskId == taskId) {
- return mRootLeash;
- } else if (mChildrenLeashes.contains(taskId)) {
- return mChildrenLeashes.get(taskId);
- } else {
- throw new IllegalArgumentException("There is no surface for taskId=" + taskId);
- }
- }
-
- void setBounds(Rect bounds, WindowContainerTransaction wct) {
- wct.setBounds(mRootTaskInfo.token, bounds);
- }
-
- void reorderChild(int taskId, boolean onTop, WindowContainerTransaction wct) {
- if (!containsTask(taskId)) {
- return;
- }
- wct.reorder(mChildrenTaskInfo.get(taskId).token, onTop /* onTop */);
- }
-
- void setVisibility(boolean visible, WindowContainerTransaction wct) {
- wct.reorder(mRootTaskInfo.token, visible /* onTop */);
- }
-
- void onSplitScreenListenerRegistered(SplitScreen.SplitScreenListener listener,
- @SplitScreen.StageType int stage) {
- for (int i = mChildrenTaskInfo.size() - 1; i >= 0; --i) {
- int taskId = mChildrenTaskInfo.keyAt(i);
- listener.onTaskStageChanged(taskId, stage,
- mChildrenTaskInfo.get(taskId).isVisible);
- }
- }
-
- private void updateChildTaskSurface(ActivityManager.RunningTaskInfo taskInfo,
- SurfaceControl leash, boolean firstAppeared) {
- final Point taskPositionInParent = taskInfo.positionInParent;
- mSyncQueue.runInSync(t -> {
- t.setWindowCrop(leash, null);
- t.setPosition(leash, taskPositionInParent.x, taskPositionInParent.y);
- if (firstAppeared && !ENABLE_SHELL_TRANSITIONS) {
- t.setAlpha(leash, 1f);
- t.setMatrix(leash, 1, 0, 0, 1);
- t.show(leash);
- }
- });
- }
-
- private void sendStatusChanged() {
- mCallbacks.onStatusChanged(mRootTaskInfo.isVisible, mChildrenTaskInfo.size() > 0);
- }
-
- @Override
- @CallSuper
- public void dump(@NonNull PrintWriter pw, String prefix) {
- final String innerPrefix = prefix + " ";
- final String childPrefix = innerPrefix + " ";
- pw.println(prefix + this);
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
deleted file mode 100644
index 62b9da6d4715..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/StageTaskUnfoldController.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.stagesplit;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import android.animation.RectEvaluator;
-import android.animation.TypeEvaluator;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.SparseArray;
-import android.view.InsetsSource;
-import android.view.InsetsState;
-import android.view.SurfaceControl;
-
-import com.android.internal.policy.ScreenDecorationsUtils;
-import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
-import com.android.wm.shell.unfold.UnfoldBackgroundController;
-
-import java.util.concurrent.Executor;
-
-/**
- * Controls transformations of the split screen task surfaces in response
- * to the unfolding/folding action on foldable devices
- */
-public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
-
- private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
- private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
-
- private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
- private final DisplayInsetsController mDisplayInsetsController;
- private final UnfoldBackgroundController mBackgroundController;
- private final Executor mExecutor;
- private final int mExpandedTaskBarHeight;
- private final float mWindowCornerRadiusPx;
- private final Rect mStageBounds = new Rect();
- private final TransactionPool mTransactionPool;
-
- private InsetsSource mTaskbarInsetsSource;
- private boolean mBothStagesVisible;
-
- public StageTaskUnfoldController(@NonNull Context context,
- @NonNull TransactionPool transactionPool,
- @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
- @NonNull DisplayInsetsController displayInsetsController,
- @NonNull UnfoldBackgroundController backgroundController,
- @NonNull Executor executor) {
- mUnfoldProgressProvider = unfoldProgressProvider;
- mTransactionPool = transactionPool;
- mExecutor = executor;
- mBackgroundController = backgroundController;
- mDisplayInsetsController = displayInsetsController;
- mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
- mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
- com.android.internal.R.dimen.taskbar_frame_height);
- }
-
- /**
- * Initializes the controller, starts listening for the external events
- */
- public void init() {
- mUnfoldProgressProvider.addListener(mExecutor, this);
- mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
- }
-
- @Override
- public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update();
- }
- }
-
- /**
- * Called when split screen task appeared
- * @param taskInfo info for the appeared task
- * @param leash surface leash for the appeared task
- */
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- AnimationContext context = new AnimationContext(leash);
- mAnimationContextByTaskId.put(taskInfo.taskId, context);
- }
-
- /**
- * Called when a split screen task vanished
- * @param taskInfo info for the vanished task
- */
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
- if (context != null) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- resetSurface(transaction, context);
- transaction.apply();
- mTransactionPool.release(transaction);
- }
- mAnimationContextByTaskId.remove(taskInfo.taskId);
- }
-
- @Override
- public void onStateChangeProgress(float progress) {
- if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
-
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- mBackgroundController.ensureBackground(transaction);
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- AnimationContext context = mAnimationContextByTaskId.valueAt(i);
-
- context.mCurrentCropRect.set(RECT_EVALUATOR
- .evaluate(progress, context.mStartCropRect, context.mEndCropRect));
-
- transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
- .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
- }
-
- transaction.apply();
-
- mTransactionPool.release(transaction);
- }
-
- @Override
- public void onStateChangeFinished() {
- resetTransformations();
- }
-
- /**
- * Called when split screen visibility changes
- * @param bothStagesVisible true if both stages of the split screen are visible
- */
- public void onSplitVisibilityChanged(boolean bothStagesVisible) {
- mBothStagesVisible = bothStagesVisible;
- if (!bothStagesVisible) {
- resetTransformations();
- }
- }
-
- /**
- * Called when split screen stage bounds changed
- * @param bounds new bounds for this stage
- */
- public void onLayoutChanged(Rect bounds) {
- mStageBounds.set(bounds);
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update();
- }
- }
-
- private void resetTransformations() {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- resetSurface(transaction, context);
- }
- mBackgroundController.removeBackground(transaction);
- transaction.apply();
-
- mTransactionPool.release(transaction);
- }
-
- private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
- transaction
- .setWindowCrop(context.mLeash, null)
- .setCornerRadius(context.mLeash, 0.0F);
- }
-
- private class AnimationContext {
- final SurfaceControl mLeash;
- final Rect mStartCropRect = new Rect();
- final Rect mEndCropRect = new Rect();
- final Rect mCurrentCropRect = new Rect();
-
- private AnimationContext(SurfaceControl leash) {
- this.mLeash = leash;
- update();
- }
-
- private void update() {
- mStartCropRect.set(mStageBounds);
-
- if (mTaskbarInsetsSource != null) {
- // Only insets the cropping window with taskbar when taskbar is expanded
- if (mTaskbarInsetsSource.getFrame().height() >= mExpandedTaskBarHeight) {
- mStartCropRect.inset(mTaskbarInsetsSource
- .calculateVisibleInsets(mStartCropRect));
- }
- }
-
- // Offset to surface coordinates as layout bounds are in screen coordinates
- mStartCropRect.offsetTo(0, 0);
-
- mEndCropRect.set(mStartCropRect);
-
- int maxSize = Math.max(mEndCropRect.width(), mEndCropRect.height());
- int margin = (int) (maxSize * CROPPING_START_MARGIN_FRACTION);
- mStartCropRect.inset(margin, margin, margin, margin);
- }
- }
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 54d62edf2570..a0e176c7ea68 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -261,7 +261,8 @@ public class StartingSurfaceDrawer {
WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);
params.setFitInsetsSides(0);
params.setFitInsetsTypes(0);
- params.format = PixelFormat.TRANSLUCENT;
+ params.format = suggestType == STARTING_WINDOW_TYPE_LEGACY_SPLASH_SCREEN
+ ? PixelFormat.OPAQUE : PixelFormat.TRANSLUCENT;
int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
| WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
index 95bc579a4a51..19d3acbf28d4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/TaskSnapshotWindow.java
@@ -20,10 +20,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static android.graphics.Color.WHITE;
import static android.graphics.Color.alpha;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
-import static android.view.ViewRootImpl.LOCAL_LAYOUT;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
-import static android.view.WindowLayout.UNSPECIFIED_LENGTH;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
@@ -53,7 +51,6 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.ActivityThread;
-import android.app.WindowConfiguration;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
@@ -80,7 +77,6 @@ import android.view.SurfaceSession;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowLayout;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import android.window.ClientWindowFrames;
@@ -212,8 +208,6 @@ public class TaskSnapshotWindow {
final IWindowSession session = WindowManagerGlobal.getWindowSession();
final SurfaceControl surfaceControl = new SurfaceControl();
final ClientWindowFrames tmpFrames = new ClientWindowFrames();
- final WindowLayout windowLayout = new WindowLayout();
- final Rect displayCutoutSafe = new Rect();
final InsetsSourceControl[] tmpControls = new InsetsSourceControl[0];
final MergedConfiguration tmpMergedConfiguration = new MergedConfiguration();
@@ -238,7 +232,8 @@ public class TaskSnapshotWindow {
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#addToDisplay");
final int res = session.addToDisplay(window, layoutParams, View.GONE, displayId,
- info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls);
+ info.requestedVisibilities, tmpInputChannel, tmpInsetsState, tmpControls,
+ new Rect());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
if (res < 0) {
Slog.w(TAG, "Failed to add snapshot starting window res=" + res);
@@ -250,25 +245,9 @@ public class TaskSnapshotWindow {
window.setOuter(snapshotSurface);
try {
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "TaskSnapshot#relayout");
- if (LOCAL_LAYOUT) {
- if (!surfaceControl.isValid()) {
- session.updateVisibility(window, layoutParams, View.VISIBLE,
- tmpMergedConfiguration, surfaceControl, tmpInsetsState, tmpControls);
- }
- tmpInsetsState.getDisplayCutoutSafe(displayCutoutSafe);
- final WindowConfiguration winConfig =
- tmpMergedConfiguration.getMergedConfiguration().windowConfiguration;
- windowLayout.computeFrames(layoutParams, tmpInsetsState, displayCutoutSafe,
- winConfig.getBounds(), winConfig.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, info.requestedVisibilities,
- null /* attachedWindowFrame */, 1f /* compatScale */, tmpFrames);
- session.updateLayout(window, layoutParams, 0 /* flags */, tmpFrames,
- UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH);
- } else {
- session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
- tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
- tmpControls, new Bundle());
- }
+ session.relayout(window, layoutParams, -1, -1, View.VISIBLE, 0,
+ tmpFrames, tmpMergedConfiguration, surfaceControl, tmpInsetsState,
+ tmpControls, new Bundle());
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
} catch (RemoteException e) {
snapshotSurface.clearWindowSynced();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java
new file mode 100644
index 000000000000..2fca8f0ecc76
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ConfigurationChangeListener.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.content.res.Configuration;
+
+/**
+ * Callbacks for when the configuration changes.
+ */
+public interface ConfigurationChangeListener {
+
+ /**
+ * Called when a configuration changes. This precedes all the following callbacks.
+ */
+ default void onConfigurationChanged(Configuration newConfiguration) {}
+
+ /**
+ * Convenience method to the above, called when the density or font scale changes.
+ */
+ default void onDensityOrFontScaleChanged() {}
+
+ /**
+ * Convenience method to the above, called when the smallest screen width changes.
+ */
+ default void onSmallestScreenWidthChanged() {}
+
+ /**
+ * Convenience method to the above, called when the system theme changes, including dark/light
+ * UI_MODE changes.
+ */
+ default void onThemeChanged() {}
+
+ /**
+ * Convenience method to the above, called when the local list or layout direction changes.
+ */
+ default void onLocaleOrLayoutDirectionChanged() {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
new file mode 100644
index 000000000000..1c0b35894acd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/KeyguardChangeListener.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+/**
+ * Callbacks for when the keyguard changes.
+ */
+public interface KeyguardChangeListener {
+ /**
+ * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded).
+ */
+ default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {}
+
+ /**
+ * Notifies the Shell when the keyguard dismiss animation has finished.
+ *
+ * TODO(b/206741900) deprecate this path once we're able to animate the PiP window as part of
+ * keyguard dismiss animation.
+ */
+ default void onKeyguardDismissAnimationFinished() {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
index 06f4367752fb..0427efb30b9f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandlerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellCommandHandler.java
@@ -14,15 +14,14 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.sysui;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import com.android.wm.shell.apppairs.AppPairsController;
+import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.hidedisplaycutout.HideDisplayCutoutController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController;
import com.android.wm.shell.onehanded.OneHandedController;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasksController;
@@ -36,62 +35,50 @@ import java.util.Optional;
*
* Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
*/
-public final class ShellCommandHandlerImpl {
- private static final String TAG = ShellCommandHandlerImpl.class.getSimpleName();
+public final class ShellCommandHandler {
+ private static final String TAG = ShellCommandHandler.class.getSimpleName();
- private final Optional<LegacySplitScreenController> mLegacySplitScreenOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
private final Optional<Pip> mPipOptional;
private final Optional<OneHandedController> mOneHandedOptional;
private final Optional<HideDisplayCutoutController> mHideDisplayCutout;
- private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<RecentTasksController> mRecentTasks;
private final ShellTaskOrganizer mShellTaskOrganizer;
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final ShellExecutor mMainExecutor;
- private final HandlerImpl mImpl = new HandlerImpl();
- public ShellCommandHandlerImpl(
+ public ShellCommandHandler(
+ ShellController shellController,
ShellTaskOrganizer shellTaskOrganizer,
KidsModeTaskOrganizer kidsModeTaskOrganizer,
- Optional<LegacySplitScreenController> legacySplitScreenOptional,
Optional<SplitScreenController> splitScreenOptional,
Optional<Pip> pipOptional,
Optional<OneHandedController> oneHandedOptional,
Optional<HideDisplayCutoutController> hideDisplayCutout,
- Optional<AppPairsController> appPairsOptional,
Optional<RecentTasksController> recentTasks,
ShellExecutor mainExecutor) {
mShellTaskOrganizer = shellTaskOrganizer;
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mRecentTasks = recentTasks;
- mLegacySplitScreenOptional = legacySplitScreenOptional;
mSplitScreenOptional = splitScreenOptional;
mPipOptional = pipOptional;
mOneHandedOptional = oneHandedOptional;
mHideDisplayCutout = hideDisplayCutout;
- mAppPairsOptional = appPairsOptional;
mMainExecutor = mainExecutor;
- }
-
- public ShellCommandHandler asShellCommandHandler() {
- return mImpl;
+ // TODO(238217847): To be removed once the command handler dependencies are inverted
+ shellController.setShellCommandHandler(this);
}
/** Dumps WM Shell internal state. */
- private void dump(PrintWriter pw) {
+ public void dump(PrintWriter pw) {
mShellTaskOrganizer.dump(pw, "");
pw.println();
pw.println();
mPipOptional.ifPresent(pip -> pip.dump(pw));
- mLegacySplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw));
mOneHandedOptional.ifPresent(oneHanded -> oneHanded.dump(pw));
mHideDisplayCutout.ifPresent(hideDisplayCutout -> hideDisplayCutout.dump(pw));
pw.println();
pw.println();
- mAppPairsOptional.ifPresent(appPairs -> appPairs.dump(pw, ""));
- pw.println();
- pw.println();
mSplitScreenOptional.ifPresent(splitScreen -> splitScreen.dump(pw, ""));
pw.println();
pw.println();
@@ -103,16 +90,12 @@ public final class ShellCommandHandlerImpl {
/** Returns {@code true} if command was found and executed. */
- private boolean handleCommand(final String[] args, PrintWriter pw) {
+ public boolean handleCommand(final String[] args, PrintWriter pw) {
if (args.length < 2) {
// Argument at position 0 is "WMShell".
return false;
}
switch (args[1]) {
- case "pair":
- return runPair(args, pw);
- case "unpair":
- return runUnpair(args, pw);
case "moveToSideStage":
return runMoveToSideStage(args, pw);
case "removeFromSideStage":
@@ -126,29 +109,6 @@ public final class ShellCommandHandlerImpl {
}
}
- private boolean runPair(String[] args, PrintWriter pw) {
- if (args.length < 4) {
- // First two arguments are "WMShell" and command name.
- pw.println("Error: two task ids should be provided as arguments");
- return false;
- }
- final int taskId1 = new Integer(args[2]);
- final int taskId2 = new Integer(args[3]);
- mAppPairsOptional.ifPresent(appPairs -> appPairs.pair(taskId1, taskId2));
- return true;
- }
-
- private boolean runUnpair(String[] args, PrintWriter pw) {
- if (args.length < 3) {
- // First two arguments are "WMShell" and command name.
- pw.println("Error: task id should be provided as an argument");
- return false;
- }
- final int taskId = new Integer(args[2]);
- mAppPairsOptional.ifPresent(appPairs -> appPairs.unpair(taskId));
- return true;
- }
-
private boolean runMoveToSideStage(String[] args, PrintWriter pw) {
if (args.length < 3) {
// First arguments are "WMShell" and command name.
@@ -203,28 +163,4 @@ public final class ShellCommandHandlerImpl {
pw.println(" Sets the position of the side-stage.");
return true;
}
-
- private class HandlerImpl implements ShellCommandHandler {
- @Override
- public void dump(PrintWriter pw) {
- try {
- mMainExecutor.executeBlocking(() -> ShellCommandHandlerImpl.this.dump(pw));
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to dump the Shell in 2s", e);
- }
- }
-
- @Override
- public boolean handleCommand(String[] args, PrintWriter pw) {
- try {
- boolean[] result = new boolean[1];
- mMainExecutor.executeBlocking(() -> {
- result[0] = ShellCommandHandlerImpl.this.handleCommand(args, pw);
- });
- return result[0];
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to handle Shell command in 2s", e);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
new file mode 100644
index 000000000000..618028c1544f
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellController.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
+import static android.content.pm.ActivityInfo.CONFIG_FONT_SCALE;
+import static android.content.pm.ActivityInfo.CONFIG_LAYOUT_DIRECTION;
+import static android.content.pm.ActivityInfo.CONFIG_LOCALE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SYSUI_EVENTS;
+
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ExternalThread;
+
+import java.io.PrintWriter;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Handles event callbacks from SysUI that can be used within the Shell.
+ */
+public class ShellController {
+ private static final String TAG = ShellController.class.getSimpleName();
+
+ private final ShellExecutor mMainExecutor;
+ private final ShellInterfaceImpl mImpl = new ShellInterfaceImpl();
+
+ private ShellInit mShellInit;
+ private ShellCommandHandler mShellCommandHandler;
+
+ private final CopyOnWriteArrayList<ConfigurationChangeListener> mConfigChangeListeners =
+ new CopyOnWriteArrayList<>();
+ private final CopyOnWriteArrayList<KeyguardChangeListener> mKeyguardChangeListeners =
+ new CopyOnWriteArrayList<>();
+ private Configuration mLastConfiguration;
+
+
+ public ShellController(ShellExecutor mainExecutor) {
+ mMainExecutor = mainExecutor;
+ }
+
+ /**
+ * Returns the external interface to this controller.
+ */
+ public ShellInterface asShell() {
+ return mImpl;
+ }
+
+ /**
+ * Sets the init handler to call back to.
+ * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
+ * init handler to other classes.
+ */
+ public void setShellInit(ShellInit shellInit) {
+ mShellInit = shellInit;
+ }
+
+ /**
+ * Sets the command handler to call back to.
+ * TODO(238217847): This is only exposed this way until we can remove the dependencies from the
+ * command handler to other classes.
+ */
+ public void setShellCommandHandler(ShellCommandHandler shellCommandHandler) {
+ mShellCommandHandler = shellCommandHandler;
+ }
+
+ /**
+ * Adds a new configuration listener. The configuration change callbacks are not made in any
+ * particular order.
+ */
+ public void addConfigurationChangeListener(ConfigurationChangeListener listener) {
+ mConfigChangeListeners.remove(listener);
+ mConfigChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes an existing configuration listener.
+ */
+ public void removeConfigurationChangeListener(ConfigurationChangeListener listener) {
+ mConfigChangeListeners.remove(listener);
+ }
+
+ /**
+ * Adds a new Keyguard listener. The Keyguard change callbacks are not made in any
+ * particular order.
+ */
+ public void addKeyguardChangeListener(KeyguardChangeListener listener) {
+ mKeyguardChangeListeners.remove(listener);
+ mKeyguardChangeListeners.add(listener);
+ }
+
+ /**
+ * Removes an existing Keyguard listener.
+ */
+ public void removeKeyguardChangeListener(KeyguardChangeListener listener) {
+ mKeyguardChangeListeners.remove(listener);
+ }
+
+ @VisibleForTesting
+ void onConfigurationChanged(Configuration newConfig) {
+ // The initial config is send on startup and doesn't trigger listener callbacks
+ if (mLastConfiguration == null) {
+ mLastConfiguration = new Configuration(newConfig);
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "Initial Configuration: %s", newConfig);
+ return;
+ }
+
+ final int diff = newConfig.diff(mLastConfiguration);
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "New configuration change: %s", newConfig);
+ ProtoLog.v(WM_SHELL_SYSUI_EVENTS, "\tchanges=%s",
+ Configuration.configurationDiffToString(diff));
+ final boolean densityFontScaleChanged = (diff & CONFIG_FONT_SCALE) != 0
+ || (diff & ActivityInfo.CONFIG_DENSITY) != 0;
+ final boolean smallestScreenWidthChanged = (diff & CONFIG_SMALLEST_SCREEN_SIZE) != 0;
+ final boolean themeChanged = (diff & CONFIG_ASSETS_PATHS) != 0
+ || (diff & CONFIG_UI_MODE) != 0;
+ final boolean localOrLayoutDirectionChanged = (diff & CONFIG_LOCALE) != 0
+ || (diff & CONFIG_LAYOUT_DIRECTION) != 0;
+
+ // Update the last configuration and call listeners
+ mLastConfiguration.updateFrom(newConfig);
+ for (ConfigurationChangeListener listener : mConfigChangeListeners) {
+ listener.onConfigurationChanged(newConfig);
+ if (densityFontScaleChanged) {
+ listener.onDensityOrFontScaleChanged();
+ }
+ if (smallestScreenWidthChanged) {
+ listener.onSmallestScreenWidthChanged();
+ }
+ if (themeChanged) {
+ listener.onThemeChanged();
+ }
+ if (localOrLayoutDirectionChanged) {
+ listener.onLocaleOrLayoutDirectionChanged();
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void onKeyguardVisibilityChanged(boolean visible, boolean occluded, boolean animatingDismiss) {
+ for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+ listener.onKeyguardVisibilityChanged(visible, occluded, animatingDismiss);
+ }
+ }
+
+ @VisibleForTesting
+ void onKeyguardDismissAnimationFinished() {
+ for (KeyguardChangeListener listener : mKeyguardChangeListeners) {
+ listener.onKeyguardDismissAnimationFinished();
+ }
+ }
+
+ public void dump(@NonNull PrintWriter pw, String prefix) {
+ final String innerPrefix = prefix + " ";
+ pw.println(prefix + TAG);
+ pw.println(innerPrefix + "mConfigChangeListeners=" + mConfigChangeListeners.size());
+ pw.println(innerPrefix + "mLastConfiguration=" + mLastConfiguration);
+ pw.println(innerPrefix + "mKeyguardChangeListeners=" + mKeyguardChangeListeners.size());
+ }
+
+ /**
+ * The interface for calls from outside the Shell, within the host process.
+ */
+ @ExternalThread
+ private class ShellInterfaceImpl implements ShellInterface {
+
+ @Override
+ public void onInit() {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellInit.init());
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to initialize the Shell in 2s", e);
+ }
+ }
+
+ @Override
+ public void dump(PrintWriter pw) {
+ try {
+ mMainExecutor.executeBlocking(() -> mShellCommandHandler.dump(pw));
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to dump the Shell in 2s", e);
+ }
+ }
+
+ @Override
+ public boolean handleCommand(String[] args, PrintWriter pw) {
+ try {
+ boolean[] result = new boolean[1];
+ mMainExecutor.executeBlocking(() -> {
+ result[0] = mShellCommandHandler.handleCommand(args, pw);
+ });
+ return result[0];
+ } catch (InterruptedException e) {
+ throw new RuntimeException("Failed to handle Shell command in 2s", e);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ mMainExecutor.execute(() ->
+ ShellController.this.onConfigurationChanged(newConfiguration));
+ }
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ mMainExecutor.execute(() ->
+ ShellController.this.onKeyguardVisibilityChanged(visible, occluded,
+ animatingDismiss));
+ }
+
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ mMainExecutor.execute(() ->
+ ShellController.this.onKeyguardDismissAnimationFinished());
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 62fb840d29d1..2619b37b67d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -14,36 +14,47 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.sysui;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_INIT;
-import com.android.wm.shell.apppairs.AppPairsController;
+import android.os.Build;
+import android.os.SystemClock;
+import android.util.Pair;
+
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
import com.android.wm.shell.bubbles.BubbleController;
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.ShellExecutor;
-import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.freeform.FreeformTaskListener;
import com.android.wm.shell.fullscreen.FullscreenTaskListener;
-import com.android.wm.shell.fullscreen.FullscreenUnfoldController;
import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
import com.android.wm.shell.pip.phone.PipTouchHandler;
import com.android.wm.shell.recents.RecentTasksController;
import com.android.wm.shell.splitscreen.SplitScreenController;
import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.transition.DefaultMixedHandler;
import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+import java.util.ArrayList;
import java.util.Optional;
/**
- * The entry point implementation into the shell for initializing shell internal state.
+ * The entry point implementation into the shell for initializing shell internal state. Classes
+ * which need to setup on start should inject an instance of this class and add an init callback.
*/
-public class ShellInitImpl {
- private static final String TAG = ShellInitImpl.class.getSimpleName();
+public class ShellInit {
+ private static final String TAG = ShellInit.class.getSimpleName();
private final DisplayController mDisplayController;
private final DisplayImeController mDisplayImeController;
@@ -53,20 +64,23 @@ public class ShellInitImpl {
private final KidsModeTaskOrganizer mKidsModeTaskOrganizer;
private final Optional<BubbleController> mBubblesOptional;
private final Optional<SplitScreenController> mSplitScreenOptional;
- private final Optional<AppPairsController> mAppPairsOptional;
private final Optional<PipTouchHandler> mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
- private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
+ private final Optional<UnfoldAnimationController> mUnfoldController;
private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
- private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
+ private final Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
private final StartingWindowController mStartingWindow;
private final Optional<RecentTasksController> mRecentTasks;
+ private final Optional<ActivityEmbeddingController> mActivityEmbeddingOptional;
- private final InitImpl mImpl = new InitImpl();
+ // An ordered list of init callbacks to be made once shell is first started
+ private final ArrayList<Pair<String, Runnable>> mInitCallbacks = new ArrayList<>();
+ private boolean mHasInitialized;
- public ShellInitImpl(
+ public ShellInit(
+ ShellController shellController,
DisplayController displayController,
DisplayImeController displayImeController,
DisplayInsetsController displayInsetsController,
@@ -75,13 +89,13 @@ public class ShellInitImpl {
KidsModeTaskOrganizer kidsModeTaskOrganizer,
Optional<BubbleController> bubblesOptional,
Optional<SplitScreenController> splitScreenOptional,
- Optional<AppPairsController> appPairsOptional,
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
- Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
+ Optional<UnfoldAnimationController> unfoldAnimationController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
- Optional<FreeformTaskListener> freeformTaskListenerOptional,
+ Optional<FreeformTaskListener<?>> freeformTaskListenerOptional,
Optional<RecentTasksController> recentTasks,
+ Optional<ActivityEmbeddingController> activityEmbeddingOptional,
Transitions transitions,
StartingWindowController startingWindow,
ShellExecutor mainExecutor) {
@@ -93,23 +107,21 @@ public class ShellInitImpl {
mKidsModeTaskOrganizer = kidsModeTaskOrganizer;
mBubblesOptional = bubblesOptional;
mSplitScreenOptional = splitScreenOptional;
- mAppPairsOptional = appPairsOptional;
mFullscreenTaskListener = fullscreenTaskListener;
mPipTouchHandlerOptional = pipTouchHandlerOptional;
- mFullscreenUnfoldController = fullscreenUnfoldTransitionController;
+ mUnfoldController = unfoldAnimationController;
mUnfoldTransitionHandler = unfoldTransitionHandler;
mFreeformTaskListenerOptional = freeformTaskListenerOptional;
mRecentTasks = recentTasks;
+ mActivityEmbeddingOptional = activityEmbeddingOptional;
mTransitions = transitions;
mMainExecutor = mainExecutor;
mStartingWindow = startingWindow;
+ // TODO(238217847): To be removed once the init dependencies are inverted
+ shellController.setShellInit(this);
}
- public ShellInit asShellInit() {
- return mImpl;
- }
-
- private void init() {
+ private void legacyInit() {
// Start listening for display and insets changes
mDisplayController.initialize();
mDisplayInsetsController.initialize();
@@ -121,7 +133,6 @@ public class ShellInitImpl {
mShellTaskOrganizer.initStartingWindow(mStartingWindow);
mShellTaskOrganizer.registerOrganizer();
- mAppPairsOptional.ifPresent(AppPairsController::onOrganizerRegistered);
mSplitScreenOptional.ifPresent(SplitScreenController::onOrganizerRegistered);
mBubblesOptional.ifPresent(BubbleController::initialize);
@@ -130,7 +141,15 @@ public class ShellInitImpl {
if (Transitions.ENABLE_SHELL_TRANSITIONS) {
mTransitions.register(mShellTaskOrganizer);
+ mActivityEmbeddingOptional.ifPresent(ActivityEmbeddingController::init);
mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init);
+ if (mSplitScreenOptional.isPresent() && mPipTouchHandlerOptional.isPresent()) {
+ final DefaultMixedHandler mixedHandler = new DefaultMixedHandler(mTransitions,
+ mPipTouchHandlerOptional.get().getTransitionHandler(),
+ mSplitScreenOptional.get().getTransitionHandler());
+ // Added at end so that it has highest priority.
+ mTransitions.addHandler(mixedHandler);
+ }
}
// TODO(b/181599115): This should really be the pip controller, but until we can provide the
@@ -143,22 +162,50 @@ public class ShellInitImpl {
mShellTaskOrganizer.addListenerForType(
f, ShellTaskOrganizer.TASK_LISTENER_TYPE_FREEFORM));
- mFullscreenUnfoldController.ifPresent(FullscreenUnfoldController::init);
+ mUnfoldController.ifPresent(UnfoldAnimationController::init);
mRecentTasks.ifPresent(RecentTasksController::init);
// Initialize kids mode task organizer
mKidsModeTaskOrganizer.initialize(mStartingWindow);
}
- @ExternalThread
- private class InitImpl implements ShellInit {
- @Override
- public void init() {
- try {
- mMainExecutor.executeBlocking(() -> ShellInitImpl.this.init());
- } catch (InterruptedException e) {
- throw new RuntimeException("Failed to initialize the Shell in 2s", e);
+ /**
+ * Adds a callback to the ordered list of callbacks be made when Shell is first started. This
+ * can be used in class constructors when dagger is used to ensure that the initialization order
+ * matches the dependency order.
+ */
+ public <T extends Object> void addInitCallback(Runnable r, T instance) {
+ if (mHasInitialized) {
+ if (Build.isDebuggable()) {
+ // All callbacks must be added prior to the Shell being initialized
+ throw new IllegalArgumentException("Can not add callback after init");
}
+ return;
}
+ final String className = instance.getClass().getSimpleName();
+ mInitCallbacks.add(new Pair<>(className, r));
+ ProtoLog.v(WM_SHELL_INIT, "Adding init callback for %s", className);
+ }
+
+ /**
+ * Calls all the init callbacks when the Shell is first starting.
+ */
+ @VisibleForTesting
+ public void init() {
+ ProtoLog.v(WM_SHELL_INIT, "Initializing Shell Components: %d", mInitCallbacks.size());
+ // Init in order of registration
+ for (int i = 0; i < mInitCallbacks.size(); i++) {
+ final Pair<String, Runnable> info = mInitCallbacks.get(i);
+ final long t1 = SystemClock.uptimeMillis();
+ info.second.run();
+ final long t2 = SystemClock.uptimeMillis();
+ ProtoLog.v(WM_SHELL_INIT, "\t%s took %dms", info.first, (t2 - t1));
+ }
+ mInitCallbacks.clear();
+
+ // TODO: To be removed
+ legacyInit();
+
+ mHasInitialized = true;
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
new file mode 100644
index 000000000000..254c253b0042
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInterface.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import android.content.res.Configuration;
+
+import java.io.PrintWriter;
+
+/**
+ * General interface for notifying the Shell of common SysUI events like configuration or keyguard
+ * changes.
+ */
+public interface ShellInterface {
+
+ /**
+ * Initializes the shell state.
+ */
+ default void onInit() {}
+
+ /**
+ * Dumps the shell state.
+ */
+ default void dump(PrintWriter pw) {}
+
+ /**
+ * Handles a shell command.
+ */
+ default boolean handleCommand(final String[] args, PrintWriter pw) {
+ return false;
+ }
+
+ /**
+ * Notifies the Shell that the configuration has changed.
+ */
+ default void onConfigurationChanged(Configuration newConfiguration) {}
+
+ /**
+ * Notifies the Shell that the keyguard is showing (and if so, whether it is occluded) or not
+ * showing, and whether it is animating a dismiss.
+ */
+ default void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {}
+
+ /**
+ * Notifies the Shell when the keyguard dismiss animation has finished.
+ */
+ default void onKeyguardDismissAnimationFinished() {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
deleted file mode 100644
index ad9dda619370..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelper.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Interface to communicate with a Task's SurfaceControl.
- */
-public interface TaskSurfaceHelper {
-
- /** Sets the METADATA_GAME_MODE for the layer corresponding to the task **/
- default void setGameModeForTask(int taskId, int gameMode) {}
-
- /** Takes a screenshot for a task **/
- default void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {}
-}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
deleted file mode 100644
index 064d9d1231c1..000000000000
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperController.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.tasksurfacehelper;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.graphics.Rect;
-import android.view.SurfaceControl;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-
-import java.util.concurrent.Executor;
-import java.util.function.Consumer;
-
-/**
- * Intermediary controller that communicates with {@link ShellTaskOrganizer} to send commands
- * to SurfaceControl.
- */
-public class TaskSurfaceHelperController {
-
- private final ShellTaskOrganizer mTaskOrganizer;
- private final ShellExecutor mMainExecutor;
- private final TaskSurfaceHelperImpl mImpl = new TaskSurfaceHelperImpl();
-
- public TaskSurfaceHelperController(ShellTaskOrganizer taskOrganizer,
- ShellExecutor mainExecutor) {
- mTaskOrganizer = taskOrganizer;
- mMainExecutor = mainExecutor;
- }
-
- public TaskSurfaceHelper asTaskSurfaceHelper() {
- return mImpl;
- }
-
- /**
- * Sends a Transaction to set the game mode metadata on the
- * corresponding SurfaceControl
- */
- public void setGameModeForTask(int taskId, int gameMode) {
- mTaskOrganizer.setSurfaceMetadata(taskId, SurfaceControl.METADATA_GAME_MODE, gameMode);
- }
-
- /**
- * Take screenshot of the specified task.
- */
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
- mTaskOrganizer.screenshotTask(taskInfo, crop, consumer);
- }
-
- private class TaskSurfaceHelperImpl implements TaskSurfaceHelper {
- @Override
- public void setGameModeForTask(int taskId, int gameMode) {
- mMainExecutor.execute(() -> {
- TaskSurfaceHelperController.this.setGameModeForTask(taskId, gameMode);
- });
- }
-
- @Override
- public void screenshotTask(RunningTaskInfo taskInfo, Rect crop, Executor executor,
- Consumer<SurfaceControl.ScreenshotHardwareBuffer> consumer) {
- mMainExecutor.execute(() -> {
- TaskSurfaceHelperController.this.screenshotTask(taskInfo, crop,
- (t) -> executor.execute(() -> consumer.accept(t)));
- });
- }
- }
-}
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
new file mode 100644
index 000000000000..11b453cb24a2
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.transition;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
+
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_CHILD_TASK_ENTER_PIP;
+import static com.android.wm.shell.splitscreen.StageCoordinator.FLAG_IS_DIVIDER_BAR;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.IBinder;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.splitscreen.StageCoordinator;
+
+import java.util.ArrayList;
+
+/**
+ * A handler for dealing with transitions involving multiple other handlers. For example: an
+ * activity in split-screen going into PiP.
+ */
+public class DefaultMixedHandler implements Transitions.TransitionHandler {
+
+ private final Transitions mPlayer;
+ private final PipTransitionController mPipHandler;
+ private final StageCoordinator mSplitHandler;
+
+ private static class MixedTransition {
+ static final int TYPE_ENTER_PIP_FROM_SPLIT = 1;
+
+ /** The default animation for this mixed transition. */
+ static final int ANIM_TYPE_DEFAULT = 0;
+
+ /** For ENTER_PIP_FROM_SPLIT, indicates that this is a to-home animation. */
+ static final int ANIM_TYPE_GOING_HOME = 1;
+
+ final int mType;
+ int mAnimType = 0;
+ final IBinder mTransition;
+
+ Transitions.TransitionFinishCallback mFinishCallback = null;
+ Transitions.TransitionHandler mLeftoversHandler = null;
+
+ /**
+ * Mixed transitions are made up of multiple "parts". This keeps track of how many
+ * parts are currently animating.
+ */
+ int mInFlightSubAnimations = 0;
+
+ MixedTransition(int type, IBinder transition) {
+ mType = type;
+ mTransition = transition;
+ }
+ }
+ private final ArrayList<MixedTransition> mActiveTransitions = new ArrayList<>();
+
+ public DefaultMixedHandler(@NonNull Transitions player,
+ @NonNull PipTransitionController pipHandler, @NonNull StageCoordinator splitHandler) {
+ mPlayer = player;
+ mPipHandler = pipHandler;
+ mSplitHandler = splitHandler;
+ }
+
+ @Nullable
+ @Override
+ public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+ @NonNull TransitionRequestInfo request) {
+ if (mPipHandler.requestHasPipEnter(request) && mSplitHandler.isSplitActive()) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Got a PiP-enter request while "
+ + "Split-Screen is active, so treat it as Mixed.");
+ if (request.getRemoteTransition() != null) {
+ throw new IllegalStateException("Unexpected remote transition in"
+ + "pip-enter-from-split request");
+ }
+ mActiveTransitions.add(new MixedTransition(MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT,
+ transition));
+
+ WindowContainerTransaction out = new WindowContainerTransaction();
+ mPipHandler.augmentRequest(transition, request, out);
+ mSplitHandler.addEnterOrExitIfNeeded(request, out);
+ return out;
+ }
+ return null;
+ }
+
+ private TransitionInfo subCopy(@NonNull TransitionInfo info,
+ @WindowManager.TransitionType int newType) {
+ final TransitionInfo out = new TransitionInfo(newType, info.getFlags());
+ for (int i = 0; i < info.getChanges().size(); ++i) {
+ out.getChanges().add(info.getChanges().get(i));
+ }
+ out.setRootLeash(info.getRootLeash(), info.getRootOffset().x, info.getRootOffset().y);
+ out.setAnimationOptions(info.getAnimationOptions());
+ return out;
+ }
+
+ private boolean isHomeOpening(@NonNull TransitionInfo.Change change) {
+ return change.getTaskInfo() != null
+ && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME;
+ }
+
+ private boolean isWallpaper(@NonNull TransitionInfo.Change change) {
+ return (change.getFlags() & FLAG_IS_WALLPAPER) != 0;
+ }
+
+ @Override
+ public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ MixedTransition mixed = null;
+ for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
+ if (mActiveTransitions.get(i).mTransition != transition) continue;
+ mixed = mActiveTransitions.get(i);
+ break;
+ }
+ if (mixed == null) return false;
+
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ return animateEnterPipFromSplit(mixed, info, startTransaction, finishTransaction,
+ finishCallback);
+ } else {
+ mActiveTransitions.remove(mixed);
+ throw new IllegalStateException("Starting mixed animation without a known mixed type? "
+ + mixed.mType);
+ }
+ }
+
+ private boolean animateEnterPipFromSplit(@NonNull final MixedTransition mixed,
+ @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startTransaction,
+ @NonNull SurfaceControl.Transaction finishTransaction,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animating a mixed transition for "
+ + "entering PIP while Split-Screen is active.");
+ TransitionInfo.Change pipChange = null;
+ TransitionInfo.Change wallpaper = null;
+ final TransitionInfo everythingElse = subCopy(info, TRANSIT_TO_BACK);
+ boolean homeIsOpening = false;
+ for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ TransitionInfo.Change change = info.getChanges().get(i);
+ if (mPipHandler.isEnteringPip(change, info.getType())) {
+ if (pipChange != null) {
+ throw new IllegalStateException("More than 1 pip-entering changes in one"
+ + " transition? " + info);
+ }
+ pipChange = change;
+ // going backwards, so remove-by-index is fine.
+ everythingElse.getChanges().remove(i);
+ } else if (isHomeOpening(change)) {
+ homeIsOpening = true;
+ } else if (isWallpaper(change)) {
+ wallpaper = change;
+ }
+ }
+ if (pipChange == null) {
+ // um, something probably went wrong.
+ return false;
+ }
+ final boolean isGoingHome = homeIsOpening;
+ mixed.mFinishCallback = finishCallback;
+ Transitions.TransitionFinishCallback finishCB = (wct, wctCB) -> {
+ --mixed.mInFlightSubAnimations;
+ if (mixed.mInFlightSubAnimations > 0) return;
+ mActiveTransitions.remove(mixed);
+ if (isGoingHome) {
+ mSplitHandler.onTransitionAnimationComplete();
+ }
+ mixed.mFinishCallback.onTransitionFinished(wct, wctCB);
+ };
+ if (isGoingHome) {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Animation is actually mixed "
+ + "since entering-PiP caused us to leave split and return home.");
+ // We need to split the transition into 2 parts: the pip part (animated by pip)
+ // and the dismiss-part (animated by launcher).
+ mixed.mInFlightSubAnimations = 2;
+ // immediately make the wallpaper visible (so that we don't see it pop-in during
+ // the time it takes to start recents animation (which is remote).
+ if (wallpaper != null) {
+ startTransaction.show(wallpaper.getLeash()).setAlpha(wallpaper.getLeash(), 1.f);
+ }
+ // make a new startTransaction because pip's startEnterAnimation "consumes" it so
+ // we need a separate one to send over to launcher.
+ SurfaceControl.Transaction otherStartT = new SurfaceControl.Transaction();
+ // Let split update internal state for dismiss.
+ mSplitHandler.prepareDismissAnimation(STAGE_TYPE_UNDEFINED,
+ EXIT_REASON_CHILD_TASK_ENTER_PIP, everythingElse, otherStartT,
+ finishTransaction);
+
+ // We are trying to accommodate launcher's close animation which can't handle the
+ // divider-bar, so if split-handler is closing the divider-bar, just hide it and remove
+ // from transition info.
+ for (int i = everythingElse.getChanges().size() - 1; i >= 0; --i) {
+ if ((everythingElse.getChanges().get(i).getFlags() & FLAG_IS_DIVIDER_BAR) != 0) {
+ everythingElse.getChanges().remove(i);
+ break;
+ }
+ }
+
+ mPipHandler.startEnterAnimation(pipChange, startTransaction, finishTransaction,
+ finishCB);
+ // Dispatch the rest of the transition normally. This will most-likely be taken by
+ // recents or default handler.
+ mixed.mLeftoversHandler = mPlayer.dispatchTransition(mixed.mTransition, everythingElse,
+ otherStartT, finishTransaction, finishCB, this);
+ } else {
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " Not leaving split, so just "
+ + "forward animation to Pip-Handler.");
+ // This happens if the pip-ing activity is in a multi-activity task (and thus a
+ // new pip task is spawned). In this case, we don't actually exit split so we can
+ // just let pip transition handle the animation verbatim.
+ mixed.mInFlightSubAnimations = 1;
+ mPipHandler.startAnimation(mixed.mTransition, info, startTransaction, finishTransaction,
+ finishCB);
+ }
+ return true;
+ }
+
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ for (int i = 0; i < mActiveTransitions.size(); ++i) {
+ if (mActiveTransitions.get(i) != mergeTarget) continue;
+ MixedTransition mixed = mActiveTransitions.get(i);
+ if (mixed.mInFlightSubAnimations <= 0) {
+ // Already done, so no need to end it.
+ return;
+ }
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ if (mixed.mAnimType == MixedTransition.ANIM_TYPE_GOING_HOME) {
+ boolean ended = mSplitHandler.end();
+ // If split couldn't end (because it is remote), then don't end everything else
+ // since we have to play out the animation anyways.
+ if (!ended) return;
+ mPipHandler.end();
+ if (mixed.mLeftoversHandler != null) {
+ mixed.mLeftoversHandler.mergeAnimation(transition, info, t, mergeTarget,
+ finishCallback);
+ }
+ } else {
+ mPipHandler.end();
+ }
+ } else {
+ throw new IllegalStateException("Playing a mixed transition with unknown type? "
+ + mixed.mType);
+ }
+ }
+ }
+
+ @Override
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
+ MixedTransition mixed = null;
+ for (int i = mActiveTransitions.size() - 1; i >= 0; --i) {
+ if (mActiveTransitions.get(i).mTransition != transition) continue;
+ mixed = mActiveTransitions.remove(i);
+ break;
+ }
+ if (mixed == null) return;
+ if (mixed.mType == MixedTransition.TYPE_ENTER_PIP_FROM_SPLIT) {
+ mPipHandler.onTransitionConsumed(transition, aborted);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index 9154226b7b22..0bec54399dd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -24,6 +24,7 @@ import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
import static android.app.ActivityOptions.ANIM_SCALE_UP;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_DOWN;
import static android.app.ActivityOptions.ANIM_THUMBNAIL_SCALE_UP;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
import static android.app.admin.DevicePolicyManager.EXTRA_RESOURCE_TYPE;
@@ -42,6 +43,7 @@ import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManager.TRANSIT_TO_BACK;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
@@ -470,8 +472,9 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
}
final Rect clipRect = Transitions.isClosingType(change.getMode())
- ? mRotator.getEndBoundsInStartRotation(change)
- : change.getEndAbsBounds();
+ ? new Rect(mRotator.getEndBoundsInStartRotation(change))
+ : new Rect(change.getEndAbsBounds());
+ clipRect.offsetTo(0, 0);
if (delayedEdgeExtension) {
// If the edge extension needs to happen after the startTransition has been
@@ -480,11 +483,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
postStartTransactionCallbacks.add(t ->
startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
mTransactionPool, mMainExecutor, mAnimExecutor,
- null /* position */, cornerRadius, clipRect));
+ change.getEndRelOffset(), cornerRadius, clipRect));
} else {
startSurfaceAnimation(animations, a, change.getLeash(), onAnimFinish,
- mTransactionPool, mMainExecutor, mAnimExecutor, null /* position */,
- cornerRadius, clipRect);
+ mTransactionPool, mMainExecutor, mAnimExecutor,
+ change.getEndRelOffset(), cornerRadius, clipRect);
}
if (info.getAnimationOptions() != null) {
@@ -520,6 +523,18 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
return true;
}
+ @Override
+ public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
+ @NonNull Transitions.TransitionFinishCallback finishCallback) {
+ ArrayList<Animator> anims = mAnimations.get(mergeTarget);
+ if (anims == null) return;
+ for (int i = anims.size() - 1; i >= 0; --i) {
+ final Animator anim = anims.get(i);
+ mAnimExecutor.execute(anim::end);
+ }
+ }
+
private void edgeExtendWindow(TransitionInfo.Change change,
Animation a, SurfaceControl.Transaction startTransaction,
SurfaceControl.Transaction finishTransaction) {
@@ -684,6 +699,8 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
final Rect endBounds = Transitions.isClosingType(changeMode)
? mRotator.getEndBoundsInStartRotation(change)
: change.getEndAbsBounds();
+ final boolean isDream =
+ isTask && change.getTaskInfo().topActivityType == ACTIVITY_TYPE_DREAM;
if (info.isKeyguardGoingAway()) {
a = mTransitionAnimation.loadKeyguardExitAnimation(flags,
@@ -726,7 +743,17 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
} else {
int animAttr = 0;
boolean translucent = false;
- if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
+ if (isDream) {
+ if (type == TRANSIT_OPEN) {
+ animAttr = enter
+ ? R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation
+ : R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
+ } else if (type == TRANSIT_CLOSE) {
+ animAttr = enter
+ ? 0
+ : R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+ }
+ } else if (wallpaperTransit == WALLPAPER_TRANSITION_INTRA_OPEN) {
animAttr = enter
? R.styleable.WindowAnimation_wallpaperIntraOpenEnterAnimation
: R.styleable.WindowAnimation_wallpaperIntraOpenExitAnimation;
@@ -790,6 +817,11 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a = mTransitionAnimation.loadDefaultAnimationAttr(animAttr, translucent);
}
}
+
+ ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
+ "loadAnimation: anim=%s animAttr=0x%x type=%s isEntrance=%b", a, animAttr,
+ transitTypeToString(type),
+ enter);
}
if (a != null) {
@@ -834,13 +866,19 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
});
};
va.addListener(new AnimatorListenerAdapter() {
+ private boolean mFinished = false;
+
@Override
public void onAnimationEnd(Animator animation) {
+ if (mFinished) return;
+ mFinished = true;
finisher.run();
}
@Override
public void onAnimationCancel(Animator animation) {
+ if (mFinished) return;
+ mFinished = true;
finisher.run();
}
});
@@ -896,7 +934,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, new Point(bounds.left, bounds.top),
+ mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
cornerRadius, change.getEndAbsBounds());
}
@@ -921,7 +959,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
a.restrictDuration(MAX_ANIMATION_DURATION);
a.scaleCurrentDuration(mTransitionAnimationScaleSetting);
startSurfaceAnimation(animations, a, wt.getSurface(), finisher, mTransactionPool,
- mMainExecutor, mAnimExecutor, null /* position */,
+ mMainExecutor, mAnimExecutor, change.getEndRelOffset(),
cornerRadius, change.getEndAbsBounds());
}
@@ -954,7 +992,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
private static void applyTransformation(long time, SurfaceControl.Transaction t,
SurfaceControl leash, Animation anim, Transformation transformation, float[] matrix,
- Point position, float cornerRadius, @Nullable Rect clipRect) {
+ Point position, float cornerRadius, @Nullable Rect immutableClipRect) {
anim.getTransformation(time, transformation);
if (position != null) {
transformation.getMatrix().postTranslate(position.x, position.y);
@@ -962,6 +1000,7 @@ public class DefaultTransitionHandler implements Transitions.TransitionHandler {
t.setMatrix(leash, transformation.getMatrix(), matrix);
t.setAlpha(leash, transformation.getAlpha());
+ final Rect clipRect = immutableClipRect == null ? null : new Rect(immutableClipRect);
Insets extensionInsets = Insets.min(transformation.getInsets(), Insets.NONE);
if (!extensionInsets.equals(Insets.NONE) && clipRect != null && !clipRect.isEmpty()) {
// Clip out any overflowing edge extension
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index 3e2a0e635a75..ebaece2189aa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -99,6 +99,8 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
+ " during unit tests");
}
mRemote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ // assume that remote will apply the start transaction.
+ startTransaction.clear();
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
if (mRemote.asBinder() != null) {
@@ -120,6 +122,11 @@ public class OneShotRemoteHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ // We have merged, since we sent the transaction over binder, the one in this
+ // process won't be cleared if the remote applied it. We don't actually know if the
+ // remote applied the transaction, but applying twice will break surfaceflinger
+ // so just assume the worst-case and clear the local transaction.
+ t.clear();
mMainExecutor.execute(
() -> finishCallback.onTransitionFinished(wct, null /* wctCB */));
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
index ece9f47e8788..b15c48cb5889 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RemoteTransitionHandler.java
@@ -83,7 +83,7 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
}
@Override
- public void onTransitionMerged(@NonNull IBinder transition) {
+ public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) {
mRequestedRemotes.remove(transition);
}
@@ -139,6 +139,8 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
+ " during unit tests");
}
remote.getRemoteTransition().startAnimation(transition, info, startTransaction, cb);
+ // assume that remote will apply the start transaction.
+ startTransaction.clear();
} catch (RemoteException e) {
Log.e(Transitions.TAG, "Error running remote transition.", e);
unhandleDeath(remote.asBinder(), finishCallback);
@@ -162,6 +164,11 @@ public class RemoteTransitionHandler implements Transitions.TransitionHandler {
@Override
public void onTransitionFinished(WindowContainerTransaction wct,
SurfaceControl.Transaction sct) {
+ // We have merged, since we sent the transaction over binder, the one in this
+ // process won't be cleared if the remote applied it. We don't actually know if the
+ // remote applied the transaction, but applying twice will break surfaceflinger
+ // so just assume the worst-case and clear the local transaction.
+ t.clear();
mMainExecutor.execute(() -> {
if (!mRequestedRemotes.containsKey(mergeTarget)) {
Log.e(TAG, "Merged transition finished after it's mergeTarget (the "
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 435d67087f34..fa22c7ca94d2 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
@@ -23,6 +23,7 @@ import static android.view.WindowManager.TRANSIT_KEYGUARD_GOING_AWAY;
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.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
@@ -287,12 +288,14 @@ public class Transitions implements RemoteCallable<Transitions> {
finishT.setAlpha(leash, 1.f);
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
- // Wallpaper is a bit of an anomaly: it's visibility is tied to other WindowStates.
- // As a result, we actually can't hide it's WindowToken because there may not be a
- // transition associated with it becoming visible again. Fortunately, since it is
- // always z-ordered to the back, we don't have to worry about it flickering to the
- // front during reparenting, so the hide here isn't necessary for it.
- if ((change.getFlags() & FLAG_IS_WALLPAPER) == 0) {
+ // Wallpaper/IME are anomalies: their visibility is tied to other WindowStates.
+ // As a result, we actually can't hide their WindowTokens because there may not be a
+ // transition associated with them becoming visible again. Fortunately, since
+ // wallpapers are always z-ordered to the back, we don't have to worry about it
+ // flickering to the front during reparenting. Similarly, the IME is reparented to
+ // the associated app, so its visibility is coupled. So, an explicit hide is not
+ // needed visually anyways.
+ if ((change.getFlags() & (FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD)) == 0) {
finishT.hide(leash);
}
}
@@ -309,13 +312,14 @@ public class Transitions implements RemoteCallable<Transitions> {
if (info.getRootLeash().isValid()) {
t.show(info.getRootLeash());
}
+ final int numChanges = info.getChanges().size();
// Put animating stuff above this line and put static stuff below it.
- int zSplitLine = info.getChanges().size();
+ final int zSplitLine = numChanges + 1;
// changes should be ordered top-to-bottom in z
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+ for (int i = numChanges - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = change.getLeash();
- final int mode = info.getChanges().get(i).getMode();
+ final int mode = change.getMode();
// Don't reparent anything that isn't independent within its parents
if (!TransitionInfo.isIndependent(change, info)) {
@@ -329,26 +333,31 @@ public class Transitions implements RemoteCallable<Transitions> {
t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
change.getStartAbsBounds().top - info.getRootOffset().y);
}
+ final int layer;
// Put all the OPEN/SHOW on top
- if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
+ if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+ // Wallpaper is always at the bottom.
+ layer = 0;
+ } else if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
// put on top
- t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
+ layer = zSplitLine + numChanges - i;
} else {
// put on bottom
- t.setLayer(leash, zSplitLine - i);
+ layer = zSplitLine - i;
}
} else if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
if (isOpening) {
// put on bottom and leave visible
- t.setLayer(leash, zSplitLine - i);
+ layer = zSplitLine - i;
} else {
// put on top
- t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
+ layer = zSplitLine + numChanges - i;
}
} else { // CHANGE or other
- t.setLayer(leash, zSplitLine + info.getChanges().size() - i);
+ layer = zSplitLine + numChanges - i;
}
+ t.setLayer(leash, layer);
}
}
@@ -377,6 +386,7 @@ public class Transitions implements RemoteCallable<Transitions> {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Invalid root leash (%s): %s",
transitionToken, info);
t.apply();
+ finishT.apply();
onAbort(transitionToken);
return;
}
@@ -400,6 +410,7 @@ public class Transitions implements RemoteCallable<Transitions> {
}
if (nonTaskChange && transferStartingWindow) {
t.apply();
+ finishT.apply();
// Treat this as an abort since we are bypassing any merge logic and effectively
// finishing immediately.
onAbort(transitionToken);
@@ -435,33 +446,42 @@ public class Transitions implements RemoteCallable<Transitions> {
playing.mToken, (wct, cb) -> onFinish(merging.mToken, wct, cb));
}
- boolean startAnimation(@NonNull ActiveTransition active, TransitionHandler handler) {
- return handler.startAnimation(active.mToken, active.mInfo, active.mStartT, active.mFinishT,
- (wct, cb) -> onFinish(active.mToken, wct, cb));
- }
-
- void playTransition(@NonNull ActiveTransition active) {
+ private void playTransition(@NonNull ActiveTransition active) {
setupAnimHierarchy(active.mInfo, active.mStartT, active.mFinishT);
// If a handler already chose to run this animation, try delegating to it first.
if (active.mHandler != null) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try firstHandler %s",
active.mHandler);
- if (startAnimation(active, active.mHandler)) {
+ boolean consumed = active.mHandler.startAnimation(active.mToken, active.mInfo,
+ active.mStartT, active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb));
+ if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by firstHandler");
return;
}
}
- // Otherwise give every other handler a chance (in order)
+ // Otherwise give every other handler a chance
+ active.mHandler = dispatchTransition(active.mToken, active.mInfo, active.mStartT,
+ active.mFinishT, (wct, cb) -> onFinish(active.mToken, wct, cb), active.mHandler);
+ }
+
+ /**
+ * Gives every handler (in order) a chance to animate until one consumes the transition.
+ * @return the handler which consumed the transition.
+ */
+ TransitionHandler dispatchTransition(@NonNull IBinder transition, @NonNull TransitionInfo info,
+ @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT,
+ @NonNull TransitionFinishCallback finishCB, @Nullable TransitionHandler skip) {
for (int i = mHandlers.size() - 1; i >= 0; --i) {
- if (mHandlers.get(i) == active.mHandler) continue;
+ if (mHandlers.get(i) == skip) continue;
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " try handler %s",
mHandlers.get(i));
- if (startAnimation(active, mHandlers.get(i))) {
+ boolean consumed = mHandlers.get(i).startAnimation(transition, info, startT, finishT,
+ finishCB);
+ if (consumed) {
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, " animated by %s",
mHandlers.get(i));
- active.mHandler = mHandlers.get(i);
- return;
+ return mHandlers.get(i);
}
}
throw new IllegalStateException(
@@ -496,15 +516,20 @@ public class Transitions implements RemoteCallable<Transitions> {
active.mMerged = true;
active.mAborted = abort;
if (active.mHandler != null) {
- active.mHandler.onTransitionMerged(active.mToken);
+ active.mHandler.onTransitionConsumed(active.mToken, abort);
}
return;
}
- mActiveTransitions.get(activeIdx).mAborted = abort;
+ final ActiveTransition active = mActiveTransitions.get(activeIdx);
+ active.mAborted = abort;
+ if (active.mAborted && active.mHandler != null) {
+ // Notifies to clean-up the aborted transition.
+ active.mHandler.onTransitionConsumed(transition, true /* aborted */);
+ }
ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS,
"Transition animation finished (abort=%b), notifying core %s", abort, transition);
// Merge all relevant transactions together
- SurfaceControl.Transaction fullFinish = mActiveTransitions.get(activeIdx).mFinishT;
+ SurfaceControl.Transaction fullFinish = active.mFinishT;
for (int iA = activeIdx + 1; iA < mActiveTransitions.size(); ++iA) {
final ActiveTransition toMerge = mActiveTransitions.get(iA);
if (!toMerge.mMerged) break;
@@ -533,6 +558,10 @@ public class Transitions implements RemoteCallable<Transitions> {
while (mActiveTransitions.size() > activeIdx
&& mActiveTransitions.get(activeIdx).mAborted) {
ActiveTransition aborted = mActiveTransitions.remove(activeIdx);
+ // Notifies to clean-up the aborted transition.
+ if (aborted.mHandler != null) {
+ aborted.mHandler.onTransitionConsumed(transition, true /* aborted */);
+ }
mOrganizer.finishTransition(aborted.mToken, null /* wct */, null /* wctCB */);
}
if (mActiveTransitions.size() <= activeIdx) {
@@ -615,8 +644,9 @@ public class Transitions implements RemoteCallable<Transitions> {
if (wct == null) {
wct = new WindowContainerTransaction();
}
- mDisplayController.getChangeController().dispatchOnRotateDisplay(wct,
- change.getDisplayId(), change.getStartRotation(), change.getEndRotation());
+ mDisplayController.getChangeController().dispatchOnDisplayChange(wct,
+ change.getDisplayId(), change.getStartRotation(), change.getEndRotation(),
+ null /* newDisplayAreaInfo */);
}
}
active.mToken = mOrganizer.startTransition(
@@ -714,9 +744,10 @@ public class Transitions implements RemoteCallable<Transitions> {
/**
* Called when a transition which was already "claimed" by this handler has been merged
- * into another animation. Gives this handler a chance to clean-up any expectations.
+ * into another animation or has been aborted. Gives this handler a chance to clean-up any
+ * expectations.
*/
- default void onTransitionMerged(@NonNull IBinder transition) { }
+ default void onTransitionConsumed(@NonNull IBinder transition, boolean aborted) { }
/**
* Sets transition animation scale settings value to handler.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
new file mode 100644
index 000000000000..05a024a0eb12
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.util.SparseArray;
+import android.view.SurfaceControl;
+
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+
+import dagger.Lazy;
+
+/**
+ * Manages fold/unfold animations of tasks on foldable devices.
+ * When folding or unfolding a foldable device we play animations that
+ * transform task cropping/scaling/rounded corners.
+ *
+ * This controller manages:
+ * 1) Folding/unfolding when Shell transitions disabled
+ * 2) Folding when Shell transitions enabled, unfolding is managed by
+ * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler}
+ */
+public class UnfoldAnimationController implements UnfoldListener {
+
+ private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
+ private final Executor mExecutor;
+ private final TransactionPool mTransactionPool;
+ private final List<UnfoldTaskAnimator> mAnimators;
+ private final Lazy<Optional<UnfoldTransitionHandler>> mUnfoldTransitionHandler;
+
+ private final SparseArray<SurfaceControl> mTaskSurfaces = new SparseArray<>();
+ private final SparseArray<UnfoldTaskAnimator> mAnimatorsByTaskId = new SparseArray<>();
+
+ public UnfoldAnimationController(@NonNull TransactionPool transactionPool,
+ @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
+ @NonNull List<UnfoldTaskAnimator> animators,
+ @NonNull Lazy<Optional<UnfoldTransitionHandler>> unfoldTransitionHandler,
+ @NonNull Executor executor) {
+ mUnfoldProgressProvider = unfoldProgressProvider;
+ mUnfoldTransitionHandler = unfoldTransitionHandler;
+ mTransactionPool = transactionPool;
+ mExecutor = executor;
+ mAnimators = animators;
+ }
+
+ /**
+ * Initializes the controller, starts listening for the external events
+ */
+ public void init() {
+ mUnfoldProgressProvider.addListener(mExecutor, this);
+
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.init();
+ animator.start();
+ }
+ }
+
+ /**
+ * Called when a task appeared
+ * @param taskInfo info for the appeared task
+ * @param leash surface leash for the appeared task
+ */
+ public void onTaskAppeared(RunningTaskInfo taskInfo, SurfaceControl leash) {
+ mTaskSurfaces.put(taskInfo.taskId, leash);
+
+ // Find the first matching animator
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.isApplicableTask(taskInfo)) {
+ mAnimatorsByTaskId.put(taskInfo.taskId, animator);
+ animator.onTaskAppeared(taskInfo, leash);
+ break;
+ }
+ }
+ }
+
+ /**
+ * Called when task info changed
+ * @param taskInfo info for the changed task
+ */
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
+ final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId);
+ final boolean isCurrentlyApplicable = animator != null;
+
+ if (isCurrentlyApplicable) {
+ final boolean isApplicable = animator.isApplicableTask(taskInfo);
+ if (isApplicable) {
+ // Still applicable, send update
+ animator.onTaskChanged(taskInfo);
+ } else {
+ // Became inapplicable
+ resetTask(animator, taskInfo);
+ animator.onTaskVanished(taskInfo);
+ mAnimatorsByTaskId.remove(taskInfo.taskId);
+ }
+ } else {
+ // Find the first matching animator
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator currentAnimator = mAnimators.get(i);
+ if (currentAnimator.isApplicableTask(taskInfo)) {
+ // Became applicable
+ mAnimatorsByTaskId.put(taskInfo.taskId, currentAnimator);
+
+ SurfaceControl leash = mTaskSurfaces.get(taskInfo.taskId);
+ currentAnimator.onTaskAppeared(taskInfo, leash);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * Called when a task vanished
+ * @param taskInfo info for the vanished task
+ */
+ public void onTaskVanished(RunningTaskInfo taskInfo) {
+ mTaskSurfaces.remove(taskInfo.taskId);
+
+ final UnfoldTaskAnimator animator = mAnimatorsByTaskId.get(taskInfo.taskId);
+ final boolean isCurrentlyApplicable = animator != null;
+
+ if (isCurrentlyApplicable) {
+ resetTask(animator, taskInfo);
+ animator.onTaskVanished(taskInfo);
+ mAnimatorsByTaskId.remove(taskInfo.taskId);
+ }
+ }
+
+ @Override
+ public void onStateChangeStarted() {
+ if (mUnfoldTransitionHandler.get().get().willHandleTransition()) {
+ return;
+ }
+
+ SurfaceControl.Transaction transaction = null;
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.hasActiveTasks()) {
+ if (transaction == null) transaction = mTransactionPool.acquire();
+ animator.prepareStartTransaction(transaction);
+ }
+ }
+
+ if (transaction != null) {
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ if (mUnfoldTransitionHandler.get().get().willHandleTransition()) {
+ return;
+ }
+
+ SurfaceControl.Transaction transaction = null;
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.hasActiveTasks()) {
+ if (transaction == null) transaction = mTransactionPool.acquire();
+ animator.applyAnimationProgress(progress, transaction);
+ }
+ }
+
+ if (transaction != null) {
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ if (mUnfoldTransitionHandler.get().get().willHandleTransition()) {
+ return;
+ }
+
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.resetAllSurfaces(transaction);
+ animator.prepareFinishTransaction(transaction);
+ }
+
+ transaction.apply();
+
+ mTransactionPool.release(transaction);
+ }
+
+ private void resetTask(UnfoldTaskAnimator animator, TaskInfo taskInfo) {
+ if (taskInfo.getWindowingMode() == WINDOWING_MODE_PINNED) {
+ // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
+ return;
+ }
+ final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ animator.resetSurface(taskInfo, transaction);
+ transaction.apply();
+ mTransactionPool.release(transaction);
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
index 9faf454261d3..86ca292399cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldBackgroundController.java
@@ -79,7 +79,7 @@ public class UnfoldBackgroundController {
}
private float[] getBackgroundColor(Context context) {
- int colorInt = context.getResources().getColor(R.color.unfold_transition_background);
+ int colorInt = context.getResources().getColor(R.color.taskbar_background);
return new float[]{
(float) red(colorInt) / 255.0F,
(float) green(colorInt) / 255.0F,
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 639603941c18..9bf32faa12bd 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
@@ -16,8 +16,6 @@
package com.android.wm.shell.unfold;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.view.WindowManager.TRANSIT_CHANGE;
import android.os.IBinder;
@@ -34,11 +32,19 @@ import com.android.wm.shell.transition.Transitions;
import com.android.wm.shell.transition.Transitions.TransitionFinishCallback;
import com.android.wm.shell.transition.Transitions.TransitionHandler;
import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator;
+import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
+/**
+ * Transition handler that is responsible for animating app surfaces when unfolding of foldable
+ * devices. It does not handle the folding animation, which is done in
+ * {@link UnfoldAnimationController}.
+ */
public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener {
private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
@@ -51,17 +57,26 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@Nullable
private IBinder mTransition;
- private final List<TransitionInfo.Change> mAnimatedFullscreenTasks = new ArrayList<>();
+ private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>();
public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider,
- TransactionPool transactionPool, Executor executor, Transitions transitions) {
+ FullscreenUnfoldTaskAnimator fullscreenUnfoldAnimator,
+ SplitTaskUnfoldAnimator splitUnfoldTaskAnimator,
+ TransactionPool transactionPool,
+ Executor executor, Transitions transitions) {
mUnfoldProgressProvider = unfoldProgressProvider;
mTransactionPool = transactionPool;
mExecutor = executor;
mTransitions = transitions;
+
+ mAnimators.add(splitUnfoldTaskAnimator);
+ mAnimators.add(fullscreenUnfoldAnimator);
}
public void init() {
+ for (int i = 0; i < mAnimators.size(); i++) {
+ mAnimators.get(i).init();
+ }
mTransitions.addHandler(this);
mUnfoldProgressProvider.addListener(mExecutor, this);
}
@@ -71,49 +86,69 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull TransitionFinishCallback finishCallback) {
-
if (transition != mTransition) return false;
- startTransaction.apply();
-
- mAnimatedFullscreenTasks.clear();
- info.getChanges().forEach(change -> {
- final boolean allowedToAnimate = change.getTaskInfo() != null
- && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN
- && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME
- && change.getMode() == TRANSIT_CHANGE;
-
- if (allowedToAnimate) {
- mAnimatedFullscreenTasks.add(change);
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.clearTasks();
+
+ info.getChanges().forEach(change -> {
+ if (change.getTaskInfo() != null
+ && change.getMode() == TRANSIT_CHANGE
+ && animator.isApplicableTask(change.getTaskInfo())) {
+ animator.onTaskAppeared(change.getTaskInfo(), change.getLeash());
+ }
+ });
+
+ if (animator.hasActiveTasks()) {
+ animator.prepareStartTransaction(startTransaction);
+ animator.prepareFinishTransaction(finishTransaction);
+ animator.start();
}
- });
+ }
+ startTransaction.apply();
mFinishCallback = finishCallback;
- mTransition = null;
return true;
}
@Override
public void onStateChangeProgress(float progress) {
- mAnimatedFullscreenTasks.forEach(change -> {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
+ if (mTransition == null) return;
+
+ SurfaceControl.Transaction transaction = null;
- // TODO: this is a placeholder animation, replace with a spec version in the next CLs
- final float testScale = 0.8f + 0.2f * progress;
- transaction.setScale(change.getLeash(), testScale, testScale);
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ if (animator.hasActiveTasks()) {
+ if (transaction == null) {
+ transaction = mTransactionPool.acquire();
+ }
+
+ animator.applyAnimationProgress(progress, transaction);
+ }
+ }
+
+ if (transaction != null) {
transaction.apply();
mTransactionPool.release(transaction);
- });
+ }
}
@Override
public void onStateChangeFinished() {
- if (mFinishCallback != null) {
- mFinishCallback.onTransitionFinished(null, null);
- mFinishCallback = null;
- mAnimatedFullscreenTasks.clear();
+ if (mFinishCallback == null) return;
+
+ for (int i = 0; i < mAnimators.size(); i++) {
+ final UnfoldTaskAnimator animator = mAnimators.get(i);
+ animator.clearTasks();
+ animator.stop();
}
+
+ mFinishCallback.onTransitionFinished(null, null);
+ mFinishCallback = null;
+ mTransition = null;
}
@Nullable
@@ -127,4 +162,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene
}
return null;
}
+
+ public boolean willHandleTransition() {
+ return mTransition != null;
+ }
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
index aa3868cfca84..eab82f00e962 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/fullscreen/FullscreenUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/FullscreenUnfoldTaskAnimator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,16 +14,16 @@
* limitations under the License.
*/
-package com.android.wm.shell.fullscreen;
+package com.android.wm.shell.unfold.animation;
-import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.util.MathUtils.lerp;
import static android.view.Display.DEFAULT_DISPLAY;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
import android.annotation.NonNull;
-import android.app.ActivityManager;
import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Matrix;
@@ -32,22 +32,26 @@ import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
-import java.util.concurrent.Executor;
-
/**
- * Controls full screen app unfold transition: animating cropping window and scaling when
- * folding or unfolding a foldable device.
+ * This helper class contains logic that calculates scaling and cropping parameters
+ * for the folding/unfolding animation. As an input it receives TaskInfo objects and
+ * surfaces leashes and as an output it could fill surface transactions with required
+ * transformations.
+ *
+ * This class is used by
+ * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and
+ * {@link UnfoldAnimationController}. They use independent
+ * instances of FullscreenUnfoldTaskAnimator.
*/
-public final class FullscreenUnfoldController implements UnfoldListener,
- OnInsetsChangedListener {
+public class FullscreenUnfoldTaskAnimator implements UnfoldTaskAnimator,
+ DisplayInsetsController.OnInsetsChangedListener {
private static final float[] FLOAT_9 = new float[9];
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
@@ -57,49 +61,77 @@ public final class FullscreenUnfoldController implements UnfoldListener,
private static final float END_SCALE = 1f;
private static final float START_SCALE = END_SCALE - VERTICAL_START_MARGIN * 2;
- private final Executor mExecutor;
- private final ShellUnfoldProgressProvider mProgressProvider;
- private final DisplayInsetsController mDisplayInsetsController;
-
private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
+ private final int mExpandedTaskBarHeight;
+ private final float mWindowCornerRadiusPx;
+ private final DisplayInsetsController mDisplayInsetsController;
private final UnfoldBackgroundController mBackgroundController;
private InsetsSource mTaskbarInsetsSource;
- private final float mWindowCornerRadiusPx;
- private final float mExpandedTaskBarHeight;
-
- private final SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
-
- public FullscreenUnfoldController(
- @NonNull Context context,
- @NonNull Executor executor,
+ public FullscreenUnfoldTaskAnimator(Context context,
@NonNull UnfoldBackgroundController backgroundController,
- @NonNull ShellUnfoldProgressProvider progressProvider,
- @NonNull DisplayInsetsController displayInsetsController
- ) {
- mExecutor = executor;
- mProgressProvider = progressProvider;
+ DisplayInsetsController displayInsetsController) {
mDisplayInsetsController = displayInsetsController;
- mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mBackgroundController = backgroundController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
- mBackgroundController = backgroundController;
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
- /**
- * Initializes the controller
- */
public void init() {
- mProgressProvider.addListener(mExecutor, this);
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
}
@Override
- public void onStateChangeProgress(float progress) {
- if (mAnimationContextByTaskId.size() == 0) return;
+ public void insetsChanged(InsetsState insetsState) {
+ mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ context.update(mTaskbarInsetsSource, context.mTaskInfo);
+ }
+ }
+
+ public boolean hasActiveTasks() {
+ return mAnimationContextByTaskId.size() > 0;
+ }
+
+ @Override
+ public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
+ AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
+ taskInfo);
+ mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
+ }
+
+ @Override
+ public void onTaskChanged(TaskInfo taskInfo) {
+ AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (animationContext != null) {
+ animationContext.update(mTaskbarInsetsSource, taskInfo);
+ }
+ }
+
+ @Override
+ public void onTaskVanished(TaskInfo taskInfo) {
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
- mBackgroundController.ensureBackground(mTransaction);
+ @Override
+ public void clearTasks() {
+ mAnimationContextByTaskId.clear();
+ }
+
+ @Override
+ public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
+ final AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
+ if (context != null) {
+ resetSurface(context, transaction);
+ }
+ }
+
+ @Override
+ public void applyAnimationProgress(float progress, Transaction transaction) {
+ if (mAnimationContextByTaskId.size() == 0) return;
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
@@ -111,75 +143,41 @@ public final class FullscreenUnfoldController implements UnfoldListener,
context.mMatrix.setScale(scale, scale, context.mCurrentCropRect.exactCenterX(),
context.mCurrentCropRect.exactCenterY());
- mTransaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
+ transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setMatrix(context.mLeash, context.mMatrix, FLOAT_9)
- .setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
+ .setCornerRadius(context.mLeash, mWindowCornerRadiusPx)
+ .show(context.mLeash);
}
-
- mTransaction.apply();
}
@Override
- public void onStateChangeFinished() {
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- resetSurface(context);
- }
-
- mBackgroundController.removeBackground(mTransaction);
- mTransaction.apply();
+ public void prepareStartTransaction(Transaction transaction) {
+ mBackgroundController.ensureBackground(transaction);
}
@Override
- public void insetsChanged(InsetsState insetsState) {
- mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update(mTaskbarInsetsSource, context.mTaskInfo);
- }
+ public void prepareFinishTransaction(Transaction transaction) {
+ mBackgroundController.removeBackground(transaction);
}
- /**
- * Called when a new matching task appeared
- */
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- AnimationContext animationContext = new AnimationContext(leash, mTaskbarInsetsSource,
- taskInfo);
- mAnimationContextByTaskId.put(taskInfo.taskId, animationContext);
- }
-
- /**
- * Called when matching task changed
- */
- public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
- AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
- if (animationContext != null) {
- animationContext.update(mTaskbarInsetsSource, taskInfo);
- }
+ @Override
+ public boolean isApplicableTask(TaskInfo taskInfo) {
+ return taskInfo != null && taskInfo.isVisible()
+ && taskInfo.realActivity != null // to filter out parents created by organizer
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+ && taskInfo.getActivityType() != ACTIVITY_TYPE_HOME;
}
- /**
- * Called when matching task vanished
- */
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- AnimationContext animationContext = mAnimationContextByTaskId.get(taskInfo.taskId);
- if (animationContext != null) {
- // PiP task has its own cleanup path, ignore surface reset to avoid conflict.
- if (taskInfo.getWindowingMode() != WINDOWING_MODE_PINNED) {
- resetSurface(animationContext);
- }
- mAnimationContextByTaskId.remove(taskInfo.taskId);
- }
-
- if (mAnimationContextByTaskId.size() == 0) {
- mBackgroundController.removeBackground(mTransaction);
+ @Override
+ public void resetAllSurfaces(Transaction transaction) {
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(context, transaction);
}
-
- mTransaction.apply();
}
- private void resetSurface(AnimationContext context) {
- mTransaction
+ private void resetSurface(AnimationContext context, Transaction transaction) {
+ transaction
.setWindowCrop(context.mLeash, null)
.setCornerRadius(context.mLeash, 0.0F)
.setMatrix(context.mLeash, 1.0F, 0.0F, 0.0F, 1.0F)
@@ -197,10 +195,9 @@ public final class FullscreenUnfoldController implements UnfoldListener,
TaskInfo mTaskInfo;
- private AnimationContext(SurfaceControl leash,
- InsetsSource taskBarInsetsSource,
- TaskInfo taskInfo) {
- this.mLeash = leash;
+ private AnimationContext(SurfaceControl leash, InsetsSource taskBarInsetsSource,
+ TaskInfo taskInfo) {
+ mLeash = leash;
update(taskBarInsetsSource, taskInfo);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index 59eecb5db136..6e10ebe94c5d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskUnfoldController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,17 +14,19 @@
* limitations under the License.
*/
-package com.android.wm.shell.splitscreen;
+package com.android.wm.shell.unfold.animation;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
+import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.animation.RectEvaluator;
import android.animation.TypeEvaluator;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
+import android.app.TaskInfo;
import android.content.Context;
import android.graphics.Insets;
import android.graphics.Rect;
@@ -32,67 +34,130 @@ import android.util.SparseArray;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.DisplayInsetsController.OnInsetsChangedListener;
-import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider;
-import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
+import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
import com.android.wm.shell.unfold.UnfoldBackgroundController;
+import java.util.Optional;
import java.util.concurrent.Executor;
+import dagger.Lazy;
+
/**
- * Controls transformations of the split screen task surfaces in response
- * to the unfolding/folding action on foldable devices
+ * This helper class contains logic that calculates scaling and cropping parameters
+ * for the folding/unfolding animation. As an input it receives TaskInfo objects and
+ * surfaces leashes and as an output it could fill surface transactions with required
+ * transformations.
+ *
+ * This class is used by
+ * {@link com.android.wm.shell.unfold.UnfoldTransitionHandler} and
+ * {@link UnfoldAnimationController}.
+ * They use independent instances of SplitTaskUnfoldAnimator.
*/
-public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChangedListener {
+public class SplitTaskUnfoldAnimator implements UnfoldTaskAnimator,
+ DisplayInsetsController.OnInsetsChangedListener, SplitScreenListener {
private static final TypeEvaluator<Rect> RECT_EVALUATOR = new RectEvaluator(new Rect());
private static final float CROPPING_START_MARGIN_FRACTION = 0.05f;
- private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
- private final ShellUnfoldProgressProvider mUnfoldProgressProvider;
- private final DisplayInsetsController mDisplayInsetsController;
- private final UnfoldBackgroundController mBackgroundController;
private final Executor mExecutor;
+ private final DisplayInsetsController mDisplayInsetsController;
+ private final SparseArray<AnimationContext> mAnimationContextByTaskId = new SparseArray<>();
private final int mExpandedTaskBarHeight;
private final float mWindowCornerRadiusPx;
- private final Rect mStageBounds = new Rect();
- private final TransactionPool mTransactionPool;
+ private final Lazy<Optional<SplitScreenController>> mSplitScreenController;
+ private final UnfoldBackgroundController mUnfoldBackgroundController;
+
+ private final Rect mMainStageBounds = new Rect();
+ private final Rect mSideStageBounds = new Rect();
+ private final Rect mRootStageBounds = new Rect();
private InsetsSource mTaskbarInsetsSource;
- private boolean mBothStagesVisible;
-
- public StageTaskUnfoldController(@NonNull Context context,
- @NonNull TransactionPool transactionPool,
- @NonNull ShellUnfoldProgressProvider unfoldProgressProvider,
- @NonNull DisplayInsetsController displayInsetsController,
- @NonNull UnfoldBackgroundController backgroundController,
- @NonNull Executor executor) {
- mUnfoldProgressProvider = unfoldProgressProvider;
- mTransactionPool = transactionPool;
- mExecutor = executor;
- mBackgroundController = backgroundController;
+
+ @SplitPosition
+ private int mMainStagePosition = SPLIT_POSITION_UNDEFINED;
+ @SplitPosition
+ private int mSideStagePosition = SPLIT_POSITION_UNDEFINED;
+
+ public SplitTaskUnfoldAnimator(Context context, Executor executor,
+ Lazy<Optional<SplitScreenController>> splitScreenController,
+ UnfoldBackgroundController unfoldBackgroundController,
+ DisplayInsetsController displayInsetsController) {
mDisplayInsetsController = displayInsetsController;
- mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
+ mExecutor = executor;
+ mUnfoldBackgroundController = unfoldBackgroundController;
+ mSplitScreenController = splitScreenController;
mExpandedTaskBarHeight = context.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.taskbar_frame_height);
+ mWindowCornerRadiusPx = ScreenDecorationsUtils.getWindowCornerRadius(context);
}
- /**
- * Initializes the controller, starts listening for the external events
- */
+ /** Initializes the animator, this should be called only once */
+ @Override
public void init() {
- mUnfoldProgressProvider.addListener(mExecutor, this);
mDisplayInsetsController.addInsetsChangedListener(DEFAULT_DISPLAY, this);
}
+ /**
+ * Starts listening for split-screen changes and gets initial split-screen
+ * layout information through the listener
+ */
+ @Override
+ public void start() {
+ mSplitScreenController.get().get().asSplitScreen()
+ .registerSplitScreenListener(this, mExecutor);
+ }
+
+ /**
+ * Stops listening for the split-screen layout changes
+ */
+ @Override
+ public void stop() {
+ mSplitScreenController.get().get().asSplitScreen()
+ .unregisterSplitScreenListener(this);
+ }
+
@Override
public void insetsChanged(InsetsState insetsState) {
mTaskbarInsetsSource = insetsState.getSource(InsetsState.ITYPE_EXTRA_NAVIGATION_BAR);
+ updateContexts();
+ }
+
+ @Override
+ public void onTaskStageChanged(int taskId, int stage, boolean visible) {
+ final AnimationContext context = mAnimationContextByTaskId.get(taskId);
+ if (context != null) {
+ context.mStageType = stage;
+ context.update();
+ }
+ }
+
+ @Override
+ public void onStagePositionChanged(int stage, int position) {
+ if (stage == STAGE_TYPE_MAIN) {
+ mMainStagePosition = position;
+ } else {
+ mSideStagePosition = position;
+ }
+ updateContexts();
+ }
+
+ @Override
+ public void onSplitBoundsChanged(Rect rootBounds, Rect mainBounds, Rect sideBounds) {
+ mRootStageBounds.set(rootBounds);
+ mMainStageBounds.set(mainBounds);
+ mSideStageBounds.set(sideBounds);
+ updateContexts();
+ }
+
+ private void updateContexts() {
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
context.update();
@@ -100,44 +165,73 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
}
/**
- * Called when split screen task appeared
- * @param taskInfo info for the appeared task
- * @param leash surface leash for the appeared task
+ * Register a split task in the animator
+ * @param taskInfo info of the task
+ * @param leash the surface of the task
*/
- public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash) {
- // Only handle child task surface here.
- if (!taskInfo.hasParentTask()) return;
-
+ @Override
+ public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
AnimationContext context = new AnimationContext(leash);
mAnimationContextByTaskId.put(taskInfo.taskId, context);
}
/**
- * Called when a split screen task vanished
- * @param taskInfo info for the vanished task
+ * Unregister the task from the unfold animation
+ * @param taskInfo info of the task
+ */
+ @Override
+ public void onTaskVanished(TaskInfo taskInfo) {
+ mAnimationContextByTaskId.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public boolean isApplicableTask(TaskInfo taskInfo) {
+ return taskInfo.hasParentTask()
+ && taskInfo.isVisible
+ && taskInfo.realActivity != null // to filter out parents created by organizer
+ && taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW;
+ }
+
+ /**
+ * Clear all registered tasks
*/
- public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
- if (!taskInfo.hasParentTask()) return;
+ @Override
+ public void clearTasks() {
+ mAnimationContextByTaskId.clear();
+ }
+ /**
+ * Reset transformations of the task that could have been applied by the animator
+ * @param taskInfo task to reset
+ * @param transaction a transaction to write the changes to
+ */
+ @Override
+ public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
AnimationContext context = mAnimationContextByTaskId.get(taskInfo.taskId);
if (context != null) {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
resetSurface(transaction, context);
- transaction.apply();
- mTransactionPool.release(transaction);
}
- mAnimationContextByTaskId.remove(taskInfo.taskId);
}
+ /**
+ * Reset all surface transformation that could have been introduced by the animator
+ * @param transaction to write changes to
+ */
@Override
- public void onStateChangeProgress(float progress) {
- if (mAnimationContextByTaskId.size() == 0 || !mBothStagesVisible) return;
-
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
- mBackgroundController.ensureBackground(transaction);
+ public void resetAllSurfaces(Transaction transaction) {
+ for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
+ final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ resetSurface(transaction, context);
+ }
+ }
+ @Override
+ public void applyAnimationProgress(float progress, Transaction transaction) {
for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
AnimationContext context = mAnimationContextByTaskId.valueAt(i);
+ if (context.mStageType == STAGE_TYPE_UNDEFINED) {
+ continue;
+ }
context.mCurrentCropRect.set(RECT_EVALUATOR
.evaluate(progress, context.mStartCropRect, context.mEndCropRect));
@@ -145,53 +239,25 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
transaction.setWindowCrop(context.mLeash, context.mCurrentCropRect)
.setCornerRadius(context.mLeash, mWindowCornerRadiusPx);
}
-
- transaction.apply();
-
- mTransactionPool.release(transaction);
}
@Override
- public void onStateChangeFinished() {
- resetTransformations();
+ public void prepareStartTransaction(Transaction transaction) {
+ mUnfoldBackgroundController.ensureBackground(transaction);
+ mSplitScreenController.get().get().updateSplitScreenSurfaces(transaction);
}
- /**
- * Called when split screen visibility changes
- * @param bothStagesVisible true if both stages of the split screen are visible
- */
- public void onSplitVisibilityChanged(boolean bothStagesVisible) {
- mBothStagesVisible = bothStagesVisible;
- if (!bothStagesVisible) {
- resetTransformations();
- }
+ @Override
+ public void prepareFinishTransaction(Transaction transaction) {
+ mUnfoldBackgroundController.removeBackground(transaction);
}
/**
- * Called when split screen stage bounds changed
- * @param bounds new bounds for this stage
+ * @return true if there are tasks to animate
*/
- public void onLayoutChanged(Rect bounds, @SplitPosition int splitPosition,
- boolean isLandscape) {
- mStageBounds.set(bounds);
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- context.update(splitPosition, isLandscape);
- }
- }
-
- private void resetTransformations() {
- final SurfaceControl.Transaction transaction = mTransactionPool.acquire();
-
- for (int i = mAnimationContextByTaskId.size() - 1; i >= 0; i--) {
- final AnimationContext context = mAnimationContextByTaskId.valueAt(i);
- resetSurface(transaction, context);
- }
- mBackgroundController.removeBackground(transaction);
- transaction.apply();
-
- mTransactionPool.release(transaction);
+ @Override
+ public boolean hasActiveTasks() {
+ return mAnimationContextByTaskId.size() > 0;
}
private void resetSurface(SurfaceControl.Transaction transaction, AnimationContext context) {
@@ -202,26 +268,24 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
private class AnimationContext {
final SurfaceControl mLeash;
+
final Rect mStartCropRect = new Rect();
final Rect mEndCropRect = new Rect();
final Rect mCurrentCropRect = new Rect();
- private @SplitPosition int mSplitPosition = SPLIT_POSITION_UNDEFINED;
- private boolean mIsLandscape = false;
+ @SplitScreen.StageType
+ int mStageType = STAGE_TYPE_UNDEFINED;
private AnimationContext(SurfaceControl leash) {
- this.mLeash = leash;
- update();
- }
-
- private void update(@SplitPosition int splitPosition, boolean isLandscape) {
- this.mSplitPosition = splitPosition;
- this.mIsLandscape = isLandscape;
+ mLeash = leash;
update();
}
private void update() {
- mStartCropRect.set(mStageBounds);
+ final Rect stageBounds = mStageType == STAGE_TYPE_MAIN
+ ? mMainStageBounds : mSideStageBounds;
+
+ mStartCropRect.set(stageBounds);
boolean taskbarExpanded = isTaskbarExpanded();
if (taskbarExpanded) {
@@ -239,7 +303,8 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
// Sides adjacent to split bar or task bar are not be animated.
Insets margins;
- if (mIsLandscape) { // Left and right splits.
+ final boolean isLandscape = mRootStageBounds.width() > mRootStageBounds.height();
+ if (isLandscape) { // Left and right splits.
margins = getLandscapeMargins(margin, taskbarExpanded);
} else { // Top and bottom splits.
margins = getPortraitMargins(margin, taskbarExpanded);
@@ -251,7 +316,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
int left = margin;
int right = margin;
int bottom = taskbarExpanded ? 0 : margin; // Taskbar margin.
- if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ final int splitPosition = mStageType == STAGE_TYPE_MAIN
+ ? mMainStagePosition : mSideStagePosition;
+ if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
right = 0; // Divider margin.
} else {
left = 0; // Divider margin.
@@ -262,7 +329,9 @@ public class StageTaskUnfoldController implements UnfoldListener, OnInsetsChange
private Insets getPortraitMargins(int margin, boolean taskbarExpanded) {
int bottom = margin;
int top = margin;
- if (mSplitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
+ final int splitPosition = mStageType == STAGE_TYPE_MAIN
+ ? mMainStagePosition : mSideStagePosition;
+ if (splitPosition == SPLIT_POSITION_TOP_OR_LEFT) {
bottom = 0; // Divider margin.
} else { // Bottom split.
top = 0; // Divider margin.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java
new file mode 100644
index 000000000000..e1e366301b46
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/UnfoldTaskAnimator.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold.animation;
+
+import android.app.TaskInfo;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+/**
+ * Interface for classes that handle animations of tasks when folding or unfolding
+ * foldable devices.
+ */
+public interface UnfoldTaskAnimator {
+ /**
+ * Initializes the animator, this should be called once in the lifetime of the animator
+ */
+ default void init() {}
+
+ /**
+ * Starts the animator, it might start listening for some events from the system.
+ * Applying animation should be done only when animator is started.
+ * Animator could be started/stopped several times.
+ */
+ default void start() {}
+
+ /**
+ * Stops the animator, it could unsubscribe from system events.
+ */
+ default void stop() {}
+
+ /**
+ * If this method returns true then task updates will be propagated to
+ * the animator using the onTaskAppeared/Changed/Vanished callbacks.
+ * @return true if this task should be animated by this animator
+ */
+ default boolean isApplicableTask(TaskInfo taskInfo) {
+ return false;
+ }
+
+ /**
+ * Called whenever a task applicable to this animator appeared
+ * (isApplicableTask returns true for this task)
+ *
+ * @param taskInfo info of the appeared task
+ * @param leash surface of the task
+ */
+ default void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {}
+
+ /**
+ * Called whenever a task applicable to this animator changed
+ * @param taskInfo info of the changed task
+ */
+ default void onTaskChanged(TaskInfo taskInfo) {}
+
+ /**
+ * Called whenever a task applicable to this animator vanished
+ * @param taskInfo info of the vanished task
+ */
+ default void onTaskVanished(TaskInfo taskInfo) {}
+
+ /**
+ * @return true if there tasks that could be potentially animated
+ */
+ default boolean hasActiveTasks() {
+ return false;
+ }
+
+ /**
+ * Clears all registered tasks in the animator
+ */
+ default void clearTasks() {}
+
+ /**
+ * Apply task surfaces transformations based on the current unfold progress
+ * @param progress unfold transition progress
+ * @param transaction to write changes to
+ */
+ default void applyAnimationProgress(float progress, Transaction transaction) {}
+
+ /**
+ * Apply task surfaces transformations that should be set before starting the animation
+ * @param transaction to write changes to
+ */
+ default void prepareStartTransaction(Transaction transaction) {}
+
+ /**
+ * Apply task surfaces transformations that should be set after finishing the animation
+ * @param transaction to write changes to
+ */
+ default void prepareFinishTransaction(Transaction transaction) {}
+
+ /**
+ * Resets task surface to its initial transformation
+ * @param transaction to write changes to
+ */
+ default void resetSurface(TaskInfo taskInfo, Transaction transaction) {}
+
+ /**
+ * Resets all task surfaces to their initial transformations
+ * @param transaction to write changes to
+ */
+ default void resetAllSurfaces(Transaction transaction) {}
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java
new file mode 100644
index 000000000000..4c868305dcdb
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldShellTransition.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold.qualifier;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * Indicates that this class is used for the shell unfold transition
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UnfoldShellTransition {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java
new file mode 100644
index 000000000000..4d2b3e6f899b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/qualifier/UnfoldTransition.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold.qualifier;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+import javax.inject.Qualifier;
+
+/**
+ * Indicates that this class is used for unfold transition implemented
+ * without using Shell transitions
+ */
+@Qualifier
+@Retention(RetentionPolicy.RUNTIME)
+public @interface UnfoldTransition {}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
index 603d05d78fc0..2cff1714aff6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/GroupedRecentTaskInfo.java
@@ -30,7 +30,7 @@ import androidx.annotation.Nullable;
public class GroupedRecentTaskInfo implements Parcelable {
public @NonNull ActivityManager.RecentTaskInfo mTaskInfo1;
public @Nullable ActivityManager.RecentTaskInfo mTaskInfo2;
- public @Nullable StagedSplitBounds mStagedSplitBounds;
+ public @Nullable SplitBounds mSplitBounds;
public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1) {
this(task1, null, null);
@@ -38,24 +38,24 @@ public class GroupedRecentTaskInfo implements Parcelable {
public GroupedRecentTaskInfo(@NonNull ActivityManager.RecentTaskInfo task1,
@Nullable ActivityManager.RecentTaskInfo task2,
- @Nullable StagedSplitBounds stagedSplitBounds) {
+ @Nullable SplitBounds splitBounds) {
mTaskInfo1 = task1;
mTaskInfo2 = task2;
- mStagedSplitBounds = stagedSplitBounds;
+ mSplitBounds = splitBounds;
}
GroupedRecentTaskInfo(Parcel parcel) {
mTaskInfo1 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
mTaskInfo2 = parcel.readTypedObject(ActivityManager.RecentTaskInfo.CREATOR);
- mStagedSplitBounds = parcel.readTypedObject(StagedSplitBounds.CREATOR);
+ mSplitBounds = parcel.readTypedObject(SplitBounds.CREATOR);
}
@Override
public String toString() {
String taskString = "Task1: " + getTaskInfo(mTaskInfo1)
+ ", Task2: " + getTaskInfo(mTaskInfo2);
- if (mStagedSplitBounds != null) {
- taskString += ", SplitBounds: " + mStagedSplitBounds.toString();
+ if (mSplitBounds != null) {
+ taskString += ", SplitBounds: " + mSplitBounds.toString();
}
return taskString;
}
@@ -76,7 +76,7 @@ public class GroupedRecentTaskInfo implements Parcelable {
public void writeToParcel(Parcel parcel, int flags) {
parcel.writeTypedObject(mTaskInfo1, flags);
parcel.writeTypedObject(mTaskInfo2, flags);
- parcel.writeTypedObject(mStagedSplitBounds, flags);
+ parcel.writeTypedObject(mSplitBounds, flags);
}
@Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index a0c84cc33ebd..e90389764af3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/StagedSplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -25,7 +25,7 @@ import java.util.Objects;
* Container of various information needed to display split screen
* tasks/leashes/etc in Launcher
*/
-public class StagedSplitBounds implements Parcelable {
+public class SplitBounds implements Parcelable {
public final Rect leftTopBounds;
public final Rect rightBottomBounds;
/** This rect represents the actual gap between the two apps */
@@ -43,7 +43,7 @@ public class StagedSplitBounds implements Parcelable {
public final int leftTopTaskId;
public final int rightBottomTaskId;
- public StagedSplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
+ public SplitBounds(Rect leftTopBounds, Rect rightBottomBounds,
int leftTopTaskId, int rightBottomTaskId) {
this.leftTopBounds = leftTopBounds;
this.rightBottomBounds = rightBottomBounds;
@@ -66,7 +66,7 @@ public class StagedSplitBounds implements Parcelable {
topTaskPercent = this.leftTopBounds.height() / (float) rightBottomBounds.bottom;
}
- public StagedSplitBounds(Parcel parcel) {
+ public SplitBounds(Parcel parcel) {
leftTopBounds = parcel.readTypedObject(Rect.CREATOR);
rightBottomBounds = parcel.readTypedObject(Rect.CREATOR);
visualDividerBounds = parcel.readTypedObject(Rect.CREATOR);
@@ -96,11 +96,11 @@ public class StagedSplitBounds implements Parcelable {
@Override
public boolean equals(Object obj) {
- if (!(obj instanceof StagedSplitBounds)) {
+ if (!(obj instanceof SplitBounds)) {
return false;
}
// Only need to check the base fields (the other fields are derived from these)
- final StagedSplitBounds other = (StagedSplitBounds) obj;
+ final SplitBounds other = (SplitBounds) obj;
return Objects.equals(leftTopBounds, other.leftTopBounds)
&& Objects.equals(rightBottomBounds, other.rightBottomBounds)
&& leftTopTaskId == other.leftTopTaskId
@@ -120,15 +120,15 @@ public class StagedSplitBounds implements Parcelable {
+ "AppsVertical? " + appsStackedVertically;
}
- public static final Creator<StagedSplitBounds> CREATOR = new Creator<StagedSplitBounds>() {
+ public static final Creator<SplitBounds> CREATOR = new Creator<SplitBounds>() {
@Override
- public StagedSplitBounds createFromParcel(Parcel in) {
- return new StagedSplitBounds(in);
+ public SplitBounds createFromParcel(Parcel in) {
+ return new SplitBounds(in);
}
@Override
- public StagedSplitBounds[] newArray(int size) {
- return new StagedSplitBounds[size];
+ public SplitBounds[] newArray(int size) {
+ return new SplitBounds[size];
}
};
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
new file mode 100644
index 000000000000..fe3724b4ec1b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.ActivityTaskManager;
+import android.content.Context;
+import android.os.Handler;
+import android.view.Choreographer;
+import android.view.MotionEvent;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerToken;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * View model for the window decoration with a caption and shadows. Works with
+ * {@link CaptionWindowDecoration}.
+ */
+public class CaptionWindowDecorViewModel implements WindowDecorViewModel<CaptionWindowDecoration> {
+ private final ActivityTaskManager mActivityTaskManager;
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final Context mContext;
+ private final Handler mMainHandler;
+ private final Choreographer mMainChoreographer;
+ private final DisplayController mDisplayController;
+ private final SyncTransactionQueue mSyncQueue;
+
+ public CaptionWindowDecorViewModel(
+ Context context,
+ Handler mainHandler,
+ Choreographer mainChoreographer,
+ ShellTaskOrganizer taskOrganizer,
+ DisplayController displayController,
+ SyncTransactionQueue syncQueue) {
+ mContext = context;
+ mMainHandler = mainHandler;
+ mMainChoreographer = mainChoreographer;
+ mActivityTaskManager = mContext.getSystemService(ActivityTaskManager.class);
+ mTaskOrganizer = taskOrganizer;
+ mDisplayController = displayController;
+ mSyncQueue = syncQueue;
+ }
+
+ @Override
+ public CaptionWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface) {
+ final CaptionWindowDecoration windowDecoration = new CaptionWindowDecoration(
+ mContext,
+ mDisplayController,
+ mTaskOrganizer,
+ taskInfo,
+ taskSurface,
+ mMainHandler,
+ mMainChoreographer,
+ mSyncQueue);
+ TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+ CaptionTouchEventListener touchEventListener =
+ new CaptionTouchEventListener(taskInfo, taskPositioner);
+ windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
+ windowDecoration.setDragResizeCallback(taskPositioner);
+ onTaskInfoChanged(taskInfo, windowDecoration);
+ return windowDecoration;
+ }
+
+ @Override
+ public void onTaskInfoChanged(RunningTaskInfo taskInfo, CaptionWindowDecoration decoration) {
+ decoration.relayout(taskInfo);
+
+ int statusBarColor = taskInfo.taskDescription.getStatusBarColor();
+ decoration.setCaptionColor(statusBarColor);
+ }
+
+ private class CaptionTouchEventListener implements
+ View.OnClickListener, View.OnTouchListener {
+
+ private final int mTaskId;
+ private final WindowContainerToken mTaskToken;
+ private final DragResizeCallback mDragResizeCallback;
+
+ private int mDragPointerId = -1;
+
+ private CaptionTouchEventListener(
+ RunningTaskInfo taskInfo, DragResizeCallback dragResizeCallback) {
+ mTaskId = taskInfo.taskId;
+ mTaskToken = taskInfo.token;
+ mDragResizeCallback = dragResizeCallback;
+ }
+
+ @Override
+ public void onClick(View v) {
+ int id = v.getId();
+ if (id == R.id.close_window) {
+ mActivityTaskManager.removeTask(mTaskId);
+ } else if (id == R.id.maximize_window) {
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ int targetWindowingMode = taskInfo.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
+ ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_FREEFORM;
+ int displayWindowingMode =
+ taskInfo.configuration.windowConfiguration.getDisplayWindowingMode();
+ wct.setWindowingMode(mTaskToken,
+ targetWindowingMode == displayWindowingMode
+ ? WINDOWING_MODE_UNDEFINED : targetWindowingMode);
+ if (targetWindowingMode == WINDOWING_MODE_FULLSCREEN) {
+ wct.setBounds(mTaskToken, null);
+ }
+ mSyncQueue.queue(wct);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent e) {
+ if (v.getId() != R.id.caption) {
+ return false;
+ }
+ handleEventForMove(e);
+
+ if (e.getAction() != MotionEvent.ACTION_DOWN) {
+ return false;
+ }
+ RunningTaskInfo taskInfo = mTaskOrganizer.getRunningTaskInfo(mTaskId);
+ if (taskInfo.isFocused) {
+ return false;
+ }
+ WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.reorder(mTaskToken, true /* onTop */);
+ mSyncQueue.queue(wct);
+ return true;
+ }
+
+ private void handleEventForMove(MotionEvent e) {
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN:
+ mDragPointerId = e.getPointerId(0);
+ mDragResizeCallback.onDragResizeStart(
+ 0 /* ctrlType */, e.getRawX(0), e.getRawY(0));
+ break;
+ case MotionEvent.ACTION_MOVE: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragResizeCallback.onDragResizeMove(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ break;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ int dragPointerIdx = e.findPointerIndex(mDragPointerId);
+ mDragResizeCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIdx), e.getRawY(dragPointerIdx));
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
new file mode 100644
index 000000000000..2df71041595d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.app.ActivityManager;
+import android.app.WindowConfiguration;
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.VectorDrawable;
+import android.os.Handler;
+import android.view.Choreographer;
+import android.view.SurfaceControl;
+import android.view.View;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.R;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.SyncTransactionQueue;
+
+/**
+ * Defines visuals and behaviors of a window decoration of a caption bar and shadows. It works with
+ * {@link CaptionWindowDecorViewModel}. The caption bar contains maximize and close buttons.
+ *
+ * {@link CaptionWindowDecorViewModel} can change the color of the caption bar based on the foremost
+ * app's request through {@link #setCaptionColor(int)}, in which it changes the foreground color of
+ * caption buttons according to the luminance of the background.
+ *
+ * The shadow's thickness is 20dp when the window is in focus and 5dp when the window isn't.
+ */
+public class CaptionWindowDecoration extends WindowDecoration<WindowDecorLinearLayout> {
+ // The thickness of shadows of a window that has focus in DIP.
+ private static final int DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP = 20;
+ // The thickness of shadows of a window that doesn't have focus in DIP.
+ private static final int DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP = 5;
+
+ // Height of button (32dp) + 2 * margin (5dp each)
+ private static final int DECOR_CAPTION_HEIGHT_IN_DIP = 42;
+ private static final int RESIZE_HANDLE_IN_DIP = 30;
+
+ private static final Rect EMPTY_OUTSET = new Rect();
+ private static final Rect RESIZE_HANDLE_OUTSET = new Rect(
+ RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP, RESIZE_HANDLE_IN_DIP);
+
+ private final Handler mHandler;
+ private final Choreographer mChoreographer;
+ private final SyncTransactionQueue mSyncQueue;
+
+ private View.OnClickListener mOnCaptionButtonClickListener;
+ private View.OnTouchListener mOnCaptionTouchListener;
+ private DragResizeCallback mDragResizeCallback;
+
+ private DragResizeInputListener mDragResizeListener;
+
+ private final WindowDecoration.RelayoutResult<WindowDecorLinearLayout> mResult =
+ new WindowDecoration.RelayoutResult<>();
+
+ CaptionWindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Handler handler,
+ Choreographer choreographer,
+ SyncTransactionQueue syncQueue) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface);
+
+ mHandler = handler;
+ mChoreographer = choreographer;
+ mSyncQueue = syncQueue;
+ }
+
+ void setCaptionListeners(
+ View.OnClickListener onCaptionButtonClickListener,
+ View.OnTouchListener onCaptionTouchListener) {
+ mOnCaptionButtonClickListener = onCaptionButtonClickListener;
+ mOnCaptionTouchListener = onCaptionTouchListener;
+ }
+
+ void setDragResizeCallback(DragResizeCallback dragResizeCallback) {
+ mDragResizeCallback = dragResizeCallback;
+ }
+
+ @Override
+ void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ final int shadowRadiusDp = taskInfo.isFocused
+ ? DECOR_SHADOW_FOCUSED_THICKNESS_IN_DIP : DECOR_SHADOW_UNFOCUSED_THICKNESS_IN_DIP;
+ final boolean isFreeform = mTaskInfo.configuration.windowConfiguration.getWindowingMode()
+ == WindowConfiguration.WINDOWING_MODE_FREEFORM;
+ final boolean isDragResizeable = isFreeform && mTaskInfo.isResizeable;
+ final Rect outset = isDragResizeable ? RESIZE_HANDLE_OUTSET : EMPTY_OUTSET;
+
+ WindowDecorLinearLayout oldRootView = mResult.mRootView;
+ final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
+ final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ relayout(taskInfo, R.layout.caption_window_decoration, oldRootView,
+ DECOR_CAPTION_HEIGHT_IN_DIP, outset, shadowRadiusDp, t, wct, mResult);
+ taskInfo = null; // Clear it just in case we use it accidentally
+
+ mSyncQueue.runInSync(transaction -> {
+ transaction.merge(t);
+ t.close();
+
+ mTaskOrganizer.applyTransaction(wct);
+ });
+
+ if (mResult.mRootView == null) {
+ // This means something blocks the window decor from showing, e.g. the task is hidden.
+ // Nothing is set up in this case including the decoration surface.
+ return;
+ }
+ if (oldRootView != mResult.mRootView) {
+ setupRootView();
+ }
+
+ if (!isDragResizeable) {
+ closeDragResizeListener();
+ return;
+ }
+
+ if (oldDecorationSurface != mDecorationContainerSurface) {
+ closeDragResizeListener();
+ mDragResizeListener = new DragResizeInputListener(
+ mContext,
+ mHandler,
+ mChoreographer,
+ mDisplay.getDisplayId(),
+ mDecorationContainerSurface,
+ mDragResizeCallback);
+ }
+
+ mDragResizeListener.setGeometry(
+ mResult.mWidth, mResult.mHeight, (int) (mResult.mDensity * RESIZE_HANDLE_IN_DIP));
+ }
+
+ /**
+ * Sets up listeners when a new root view is created.
+ */
+ private void setupRootView() {
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+
+ caption.setOnTouchListener(mOnCaptionTouchListener);
+ View maximize = caption.findViewById(R.id.maximize_window);
+ maximize.setOnClickListener(mOnCaptionButtonClickListener);
+ View close = caption.findViewById(R.id.close_window);
+ close.setOnClickListener(mOnCaptionButtonClickListener);
+ }
+
+ void setCaptionColor(int captionColor) {
+ if (mResult.mRootView == null) {
+ return;
+ }
+
+ View caption = mResult.mRootView.findViewById(R.id.caption);
+ GradientDrawable captionDrawable = (GradientDrawable) caption.getBackground();
+ captionDrawable.setColor(captionColor);
+
+ int buttonTintColorRes =
+ Color.valueOf(captionColor).luminance() < 0.5
+ ? R.color.decor_button_light_color
+ : R.color.decor_button_dark_color;
+ ColorStateList buttonTintColor =
+ caption.getResources().getColorStateList(buttonTintColorRes, null /* theme */);
+ View maximize = caption.findViewById(R.id.maximize_window);
+ VectorDrawable maximizeBackground = (VectorDrawable) maximize.getBackground();
+ maximizeBackground.setTintList(buttonTintColor);
+
+ View close = caption.findViewById(R.id.close_window);
+ VectorDrawable closeBackground = (VectorDrawable) close.getBackground();
+ closeBackground.setTintList(buttonTintColor);
+ }
+
+ private void closeDragResizeListener() {
+ if (mDragResizeListener == null) {
+ return;
+ }
+ mDragResizeListener.close();
+ mDragResizeListener = null;
+ }
+
+ @Override
+ public void close() {
+ closeDragResizeListener();
+ super.close();
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
new file mode 100644
index 000000000000..ee160a15df19
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeCallback.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+/**
+ * Callback called when receiving drag-resize or drag-move related input events.
+ */
+public interface DragResizeCallback {
+ /**
+ * Called when a drag resize starts.
+ *
+ * @param ctrlType {@link TaskPositioner.CtrlType} indicating the direction of resizing, use
+ * {@code 0} to indicate it's a move
+ * @param x x coordinate in window decoration coordinate system where the drag resize starts
+ * @param y y coordinate in window decoration coordinate system where the drag resize starts
+ */
+ void onDragResizeStart(@TaskPositioner.CtrlType int ctrlType, float x, float y);
+
+ /**
+ * Called when the pointer moves during a drag resize.
+ * @param x x coordinate in window decoration coordinate system of the new pointer location
+ * @param y y coordinate in window decoration coordinate system of the new pointer location
+ */
+ void onDragResizeMove(float x, float y);
+
+ /**
+ * Called when a drag resize stops.
+ * @param x x coordinate in window decoration coordinate system where the drag resize stops
+ * @param y y coordinate in window decoration coordinate system where the drag resize stops
+ */
+ void onDragResizeEnd(float x, float y);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
new file mode 100644
index 000000000000..f512b0d4fe10
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.view.Choreographer;
+import android.view.IWindowSession;
+import android.view.InputChannel;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.MotionEvent;
+import android.view.PointerIcon;
+import android.view.SurfaceControl;
+import android.view.WindowManagerGlobal;
+
+import com.android.internal.view.BaseIWindow;
+
+/**
+ * An input event listener registered to InputDispatcher to receive input events on task edges and
+ * convert them to drag resize requests.
+ */
+class DragResizeInputListener implements AutoCloseable {
+ private static final String TAG = "DragResizeInputListener";
+
+ private final IWindowSession mWindowSession = WindowManagerGlobal.getWindowSession();
+ private final Handler mHandler;
+ private final Choreographer mChoreographer;
+ private final InputManager mInputManager;
+
+ private final int mDisplayId;
+ private final BaseIWindow mFakeWindow;
+ private final IBinder mFocusGrantToken;
+ private final SurfaceControl mDecorationSurface;
+ private final InputChannel mInputChannel;
+ private final TaskResizeInputEventReceiver mInputEventReceiver;
+ private final com.android.wm.shell.windowdecor.DragResizeCallback mCallback;
+
+ private int mWidth;
+ private int mHeight;
+ private int mResizeHandleThickness;
+
+ private int mDragPointerId = -1;
+
+ DragResizeInputListener(
+ Context context,
+ Handler handler,
+ Choreographer choreographer,
+ int displayId,
+ SurfaceControl decorationSurface,
+ DragResizeCallback callback) {
+ mInputManager = context.getSystemService(InputManager.class);
+ mHandler = handler;
+ mChoreographer = choreographer;
+ mDisplayId = displayId;
+ mDecorationSurface = decorationSurface;
+ // Use a fake window as the backing surface is a container layer and we don't want to create
+ // a buffer layer for it so we can't use ViewRootImpl.
+ mFakeWindow = new BaseIWindow();
+ mFakeWindow.setSession(mWindowSession);
+ mFocusGrantToken = new Binder();
+ mInputChannel = new InputChannel();
+ try {
+ mWindowSession.grantInputChannel(
+ mDisplayId,
+ new SurfaceControl(mDecorationSurface, TAG),
+ mFakeWindow,
+ null /* hostInputToken */,
+ FLAG_NOT_FOCUSABLE,
+ PRIVATE_FLAG_TRUSTED_OVERLAY,
+ TYPE_APPLICATION,
+ mFocusGrantToken,
+ TAG + " of " + decorationSurface.toString(),
+ mInputChannel);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+
+ mInputEventReceiver = new TaskResizeInputEventReceiver(
+ mInputChannel, mHandler, mChoreographer);
+ mCallback = callback;
+ }
+
+ /**
+ * Updates geometry of this drag resize handler. Needs to be called every time there is a size
+ * change to notify the input event receiver it's ready to take the next input event. Otherwise
+ * it'll keep batching move events and the drag resize process is stalled.
+ *
+ * This is also used to update the touch regions of this handler every event dispatched here is
+ * a potential resize request.
+ *
+ * @param width The width of the drag resize handler in pixels, including resize handle
+ * thickness. That is task width + 2 * resize handle thickness.
+ * @param height The height of the drag resize handler in pixels, including resize handle
+ * thickness. That is task height + 2 * resize handle thickness.
+ * @param resizeHandleThickness The thickness of the resize handle in pixels.
+ */
+ void setGeometry(int width, int height, int resizeHandleThickness) {
+ if (mWidth == width && mHeight == height
+ && mResizeHandleThickness == resizeHandleThickness) {
+ return;
+ }
+
+ mWidth = width;
+ mHeight = height;
+ mResizeHandleThickness = resizeHandleThickness;
+
+ Region touchRegion = new Region();
+ final Rect topInputBounds = new Rect(0, 0, mWidth, mResizeHandleThickness);
+ touchRegion.union(topInputBounds);
+
+ final Rect leftInputBounds = new Rect(0, mResizeHandleThickness,
+ mResizeHandleThickness, mHeight - mResizeHandleThickness);
+ touchRegion.union(leftInputBounds);
+
+ final Rect rightInputBounds = new Rect(
+ mWidth - mResizeHandleThickness, mResizeHandleThickness,
+ mWidth, mHeight - mResizeHandleThickness);
+ touchRegion.union(rightInputBounds);
+
+ final Rect bottomInputBounds = new Rect(0, mHeight - mResizeHandleThickness,
+ mWidth, mHeight);
+ touchRegion.union(bottomInputBounds);
+
+ try {
+ mWindowSession.updateInputChannel(
+ mInputChannel.getToken(),
+ mDisplayId,
+ new SurfaceControl(
+ mDecorationSurface, "DragResizeInputListener#setTouchRegion"),
+ FLAG_NOT_FOCUSABLE,
+ PRIVATE_FLAG_TRUSTED_OVERLAY,
+ touchRegion);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public void close() {
+ mInputChannel.dispose();
+ try {
+ mWindowSession.remove(mFakeWindow);
+ } catch (RemoteException e) {
+ e.rethrowFromSystemServer();
+ }
+ }
+
+ private class TaskResizeInputEventReceiver extends InputEventReceiver {
+ private final Choreographer mChoreographer;
+ private final Runnable mConsumeBatchEventRunnable;
+ private boolean mConsumeBatchEventScheduled;
+
+ private TaskResizeInputEventReceiver(
+ InputChannel inputChannel, Handler handler, Choreographer choreographer) {
+ super(inputChannel, handler.getLooper());
+ mChoreographer = choreographer;
+
+ mConsumeBatchEventRunnable = () -> {
+ mConsumeBatchEventScheduled = false;
+ if (consumeBatchedInputEvents(mChoreographer.getFrameTimeNanos())) {
+ // If we consumed a batch here, we want to go ahead and schedule the
+ // consumption of batched input events on the next frame. Otherwise, we would
+ // wait until we have more input events pending and might get starved by other
+ // things occurring in the process.
+ scheduleConsumeBatchEvent();
+ }
+ };
+ }
+
+ @Override
+ public void onBatchedInputEventPending(int source) {
+ scheduleConsumeBatchEvent();
+ }
+
+ private void scheduleConsumeBatchEvent() {
+ if (mConsumeBatchEventScheduled) {
+ return;
+ }
+ mChoreographer.postCallback(
+ Choreographer.CALLBACK_INPUT, mConsumeBatchEventRunnable, null);
+ mConsumeBatchEventScheduled = true;
+ }
+
+ @Override
+ public void onInputEvent(InputEvent inputEvent) {
+ finishInputEvent(inputEvent, handleInputEvent(inputEvent));
+ }
+
+ private boolean handleInputEvent(InputEvent inputEvent) {
+ if (!(inputEvent instanceof MotionEvent)) {
+ return false;
+ }
+
+ MotionEvent e = (MotionEvent) inputEvent;
+ switch (e.getActionMasked()) {
+ case MotionEvent.ACTION_DOWN: {
+ mDragPointerId = e.getPointerId(0);
+ mCallback.onDragResizeStart(
+ calculateCtrlType(e.getX(0), e.getY(0)), e.getRawX(0), e.getRawY(0));
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeMove(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ break;
+ }
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_CANCEL: {
+ int dragPointerIndex = e.findPointerIndex(mDragPointerId);
+ mCallback.onDragResizeEnd(
+ e.getRawX(dragPointerIndex), e.getRawY(dragPointerIndex));
+ mDragPointerId = -1;
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_ENTER:
+ case MotionEvent.ACTION_HOVER_MOVE: {
+ updateCursorType(e.getXCursorPosition(), e.getYCursorPosition());
+ break;
+ }
+ case MotionEvent.ACTION_HOVER_EXIT:
+ mInputManager.setPointerIconType(PointerIcon.TYPE_DEFAULT);
+ break;
+ }
+ return true;
+ }
+
+ @TaskPositioner.CtrlType
+ private int calculateCtrlType(float x, float y) {
+ int ctrlType = 0;
+ if (x < mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_LEFT;
+ }
+ if (x > mWidth - mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_RIGHT;
+ }
+ if (y < mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_TOP;
+ }
+ if (y > mHeight - mResizeHandleThickness) {
+ ctrlType |= TaskPositioner.CTRL_TYPE_BOTTOM;
+ }
+ return ctrlType;
+ }
+
+ private void updateCursorType(float x, float y) {
+ @TaskPositioner.CtrlType int ctrlType = calculateCtrlType(x, y);
+
+ int cursorType = PointerIcon.TYPE_DEFAULT;
+ switch (ctrlType) {
+ case TaskPositioner.CTRL_TYPE_LEFT:
+ case TaskPositioner.CTRL_TYPE_RIGHT:
+ cursorType = PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
+ break;
+ case TaskPositioner.CTRL_TYPE_TOP:
+ case TaskPositioner.CTRL_TYPE_BOTTOM:
+ cursorType = PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
+ break;
+ case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_TOP:
+ case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_BOTTOM:
+ cursorType = PointerIcon.TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW;
+ break;
+ case TaskPositioner.CTRL_TYPE_LEFT | TaskPositioner.CTRL_TYPE_BOTTOM:
+ case TaskPositioner.CTRL_TYPE_RIGHT | TaskPositioner.CTRL_TYPE_TOP:
+ cursorType = PointerIcon.TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW;
+ break;
+ }
+ mInputManager.setPointerIconType(cursorType);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java
index d7010b174744..1c61802bbd5c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskFocusStateConsumer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,17 +14,8 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.wm.shell.windowdecor;
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-/**
- * An entry point into the shell for initializing shell internal state.
- */
-@ExternalThread
-public interface ShellInit {
- /**
- * Initializes the shell state.
- */
- void init();
+interface TaskFocusStateConsumer {
+ void setTaskFocusState(boolean focused);
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
new file mode 100644
index 000000000000..280569b05d87
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.annotation.IntDef;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+
+class TaskPositioner implements DragResizeCallback {
+
+ @IntDef({CTRL_TYPE_LEFT, CTRL_TYPE_RIGHT, CTRL_TYPE_TOP, CTRL_TYPE_BOTTOM})
+ @interface CtrlType {}
+
+ static final int CTRL_TYPE_LEFT = 1;
+ static final int CTRL_TYPE_RIGHT = 2;
+ static final int CTRL_TYPE_TOP = 4;
+ static final int CTRL_TYPE_BOTTOM = 8;
+
+ private final ShellTaskOrganizer mTaskOrganizer;
+ private final WindowDecoration mWindowDecoration;
+
+ private final Rect mTaskBoundsAtDragStart = new Rect();
+ private final PointF mResizeStartPoint = new PointF();
+ private final Rect mResizeTaskBounds = new Rect();
+
+ private int mCtrlType;
+
+ TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+ mTaskOrganizer = taskOrganizer;
+ mWindowDecoration = windowDecoration;
+ }
+
+ @Override
+ public void onDragResizeStart(int ctrlType, float x, float y) {
+ mCtrlType = ctrlType;
+
+ mTaskBoundsAtDragStart.set(
+ mWindowDecoration.mTaskInfo.configuration.windowConfiguration.getBounds());
+ mResizeStartPoint.set(x, y);
+ }
+
+ @Override
+ public void onDragResizeMove(float x, float y) {
+ changeBounds(x, y);
+ }
+
+ @Override
+ public void onDragResizeEnd(float x, float y) {
+ changeBounds(x, y);
+
+ mCtrlType = 0;
+ mTaskBoundsAtDragStart.setEmpty();
+ mResizeStartPoint.set(0, 0);
+ }
+
+ private void changeBounds(float x, float y) {
+ float deltaX = x - mResizeStartPoint.x;
+ mResizeTaskBounds.set(mTaskBoundsAtDragStart);
+ if ((mCtrlType & CTRL_TYPE_LEFT) != 0) {
+ mResizeTaskBounds.left += deltaX;
+ }
+ if ((mCtrlType & CTRL_TYPE_RIGHT) != 0) {
+ mResizeTaskBounds.right += deltaX;
+ }
+ float deltaY = y - mResizeStartPoint.y;
+ if ((mCtrlType & CTRL_TYPE_TOP) != 0) {
+ mResizeTaskBounds.top += deltaY;
+ }
+ if ((mCtrlType & CTRL_TYPE_BOTTOM) != 0) {
+ mResizeTaskBounds.bottom += deltaY;
+ }
+ if (mCtrlType == 0) {
+ mResizeTaskBounds.offset((int) deltaX, (int) deltaY);
+ }
+
+ if (!mResizeTaskBounds.isEmpty()) {
+ final WindowContainerTransaction wct = new WindowContainerTransaction();
+ wct.setBounds(mWindowDecoration.mTaskInfo.token, mResizeTaskBounds);
+ mTaskOrganizer.applyTransaction(wct);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java
new file mode 100644
index 000000000000..6d8001a2f92b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorLinearLayout.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+import androidx.annotation.Nullable;
+
+import com.android.wm.shell.R;
+
+/**
+ * A {@link LinearLayout} that takes an additional task focused drawable state. The new state is
+ * used to select the correct background color for views in the window decoration.
+ */
+public class WindowDecorLinearLayout extends LinearLayout implements TaskFocusStateConsumer {
+ private static final int[] TASK_FOCUSED_STATE = { R.attr.state_task_focused };
+
+ private boolean mIsTaskFocused;
+
+ public WindowDecorLinearLayout(Context context) {
+ super(context);
+ }
+
+ public WindowDecorLinearLayout(Context context,
+ @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public WindowDecorLinearLayout(Context context, @Nullable AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public WindowDecorLinearLayout(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ public void setTaskFocusState(boolean focused) {
+ mIsTaskFocused = focused;
+
+ refreshDrawableState();
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ if (!mIsTaskFocused) {
+ return super.onCreateDrawableState(extraSpace);
+ }
+
+ final int[] states = super.onCreateDrawableState(extraSpace + 1);
+ mergeDrawableStates(states, TASK_FOCUSED_STATE);
+ return states;
+ }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
new file mode 100644
index 000000000000..6f9ceff722ac
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecorViewModel.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.app.ActivityManager;
+import android.view.SurfaceControl;
+
+/**
+ * The interface used by some {@link com.android.wm.shell.ShellTaskOrganizer.TaskListener} to help
+ * customize {@link WindowDecoration}. Its implementations are responsible to interpret user's
+ * interactions with UI widgets in window decorations and send corresponding requests to system
+ * servers.
+ *
+ * @param <T> The actual decoration type
+ */
+public interface WindowDecorViewModel<T extends AutoCloseable> {
+
+ /**
+ * Creates a window decoration for the given task.
+ *
+ * @param taskInfo the initial task info of the task
+ * @param taskSurface the surface of the task
+ * @return the window decoration object
+ */
+ T createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo, SurfaceControl taskSurface);
+
+ /**
+ * Notifies a task info update on the given task, with the window decoration created previously
+ * for this task by {@link #createWindowDecoration}.
+ *
+ * @param taskInfo the new task info of the task
+ * @param windowDecoration the window decoration created for the task
+ */
+ void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo, T windowDecoration);
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
new file mode 100644
index 000000000000..4855fbd5ba7a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -0,0 +1,341 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Color;
+import android.graphics.PixelFormat;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.LayoutInflater;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager;
+import android.view.WindowlessWindowManager;
+import android.window.WindowContainerTransaction;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+
+import java.util.function.Supplier;
+
+/**
+ * Manages a container surface and a windowless window to show window decoration. Responsible to
+ * update window decoration window state and layout parameters on task info changes and so that
+ * window decoration is in correct state and bounds.
+ *
+ * The container surface is a child of the task display area in the same display, so that window
+ * decorations can be drawn out of the task bounds and receive input events from out of the task
+ * bounds to support drag resizing.
+ *
+ * The windowless window that hosts window decoration is positioned in front of all activities, to
+ * allow the foreground activity to draw its own background behind window decorations, such as
+ * the window captions.
+ *
+ * @param <T> The type of the root view
+ */
+public abstract class WindowDecoration<T extends View & TaskFocusStateConsumer>
+ implements AutoCloseable {
+ private static final int[] CAPTION_INSETS_TYPES = { InsetsState.ITYPE_CAPTION_BAR };
+
+ /**
+ * System-wide context. Only used to create context with overridden configurations.
+ */
+ final Context mContext;
+ final DisplayController mDisplayController;
+ final ShellTaskOrganizer mTaskOrganizer;
+ final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
+ final SurfaceControlViewHostFactory mSurfaceControlViewHostFactory;
+ private final DisplayController.OnDisplaysChangedListener mOnDisplaysChangedListener =
+ new DisplayController.OnDisplaysChangedListener() {
+ @Override
+ public void onDisplayAdded(int displayId) {
+ if (mTaskInfo.displayId != displayId) {
+ return;
+ }
+
+ mDisplayController.removeDisplayWindowListener(this);
+ relayout(mTaskInfo);
+ }
+ };
+
+ RunningTaskInfo mTaskInfo;
+ final SurfaceControl mTaskSurface;
+
+ Display mDisplay;
+ Context mDecorWindowContext;
+ SurfaceControl mDecorationContainerSurface;
+ SurfaceControl mTaskBackgroundSurface;
+
+ private final CaptionWindowManager mCaptionWindowManager;
+ private SurfaceControlViewHost mViewHost;
+
+ private final Rect mCaptionInsetsRect = new Rect();
+ private final Rect mTaskSurfaceCrop = new Rect();
+ private final float[] mTmpColor = new float[3];
+
+ WindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface) {
+ this(context, displayController, taskOrganizer, taskInfo, taskSurface,
+ SurfaceControl.Builder::new, new SurfaceControlViewHostFactory() {});
+ }
+
+ WindowDecoration(
+ Context context,
+ DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer,
+ RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ mContext = context;
+ mDisplayController = displayController;
+ mTaskOrganizer = taskOrganizer;
+ mTaskInfo = taskInfo;
+ mTaskSurface = taskSurface;
+ mSurfaceControlBuilderSupplier = surfaceControlBuilderSupplier;
+ mSurfaceControlViewHostFactory = surfaceControlViewHostFactory;
+
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ mDecorWindowContext = mContext.createConfigurationContext(mTaskInfo.getConfiguration());
+
+ // Put caption under task surface because ViewRootImpl sets the destination frame of
+ // windowless window layers and BLASTBufferQueue#update() doesn't support offset.
+ mCaptionWindowManager =
+ new CaptionWindowManager(mTaskInfo.getConfiguration(), mTaskSurface);
+ }
+
+ /**
+ * Used by {@link WindowDecoration} to trigger a new relayout because the requirements for a
+ * relayout weren't satisfied are satisfied now.
+ *
+ * @param taskInfo The previous {@link RunningTaskInfo} passed into {@link #relayout} or the
+ * constructor.
+ */
+ abstract void relayout(RunningTaskInfo taskInfo);
+
+ void relayout(RunningTaskInfo taskInfo, int layoutResId, T rootView, float captionHeightDp,
+ Rect outsetsDp, float shadowRadiusDp, SurfaceControl.Transaction t,
+ WindowContainerTransaction wct, RelayoutResult<T> outResult) {
+ outResult.reset();
+
+ final Configuration oldTaskConfig = mTaskInfo.getConfiguration();
+ if (taskInfo != null) {
+ mTaskInfo = taskInfo;
+ }
+
+ if (!mTaskInfo.isVisible) {
+ releaseViews();
+ t.hide(mTaskSurface);
+ return;
+ }
+
+ if (rootView == null && layoutResId == 0) {
+ throw new IllegalArgumentException("layoutResId and rootView can't both be invalid.");
+ }
+
+ outResult.mRootView = rootView;
+ rootView = null; // Clear it just in case we use it accidentally
+ final Configuration taskConfig = mTaskInfo.getConfiguration();
+ if (oldTaskConfig.densityDpi != taskConfig.densityDpi
+ || mDisplay == null
+ || mDisplay.getDisplayId() != mTaskInfo.displayId) {
+ releaseViews();
+
+ if (!obtainDisplayOrRegisterListener()) {
+ outResult.mRootView = null;
+ return;
+ }
+ mDecorWindowContext = mContext.createConfigurationContext(taskConfig);
+ if (layoutResId != 0) {
+ outResult.mRootView =
+ (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ }
+ }
+
+ if (outResult.mRootView == null) {
+ outResult.mRootView =
+ (T) LayoutInflater.from(mDecorWindowContext).inflate(layoutResId, null);
+ }
+
+ // DecorationContainerSurface
+ if (mDecorationContainerSurface == null) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ mDecorationContainerSurface = builder
+ .setName("Decor container of Task=" + mTaskInfo.taskId)
+ .setContainerLayer()
+ .setParent(mTaskSurface)
+ .build();
+
+ t.setTrustedOverlay(mDecorationContainerSurface, true);
+ }
+
+ final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
+ outResult.mDensity = taskConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ final int decorContainerOffsetX = -(int) (outsetsDp.left * outResult.mDensity);
+ final int decorContainerOffsetY = -(int) (outsetsDp.top * outResult.mDensity);
+ outResult.mWidth = taskBounds.width()
+ + (int) (outsetsDp.right * outResult.mDensity)
+ - decorContainerOffsetX;
+ outResult.mHeight = taskBounds.height()
+ + (int) (outsetsDp.bottom * outResult.mDensity)
+ - decorContainerOffsetY;
+ t.setPosition(mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+ .setWindowCrop(mDecorationContainerSurface, outResult.mWidth, outResult.mHeight)
+ .setLayer(mDecorationContainerSurface, mTaskInfo.numActivities + 1)
+ .show(mDecorationContainerSurface);
+
+ // TaskBackgroundSurface
+ if (mTaskBackgroundSurface == null) {
+ final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+ mTaskBackgroundSurface = builder
+ .setName("Background of Task=" + mTaskInfo.taskId)
+ .setEffectLayer()
+ .setParent(mTaskSurface)
+ .build();
+ }
+
+ float shadowRadius = outResult.mDensity * shadowRadiusDp;
+ int backgroundColorInt = mTaskInfo.taskDescription.getBackgroundColor();
+ mTmpColor[0] = (float) Color.red(backgroundColorInt) / 255.f;
+ mTmpColor[1] = (float) Color.green(backgroundColorInt) / 255.f;
+ mTmpColor[2] = (float) Color.blue(backgroundColorInt) / 255.f;
+ t.setWindowCrop(mTaskBackgroundSurface, taskBounds.width(), taskBounds.height())
+ .setShadowRadius(mTaskBackgroundSurface, shadowRadius)
+ .setColor(mTaskBackgroundSurface, mTmpColor);
+
+ // Caption view
+ mCaptionWindowManager.setConfiguration(taskConfig);
+ final int captionHeight = (int) Math.ceil(captionHeightDp * outResult.mDensity);
+ final WindowManager.LayoutParams lp =
+ new WindowManager.LayoutParams(taskBounds.width(), captionHeight,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+ lp.setTitle("Caption of Task=" + mTaskInfo.taskId);
+ lp.setTrustedOverlay();
+ if (mViewHost == null) {
+ mViewHost = mSurfaceControlViewHostFactory.create(mDecorWindowContext, mDisplay,
+ mCaptionWindowManager, true);
+ mViewHost.setView(outResult.mRootView, lp);
+ } else {
+ mViewHost.relayout(lp);
+ }
+
+ if (ViewRootImpl.CAPTION_ON_SHELL) {
+ outResult.mRootView.setTaskFocusState(mTaskInfo.isFocused);
+
+ // Caption insets
+ mCaptionInsetsRect.set(taskBounds);
+ mCaptionInsetsRect.bottom = mCaptionInsetsRect.top + captionHeight;
+ wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect, CAPTION_INSETS_TYPES);
+ } else {
+ outResult.mRootView.setVisibility(View.GONE);
+ }
+
+ // Task surface itself
+ Point taskPosition = mTaskInfo.positionInParent;
+ mTaskSurfaceCrop.set(
+ decorContainerOffsetX,
+ decorContainerOffsetY,
+ outResult.mWidth + decorContainerOffsetX,
+ outResult.mHeight + decorContainerOffsetY);
+ t.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
+ .setCrop(mTaskSurface, mTaskSurfaceCrop)
+ .show(mTaskSurface);
+ }
+
+ /**
+ * Obtains the {@link Display} instance for the display ID in {@link #mTaskInfo} if it exists or
+ * registers {@link #mOnDisplaysChangedListener} if it doesn't.
+ *
+ * @return {@code true} if the {@link Display} instance exists; or {@code false} otherwise
+ */
+ private boolean obtainDisplayOrRegisterListener() {
+ mDisplay = mDisplayController.getDisplay(mTaskInfo.displayId);
+ if (mDisplay == null) {
+ mDisplayController.addDisplayWindowListener(mOnDisplaysChangedListener);
+ return false;
+ }
+ return true;
+ }
+
+ private void releaseViews() {
+ if (mViewHost != null) {
+ mViewHost.release();
+ mViewHost = null;
+ }
+
+ if (mDecorationContainerSurface != null) {
+ mDecorationContainerSurface.release();
+ mDecorationContainerSurface = null;
+ }
+
+ if (mTaskBackgroundSurface != null) {
+ mTaskBackgroundSurface.release();
+ mTaskBackgroundSurface = null;
+ }
+ }
+
+ @Override
+ public void close() {
+ mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener);
+ releaseViews();
+ }
+
+ static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
+ int mWidth;
+ int mHeight;
+ float mDensity;
+ T mRootView;
+
+ void reset() {
+ mWidth = 0;
+ mHeight = 0;
+ mDensity = 0;
+ mRootView = null;
+ }
+ }
+
+ private static class CaptionWindowManager extends WindowlessWindowManager {
+ CaptionWindowManager(Configuration config, SurfaceControl rootSurface) {
+ super(config, rootSurface, null /* hostInputToken */);
+ }
+
+ @Override
+ public void setConfiguration(Configuration configuration) {
+ super.setConfiguration(configuration);
+ }
+ }
+
+ interface SurfaceControlViewHostFactory {
+ default SurfaceControlViewHost create(
+ Context c, Display d, WindowlessWindowManager wmm, boolean useSfChoreographer) {
+ return new SurfaceControlViewHost(c, d, wmm, useSfChoreographer);
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index cb478c84c2b7..cba396a82a87 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -43,6 +43,84 @@ fun FlickerTestParameter.appPairsDividerBecomesVisible() {
}
}
+fun FlickerTestParameter.splitScreenDividerBecomesVisible() {
+ layerBecomesVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+}
+
+fun FlickerTestParameter.layerBecomesVisible(
+ component: FlickerComponentName
+) {
+ assertLayers {
+ this.isInvisible(component)
+ .then()
+ .isVisible(component)
+ }
+}
+
+fun FlickerTestParameter.layerIsVisibleAtEnd(
+ component: FlickerComponentName
+) {
+ assertLayersEnd {
+ this.isVisible(component)
+ }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsBecomesVisible(
+ rotation: Int,
+ component: FlickerComponentName,
+ splitLeftTop: Boolean
+) {
+ assertLayers {
+ val dividerRegion = this.last().layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ this.isInvisible(component)
+ .then()
+ .invoke("splitAppLayerBoundsBecomesVisible") {
+ it.visibleRegion(component).overlaps(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, rotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, rotation)
+ }
+ )
+ }
+ }
+}
+
+fun FlickerTestParameter.splitAppLayerBoundsIsVisibleAtEnd(
+ rotation: Int,
+ component: FlickerComponentName,
+ splitLeftTop: Boolean
+) {
+ assertLayersEnd {
+ val dividerRegion = layer(SPLIT_SCREEN_DIVIDER_COMPONENT).visibleRegion.region
+ visibleRegion(component).overlaps(
+ if (splitLeftTop) {
+ getSplitLeftTopRegion(dividerRegion, rotation)
+ } else {
+ getSplitRightBottomRegion(dividerRegion, rotation)
+ }
+ )
+ }
+}
+
+fun FlickerTestParameter.appWindowBecomesVisible(
+ component: FlickerComponentName
+) {
+ assertWm {
+ this.isAppWindowInvisible(component)
+ .then()
+ .isAppWindowVisible(component)
+ }
+}
+
+fun FlickerTestParameter.appWindowIsVisibleAtEnd(
+ component: FlickerComponentName
+) {
+ assertWmEnd {
+ this.isAppWindowVisible(component)
+ }
+}
+
fun FlickerTestParameter.dockedStackDividerIsVisibleAtEnd() {
assertLayersEnd {
this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
@@ -118,21 +196,53 @@ fun FlickerTestParameter.dockedStackSecondaryBoundsIsVisibleAtEnd(
fun getPrimaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(0, 0, displayBounds.bounds.right,
- dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset)
+ Region.from(
+ 0, 0, displayBounds.bounds.right,
+ dividerRegion.bounds.top + WindowUtils.dockedStackDividerInset
+ )
} else {
- Region.from(0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.bottom)
+ Region.from(
+ 0, 0, dividerRegion.bounds.left + WindowUtils.dockedStackDividerInset,
+ displayBounds.bounds.bottom
+ )
}
}
fun getSecondaryRegion(dividerRegion: Region, rotation: Int): Region {
val displayBounds = WindowUtils.getDisplayBounds(rotation)
return if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
- Region.from(0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.bounds.right, displayBounds.bounds.bottom)
+ Region.from(
+ 0, dividerRegion.bounds.bottom - WindowUtils.dockedStackDividerInset,
+ displayBounds.bounds.right, displayBounds.bounds.bottom
+ )
} else {
- Region.from(dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
- displayBounds.bounds.right, displayBounds.bounds.bottom)
+ Region.from(
+ dividerRegion.bounds.right - WindowUtils.dockedStackDividerInset, 0,
+ displayBounds.bounds.right, displayBounds.bounds.bottom
+ )
}
-} \ No newline at end of file
+}
+
+fun getSplitLeftTopRegion(dividerRegion: Region, rotation: Int): Region {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return if (displayBounds.width > displayBounds.height) {
+ Region.from(0, 0, dividerRegion.bounds.left, displayBounds.bounds.bottom)
+ } else {
+ Region.from(0, 0, displayBounds.bounds.right, dividerRegion.bounds.top)
+ }
+}
+
+fun getSplitRightBottomRegion(dividerRegion: Region, rotation: Int): Region {
+ val displayBounds = WindowUtils.getDisplayBounds(rotation)
+ return if (displayBounds.width > displayBounds.height) {
+ Region.from(
+ dividerRegion.bounds.right, 0, displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ } else {
+ Region.from(
+ 0, dividerRegion.bounds.bottom, displayBounds.bounds.right,
+ displayBounds.bounds.bottom
+ )
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 40891f36a5da..f56eb6e783aa 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -21,4 +21,5 @@ import com.android.server.wm.traces.common.FlickerComponentName
const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
val APP_PAIR_SPLIT_DIVIDER_COMPONENT = FlickerComponentName("", "AppPairSplitDivider#")
-val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#") \ No newline at end of file
+val DOCKED_STACK_DIVIDER_COMPONENT = FlickerComponentName("", "DockedStackDivider#")
+val SPLIT_SCREEN_DIVIDER_COMPONENT = FlickerComponentName("", "StageCoordinatorSplitDivider#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
deleted file mode 100644
index c9cab39b7d8b..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestCannotPairNonResizeableApps.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher. When the device doesn't support non-resizable in multi window
- * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs should not pair
- * non-resizable apps.
- *
- * To run this test: `atest WMShellFlickerTests:AppPairsTestCannotPairNonResizeableApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestCannotPairNonResizeableApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- nonResizeableApp?.launchViaIntent(wmHelper)
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- nonResizeableApp?.run { wmHelper.waitForFullScreenApp(nonResizeableApp.component) }
- }
- }
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd()
-
- @Ignore
- @Test
- fun onlyResizeableAppWindowVisible() {
- val nonResizeableApp = nonResizeableApp
- require(nonResizeableApp != null) {
- "Non resizeable app not initialized"
- }
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- isAppWindowInvisible(primaryApp.component)
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
deleted file mode 100644
index 60c32c99d1ff..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestPairPrimaryAndSecondaryApps.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher.
- * To run this test: `atest WMShellFlickerTests:AppPairsTestPairPrimaryAndSecondaryApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestPairPrimaryAndSecondaryApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- waitAppsShown(primaryApp, secondaryApp)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(primaryApp.component)
- isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appsEndingBounds() {
- testSpec.assertLayersEnd {
- val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(primaryApp.component)
- .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
- visibleRegion(secondaryApp.component)
- .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
deleted file mode 100644
index 24869a802167..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestSupportPairNonResizeableApps.kt
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import android.view.Display
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.traces.common.WindowManagerConditionsFactory
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher. When the device supports non-resizable in multi window
- * {@link Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_MULTI_WINDOW}, app pairs can pair
- * non-resizable apps.
- *
- * To run this test: `atest WMShellFlickerTests:AppPairsTestSupportPairNonResizeableApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestSupportPairNonResizeableApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- nonResizeableApp?.launchViaIntent(wmHelper)
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, nonResizeableTaskId, pair = true))
- val waitConditions = mutableListOf(
- WindowManagerConditionsFactory.isWindowVisible(primaryApp.component),
- WindowManagerConditionsFactory.isLayerVisible(primaryApp.component),
- WindowManagerConditionsFactory.isAppTransitionIdle(Display.DEFAULT_DISPLAY))
-
- nonResizeableApp?.let {
- waitConditions.add(
- WindowManagerConditionsFactory.isWindowVisible(nonResizeableApp.component))
- waitConditions.add(
- WindowManagerConditionsFactory.isLayerVisible(nonResizeableApp.component))
- }
- wmHelper.waitFor(*waitConditions.toTypedArray())
- }
- }
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- fun bothAppWindowVisible() {
- val nonResizeableApp = nonResizeableApp
- require(nonResizeableApp != null) {
- "Non resizeable app not initialized"
- }
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- isAppWindowVisible(primaryApp.component)
- }
- }
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
deleted file mode 100644
index 007415d19860..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTestUnpairPrimaryAndSecondaryApps.kt
+++ /dev/null
@@ -1,121 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import android.os.SystemClock
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.wm.shell.flicker.APP_PAIR_SPLIT_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.appPairsDividerIsInvisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test cold launch app from launcher.
- * To run this test: `atest WMShellFlickerTests:AppPairsTestUnpairPrimaryAndSecondaryApps`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class AppPairsTestUnpairPrimaryAndSecondaryApps(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- waitAppsShown(primaryApp, secondaryApp)
- }
- }
- transitions {
- // TODO pair apps through normal UX flow
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = false))
- SystemClock.sleep(AppPairsHelper.TIMEOUT_MS)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun appPairsDividerIsInvisibleAtEnd() = testSpec.appPairsDividerIsInvisibleAtEnd()
-
- @Ignore
- @Test
- fun bothAppWindowsInvisible() {
- testSpec.assertWmEnd {
- isAppWindowInvisible(primaryApp.component)
- isAppWindowInvisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appsStartingBounds() {
- testSpec.assertLayersStart {
- val dividerRegion = layer(APP_PAIR_SPLIT_DIVIDER_COMPONENT).visibleRegion.region
- visibleRegion(primaryApp.component)
- .coversExactly(appPairsHelper.getPrimaryBounds(dividerRegion))
- visibleRegion(secondaryApp.component)
- .coversExactly(appPairsHelper.getSecondaryBounds(dividerRegion))
- }
- }
-
- @Ignore
- @Test
- fun appsEndingBounds() {
- testSpec.assertLayersEnd {
- notContains(primaryApp.component)
- notContains(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): List<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = AppPairsHelper.TEST_REPETITIONS)
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
deleted file mode 100644
index 3e17948b4a84..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/AppPairsTransition.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import android.app.Instrumentation
-import android.content.Context
-import android.system.helpers.ActivityHelper
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.helpers.AppPairsHelper
-import com.android.wm.shell.flicker.helpers.BaseAppHelper
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import com.android.wm.shell.flicker.testapp.Components
-import org.junit.After
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-
-abstract class AppPairsTransition(protected val testSpec: FlickerTestParameter) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val context: Context = instrumentation.context
- protected val activityHelper = ActivityHelper.getInstance()
- protected val appPairsHelper = AppPairsHelper(instrumentation,
- Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
-
- protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
- protected open val nonResizeableApp: SplitScreenHelper? =
- SplitScreenHelper.getNonResizeable(instrumentation)
- protected var primaryTaskId = ""
- protected var secondaryTaskId = ""
- protected var nonResizeableTaskId = ""
- private var prevDevEnableNonResizableMultiWindow = 0
-
- @Before
- open fun setup() {
- prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
- if (prevDevEnableNonResizableMultiWindow != 0) {
- // Turn off the development option
- setDevEnableNonResizableMultiWindow(context, 0)
- }
- }
-
- @After
- open fun teardown() {
- setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
- }
-
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- transition(this)
- }
- }
-
- internal open val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- }
- eachRun {
- this.setRotation(testSpec.startRotation)
- primaryApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- nonResizeableApp?.launchViaIntent(wmHelper)
- updateTasksId()
- }
- }
- teardown {
- eachRun {
- executeShellCommand(composePairsCommand(
- primaryTaskId, secondaryTaskId, pair = false))
- executeShellCommand(composePairsCommand(
- primaryTaskId, nonResizeableTaskId, pair = false))
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- nonResizeableApp?.exit(wmHelper)
- }
- }
- }
-
- protected fun updateTasksId() {
- primaryTaskId = getTaskIdForActivity(
- primaryApp.component.packageName, primaryApp.component.className).toString()
- secondaryTaskId = getTaskIdForActivity(
- secondaryApp.component.packageName, secondaryApp.component.className).toString()
- val nonResizeableApp = nonResizeableApp
- if (nonResizeableApp != null) {
- nonResizeableTaskId = getTaskIdForActivity(
- nonResizeableApp.component.packageName,
- nonResizeableApp.component.className).toString()
- }
- }
-
- private fun getTaskIdForActivity(pkgName: String, activityName: String): Int {
- return activityHelper.getTaskIdForActivity(pkgName, activityName)
- }
-
- internal fun executeShellCommand(cmd: String) {
- BaseAppHelper.executeShellCommand(instrumentation, cmd)
- }
-
- internal fun composePairsCommand(
- primaryApp: String,
- secondaryApp: String,
- pair: Boolean
- ): String = buildString {
- // dumpsys activity service SystemUIService WMShell {pair|unpair} ${TASK_ID_1} ${TASK_ID_2}
- append("dumpsys activity service SystemUIService WMShell ")
- if (pair) {
- append("pair ")
- } else {
- append("unpair ")
- }
- append("$primaryApp $secondaryApp")
- }
-
- @Ignore
- @Test
- open fun navBarLayerIsVisible() {
- testSpec.navBarLayerIsVisible()
- }
-
- @Ignore
- @Test
- open fun statusBarLayerIsVisible() {
- testSpec.statusBarLayerIsVisible()
- }
-
- @Ignore
- @Test
- open fun navBarWindowIsVisible() {
- testSpec.navBarWindowIsVisible()
- }
-
- @Ignore
- @Test
- open fun statusBarWindowIsVisible() {
- testSpec.statusBarWindowIsVisible()
- }
-
- @Ignore
- @Test
- open fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @Ignore
- @Test
- open fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS
deleted file mode 100644
index 8446b37dbf06..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# window manager > wm shell > Split Screen
-# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
deleted file mode 100644
index b0c3ba20d948..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsInAppPairsMode.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open apps to app pairs and rotate.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppsInAppPairsMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class RotateTwoLaunchedAppsInAppPairsMode(
- testSpec: FlickerTestParameter
-) : RotateTwoLaunchedAppsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- executeShellCommand(composePairsCommand(
- primaryTaskId, secondaryTaskId, true /* pair */))
- waitAppsShown(primaryApp, secondaryApp)
- setRotation(testSpec.endRotation)
- }
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @Ignore
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(primaryApp.component)
- isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- fun appPairsPrimaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- primaryApp.component)
-
- @Ignore
- @Test
- fun appPairsSecondaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- secondaryApp.component)
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270)
- )
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
deleted file mode 100644
index ae56c7732a4d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsRotateAndEnterAppPairsMode.kt
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group1
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.wm.shell.flicker.appPairsDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.appPairsSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.AppPairsHelper.Companion.waitAppsShown
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Ignore
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open apps to app pairs and rotate.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppsRotateAndEnterAppPairsMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group1
-class RotateTwoLaunchedAppsRotateAndEnterAppPairsMode(
- testSpec: FlickerTestParameter
-) : RotateTwoLaunchedAppsTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- this.setRotation(testSpec.endRotation)
- executeShellCommand(
- composePairsCommand(primaryTaskId, secondaryTaskId, pair = true))
- waitAppsShown(primaryApp, secondaryApp)
- }
- }
-
- @Ignore
- @Test
- fun appPairsDividerIsVisibleAtEnd() = testSpec.appPairsDividerIsVisibleAtEnd()
-
- @Ignore
- @Test
- override fun navBarWindowIsVisible() = super.navBarWindowIsVisible()
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() = super.navBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun statusBarWindowIsVisible() = super.statusBarWindowIsVisible()
-
- @Ignore
- @Test
- override fun statusBarLayerIsVisible() = super.statusBarLayerIsVisible()
-
- @Ignore
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @Ignore
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(primaryApp.component)
- isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Ignore
- @Test
- fun appPairsPrimaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsPrimaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- primaryApp.component)
-
- @Ignore
- @Test
- fun appPairsSecondaryBoundsIsVisibleAtEnd() =
- testSpec.appPairsSecondaryBoundsIsVisibleAtEnd(testSpec.endRotation,
- secondaryApp.component)
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_90, Surface.ROTATION_270)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
deleted file mode 100644
index b1f1c9e539df..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/apppairs/RotateTwoLaunchedAppsTransition.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.apppairs
-
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.Assume.assumeFalse
-import org.junit.Before
-import org.junit.Ignore
-import org.junit.Test
-
-abstract class RotateTwoLaunchedAppsTransition(
- testSpec: FlickerTestParameter
-) : AppPairsTransition(testSpec) {
- override val nonResizeableApp: SplitScreenHelper?
- get() = null
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(Surface.ROTATION_0)
- primaryApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- updateTasksId()
- }
- }
- teardown {
- eachRun {
- executeShellCommand(composePairsCommand(
- primaryTaskId, secondaryTaskId, pair = false))
- primaryApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- }
- }
- }
-
- @Before
- override fun setup() {
- // AppPairs hasn't been updated to Shell Transition. There will be conflict on rotation.
- assumeFalse(isShellTransitionsEnabled())
- super.setup()
- }
-
- @Ignore
- @Test
- override fun navBarLayerIsVisible() {
- super.navBarLayerIsVisible()
- }
-
- @Ignore
- @Test
- override fun navBarLayerRotatesAndScales() {
- super.navBarLayerRotatesAndScales()
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
index e9d438a569d5..8157a4e453af 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/PipAppHelper.kt
@@ -39,7 +39,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
) {
private val mediaSessionManager: MediaSessionManager
get() = context.getSystemService(MediaSessionManager::class.java)
- ?: error("Could not get MediaSessionManager")
+ ?: error("Could not get MediaSessionManager")
private val mediaController: MediaController?
get() = mediaSessionManager.getActiveSessions(null).firstOrNull {
@@ -69,8 +69,10 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
action: String? = null,
stringExtras: Map<String, String>
) {
- launchViaIntentAndWaitShown(wmHelper, expectedWindowName, action, stringExtras,
- waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition))
+ launchViaIntentAndWaitShown(
+ wmHelper, expectedWindowName, action, stringExtras,
+ waitConditions = arrayOf(WindowManagerStateHelper.pipShownCondition)
+ )
}
/**
@@ -85,7 +87,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
// from "the bottom".
repeat(FOCUS_ATTEMPTS) {
uiDevice.findObject(selector)?.apply { if (isFocusedOrHasFocusedChild) return true }
- ?: error("The object we try to focus on is gone.")
+ ?: error("The object we try to focus on is gone.")
uiDevice.pressDPadDown()
uiDevice.waitForIdle()
@@ -100,29 +102,39 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
// Wait on WMHelper or simply wait for 3 seconds
wmHelper?.waitPipShown() ?: SystemClock.sleep(3_000)
// when entering pip, the dismiss button is visible at the start. to ensure the pip
- // animation is complete, wait until the pip dismiss button is no longer visible.
+ // animation is complete, wait until the pip dismiss button is no longer visible.
// b/176822698: dismiss-only state will be removed in the future
uiDevice.wait(Until.gone(By.res(SYSTEMUI_PACKAGE, "dismiss")), FIND_TIMEOUT)
}
+ fun enableEnterPipOnUserLeaveHint() {
+ clickObject(ENTER_PIP_ON_USER_LEAVE_HINT)
+ }
+
+ fun enableAutoEnterForPipActivity() {
+ clickObject(ENTER_PIP_AUTOENTER)
+ }
+
fun clickStartMediaSessionButton() {
clickObject(MEDIA_SESSION_START_RADIO_BUTTON_ID)
}
fun checkWithCustomActionsCheckbox() = uiDevice
- .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
- ?.takeIf { it.isCheckable }
- ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
- ?: error("'With custom actions' checkbox not found")
+ .findObject(By.res(component.packageName, WITH_CUSTOM_ACTIONS_BUTTON_ID))
+ ?.takeIf { it.isCheckable }
+ ?.apply { if (!isChecked) clickObject(WITH_CUSTOM_ACTIONS_BUTTON_ID) }
+ ?: error("'With custom actions' checkbox not found")
fun pauseMedia() = mediaController?.transportControls?.pause()
- ?: error("No active media session found")
+ ?: error("No active media session found")
fun stopMedia() = mediaController?.transportControls?.stop()
- ?: error("No active media session found")
+ ?: error("No active media session found")
- @Deprecated("Use PipAppHelper.closePipWindow(wmHelper) instead",
- ReplaceWith("closePipWindow(wmHelper)"))
+ @Deprecated(
+ "Use PipAppHelper.closePipWindow(wmHelper) instead",
+ ReplaceWith("closePipWindow(wmHelper)")
+ )
fun closePipWindow() {
if (isTelevision) {
uiDevice.closeTvPipWindow()
@@ -152,7 +164,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
val dismissSelector = By.res(SYSTEMUI_PACKAGE, "dismiss")
uiDevice.wait(Until.hasObject(dismissSelector), FIND_TIMEOUT)
val dismissPipObject = uiDevice.findObject(dismissSelector)
- ?: error("PIP window dismiss button not found")
+ ?: error("PIP window dismiss button not found")
val dismissButtonBounds = dismissPipObject.visibleBounds
uiDevice.click(dismissButtonBounds.centerX(), dismissButtonBounds.centerY())
}
@@ -172,7 +184,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
val expandSelector = By.res(SYSTEMUI_PACKAGE, "expand_button")
uiDevice.wait(Until.hasObject(expandSelector), FIND_TIMEOUT)
val expandPipObject = uiDevice.findObject(expandSelector)
- ?: error("PIP window expand button not found")
+ ?: error("PIP window expand button not found")
val expandButtonBounds = expandPipObject.visibleBounds
uiDevice.click(expandButtonBounds.centerX(), expandButtonBounds.centerY())
wmHelper.waitPipGone()
@@ -194,5 +206,7 @@ class PipAppHelper(instrumentation: Instrumentation) : BaseAppHelper(
private const val ENTER_PIP_BUTTON_ID = "enter_pip"
private const val WITH_CUSTOM_ACTIONS_BUTTON_ID = "with_custom_actions"
private const val MEDIA_SESSION_START_RADIO_BUTTON_ID = "media_session_start"
+ private const val ENTER_PIP_ON_USER_LEAVE_HINT = "enter_pip_on_leave_manual"
+ private const val ENTER_PIP_AUTOENTER = "enter_pip_on_leave_autoenter"
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
index 0ec9b2d869a8..49eca63a23ec 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/SplitScreenHelper.kt
@@ -17,10 +17,21 @@
package com.android.wm.shell.flicker.helpers
import android.app.Instrumentation
-import android.content.res.Resources
+import android.graphics.Point
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.MotionEvent
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.BySelector
+import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.Until
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.server.wm.traces.parser.toFlickerComponent
+import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
import com.android.wm.shell.flicker.testapp.Components
+import org.junit.Assert
class SplitScreenHelper(
instrumentation: Instrumentation,
@@ -31,25 +42,170 @@ class SplitScreenHelper(
companion object {
const val TEST_REPETITIONS = 1
const val TIMEOUT_MS = 3_000L
+ const val DRAG_DURATION_MS = 1_000L
+ const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
+ const val GESTURE_STEP_MS = 16L
- // TODO: remove all legacy split screen flicker tests when legacy split screen is fully
- // deprecated.
- fun isUsingLegacySplit(): Boolean =
- Resources.getSystem().getBoolean(com.android.internal.R.bool.config_useLegacySplit)
+ private val notificationScrollerSelector: BySelector
+ get() = By.res(SYSTEM_UI_PACKAGE_NAME, NOTIFICATION_SCROLLER)
+ private val notificationContentSelector: BySelector
+ get() = By.text("Notification content")
fun getPrimary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(instrumentation,
+ SplitScreenHelper(
+ instrumentation,
Components.SplitScreenActivity.LABEL,
- Components.SplitScreenActivity.COMPONENT.toFlickerComponent())
+ Components.SplitScreenActivity.COMPONENT.toFlickerComponent()
+ )
fun getSecondary(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(instrumentation,
+ SplitScreenHelper(
+ instrumentation,
Components.SplitScreenSecondaryActivity.LABEL,
- Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent())
+ Components.SplitScreenSecondaryActivity.COMPONENT.toFlickerComponent()
+ )
fun getNonResizeable(instrumentation: Instrumentation): SplitScreenHelper =
- SplitScreenHelper(instrumentation,
+ SplitScreenHelper(
+ instrumentation,
Components.NonResizeableActivity.LABEL,
- Components.NonResizeableActivity.COMPONENT.toFlickerComponent())
+ Components.NonResizeableActivity.COMPONENT.toFlickerComponent()
+ )
+
+ fun getSendNotification(instrumentation: Instrumentation): SplitScreenHelper =
+ SplitScreenHelper(
+ instrumentation,
+ Components.SendNotificationActivity.LABEL,
+ Components.SendNotificationActivity.COMPONENT.toFlickerComponent()
+ )
+
+ fun dragFromNotificationToSplit(
+ instrumentation: Instrumentation,
+ device: UiDevice,
+ wmHelper: WindowManagerStateHelper
+ ) {
+ val displayBounds = wmHelper.currentState.layerState
+ .displays.firstOrNull { !it.isVirtual }
+ ?.layerStackSpace
+ ?: error("Display not found")
+
+ // Pull down the notifications
+ device.swipe(
+ displayBounds.centerX(), 5,
+ displayBounds.centerX(), displayBounds.bottom, 20 /* steps */
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+
+ // Find the target notification
+ val notificationScroller = device.wait(
+ Until.findObject(notificationScrollerSelector), TIMEOUT_MS
+ )
+ var notificationContent = notificationScroller.findObject(notificationContentSelector)
+
+ while (notificationContent == null) {
+ device.swipe(
+ displayBounds.centerX(), displayBounds.centerY(),
+ displayBounds.centerX(), displayBounds.centerY() - 150, 20 /* steps */
+ )
+ notificationContent = notificationScroller.findObject(notificationContentSelector)
+ }
+
+ // Drag to split
+ var dragStart = notificationContent.visibleCenter
+ var dragMiddle = Point(dragStart.x + 50, dragStart.y)
+ var dragEnd = Point(displayBounds.width / 4, displayBounds.width / 4)
+ val downTime = SystemClock.uptimeMillis()
+
+ touch(
+ instrumentation, MotionEvent.ACTION_DOWN, downTime, downTime,
+ TIMEOUT_MS, dragStart
+ )
+ // It needs a horizontal movement to trigger the drag
+ touchMove(
+ instrumentation, downTime, SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS, dragStart, dragMiddle
+ )
+ touchMove(
+ instrumentation, downTime, SystemClock.uptimeMillis(),
+ DRAG_DURATION_MS, dragMiddle, dragEnd
+ )
+ // Wait for a while to start splitting
+ SystemClock.sleep(TIMEOUT_MS)
+ touch(
+ instrumentation, MotionEvent.ACTION_UP, downTime, SystemClock.uptimeMillis(),
+ GESTURE_STEP_MS, dragEnd
+ )
+ SystemClock.sleep(TIMEOUT_MS)
+ }
+
+ fun touch(
+ instrumentation: Instrumentation,
+ action: Int,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ point: Point
+ ) {
+ val motionEvent = MotionEvent.obtain(
+ downTime, eventTime, action, point.x.toFloat(), point.y.toFloat(), 0
+ )
+ motionEvent.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionEvent, true)
+ motionEvent.recycle()
+ SystemClock.sleep(duration)
+ }
+
+ fun touchMove(
+ instrumentation: Instrumentation,
+ downTime: Long,
+ eventTime: Long,
+ duration: Long,
+ from: Point,
+ to: Point
+ ) {
+ val steps: Long = duration / GESTURE_STEP_MS
+ var currentTime = eventTime
+ var currentX = from.x.toFloat()
+ var currentY = from.y.toFloat()
+ val stepX = (to.x.toFloat() - from.x.toFloat()) / steps.toFloat()
+ val stepY = (to.y.toFloat() - from.y.toFloat()) / steps.toFloat()
+
+ for (i in 1..steps) {
+ val motionMove = MotionEvent.obtain(
+ downTime, currentTime, MotionEvent.ACTION_MOVE, currentX, currentY, 0
+ )
+ motionMove.source = InputDevice.SOURCE_TOUCHSCREEN
+ instrumentation.uiAutomation.injectInputEvent(motionMove, true)
+ motionMove.recycle()
+
+ currentTime += GESTURE_STEP_MS
+ if (i == steps - 1) {
+ currentX = to.x.toFloat()
+ currentY = to.y.toFloat()
+ } else {
+ currentX += stepX
+ currentY += stepY
+ }
+ SystemClock.sleep(GESTURE_STEP_MS)
+ }
+ }
+
+ fun createShortcutOnHotseatIfNotExist(
+ taplInstrumentation: LauncherInstrumentation,
+ appName: String
+ ) {
+ taplInstrumentation.workspace
+ .deleteAppIcon(taplInstrumentation.workspace.getHotseatAppIcon(0))
+ val allApps = taplInstrumentation.workspace.switchToAllApps()
+ allApps.freeze()
+ try {
+ val appIconSrc = allApps.getAppIcon(appName)
+ Assert.assertNotNull("Unable to find app icon", appIconSrc)
+ val appIconDest = appIconSrc.dragToHotseat(0)
+ Assert.assertNotNull("Unable to drag app icon on hotseat", appIconDest)
+ } finally {
+ allApps.unfreeze()
+ }
+ }
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
deleted file mode 100644
index c86a1229d8d8..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenDockActivity.kt
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import android.view.WindowManagerPolicyConstants
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open activity and dock to primary split screen
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenDockActivity`
- */
-@Presubmit
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class EnterSplitScreenDockActivity(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, LIVE_WALLPAPER_COMPONENT,
- splitScreenApp.component, FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT, LAUNCHER_COMPONENT)
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible()
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @FlakyTest
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0), // bugId = 179116910
- supportedNavigationModes = listOf(
- WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
deleted file mode 100644
index 2f9244be9c18..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenFromDetachedRecentTask.kt
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test enter split screen from a detached recent task
- *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenFromDetachedRecentTask`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-class EnterSplitScreenFromDetachedRecentTask(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- // Press back to remove the task, but it should still be shown in recent.
- device.pressBack()
- }
- }
- transitions {
- device.launchSplitScreen(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
- )
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
deleted file mode 100644
index 1740c3ec24ca..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenLaunchToSide.kt
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerBecomesVisible
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open activity to primary split screen and dock secondary activity to side
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenLaunchToSide`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class EnterSplitScreenLaunchToSide(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
- secondaryApp.component, FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- secondaryApp.component)
-
- @Presubmit
- @Test
- fun dockedStackDividerBecomesVisible() = testSpec.dockedStackDividerBecomesVisible()
-
- @Presubmit
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- // when the app is launched, first the activity becomes visible, then the
- // SnapshotStartingWindow appears and then the app window becomes visible.
- // Because we log WM once per frame, sometimes the activity and the window
- // become visible in the same entry, sometimes not, thus it is not possible to
- // assert the visibility of the activity here
- this.isAppWindowInvisible(secondaryApp.component)
- .then()
- // during re-parenting, the window may disappear and reappear from the
- // trace, this occurs because we log only 1x per frame
- .notContains(secondaryApp.component, isOptional = true)
- .then()
- .isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 175687842
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
deleted file mode 100644
index 4c063b918e96..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenNotSupportNonResizable.kt
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.canSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Assert
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test enter split screen from non-resizable activity. When the device doesn't support
- * non-resizable in multi window, there should be no button to enter split screen for non-resizable
- * activity.
- *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenNotSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group4
-class EnterSplitScreenNotSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- }
- }
- transitions {
- if (device.canSplitScreen(wmHelper)) {
- Assert.fail("Non-resizeable app should not enter split screen")
- }
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT,
- nonResizeableApp.component,
- splitScreenApp.component)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
deleted file mode 100644
index f75dee619564..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/EnterSplitScreenSupportNonResizable.kt
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test enter split screen from non-resizable activity. When the device supports
- * non-resizable in multi window, there should be a button to enter split screen for non-resizable
- * activity.
- *
- * To run this test: `atest WMShellFlickerTests:EnterSplitScreenSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@Group2
-class EnterSplitScreenSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- }
- }
- transitions {
- device.launchSplitScreen(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT,
- nonResizeableApp.component,
- splitScreenApp.component)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun appWindowIsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
deleted file mode 100644
index ef7d65e8a732..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitLegacySplitScreenFromBottom.kt
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.exitSplitScreenFromBottom
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open resizeable activity split in primary, and drag divider to bottom exit split screen
- * To run this test: `atest WMShellFlickerTests:ExitLegacySplitScreenFromBottom`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class ExitLegacySplitScreenFromBottom(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- teardown {
- eachRun {
- splitScreenApp.exit(wmHelper)
- }
- }
- transitions {
- device.exitSplitScreenFromBottom(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
- splitScreenApp.component, secondaryApp.component,
- FlickerComponentName.SNAPSHOT)
-
- @FlakyTest
- @Test
- fun layerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
- .then()
- .isInvisible(DOCKED_STACK_DIVIDER_COMPONENT)
- }
- }
-
- @FlakyTest
- @Test
- fun appWindowBecomesInVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(secondaryApp.component)
- .then()
- .isAppWindowInvisible(secondaryApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @FlakyTest
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @FlakyTest
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // b/175687842
- )
- }
- }
-} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
deleted file mode 100644
index d913a6d85d3d..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ExitPrimarySplitScreenShowSecondaryFullscreen.kt
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test dock activity to primary split screen, and open secondary to side, exit primary split
- * and test secondary activity become full screen.
- * To run this test: `atest WMShellFlickerTests:ExitPrimarySplitScreenShowSecondaryFullscreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class ExitPrimarySplitScreenShowSecondaryFullscreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- teardown {
- eachRun {
- secondaryApp.exit(wmHelper)
- }
- }
- transitions {
- splitScreenApp.launchViaIntent(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- // TODO(b/175687842) Can not find Split screen divider, use exit() instead
- splitScreenApp.exit(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
- splitScreenApp.component, secondaryApp.component,
- FlickerComponentName.SNAPSHOT)
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @FlakyTest
- @Test
- fun layerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(splitScreenApp.component)
- .then()
- .isInvisible(splitScreenApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun appWindowBecomesInVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(splitScreenApp.component)
- .then()
- .isAppWindowInvisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
deleted file mode 100644
index f3ff7b156aaf..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentNotSupportNonResizable.kt
+++ /dev/null
@@ -1,196 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launch non-resizable activity via intent in split screen mode. When the device does not
- * support non-resizable in multi window, it should trigger exit split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentNotSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromIntentNotSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- nonResizeableApp.component, splitScreenApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun resizableAppLayerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(splitScreenApp.component)
- .then()
- .isInvisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.notContains(nonResizeableApp.component)
- .then()
- .isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- /**
- * Assets that [splitScreenApp] exists at the start of the trace and, once it becomes
- * invisible, it remains invisible until the end of the trace.
- */
- @Presubmit
- @Test
- fun resizableAppWindowBecomesInvisible() {
- testSpec.assertWm {
- // when the activity gets PAUSED the window may still be marked as visible
- // it will be updated in the next log entry. This occurs because we record 1x
- // per frame, thus ignore activity check here
- this.isAppWindowVisible(splitScreenApp.component)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowInvisible(splitScreenApp.component)
- }
- }
-
- /**
- * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
- * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
- * visible, it remains visible until the end of the trace.
- */
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- this.notContains(nonResizeableApp.component)
- .then()
- // we log once per frame, upon logging, window may be visible or not depending
- // on what was processed until that moment. Both behaviors are correct
- .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- /**
- * Asserts that both the app window and the activity are visible at the end of the trace
- */
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @Presubmit
- @Test
- fun onlyNonResizableAppWindowIsVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowInvisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
deleted file mode 100644
index 42e707ab0850..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromIntentSupportNonResizable.kt
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launch non-resizable activity via intent in split screen mode. When the device supports
- * non-resizable in multi window, it should show the non-resizable app in split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromIntentSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromIntentSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- nonResizeableApp.launchViaIntent(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- nonResizeableApp.component, splitScreenApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- /**
- * Assets that [nonResizeableApp] doesn't exist at the start of the trace, then
- * [nonResizeableApp] is created (visible or not) and, once [nonResizeableApp] becomes
- * visible, it remains visible until the end of the trace.
- */
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- this.notContains(nonResizeableApp.component)
- .then()
- // we log once per frame, upon logging, window may be visible or not depending
- // on what was processed until that moment. Both behaviors are correct
- .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun bothAppsWindowsAreVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
deleted file mode 100644
index c1fba7d1530c..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentNotSupportNonResizable.kt
+++ /dev/null
@@ -1,169 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerNotExistsAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launch non-resizable activity via recent overview in split screen mode. When the device does
- * not support non-resizable in multi window, it should trigger exit split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentNotSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromRecentNotSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, -1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun resizableAppLayerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(splitScreenApp.component)
- .then()
- .isInvisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun resizableAppWindowBecomesInvisible() {
- testSpec.assertWm {
- // when the activity gets PAUSED the window may still be marked as visible
- // it will be updated in the next log entry. This occurs because we record 1x
- // per frame, thus ignore activity check here
- this.isAppWindowVisible(splitScreenApp.component)
- .then()
- // immediately after the window (after onResume and before perform relayout)
- // the activity is invisible. This may or not be logged, since we record 1x
- // per frame, thus ignore activity check here
- .isAppWindowInvisible(splitScreenApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(nonResizeableApp.component)
- .then()
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerNotExistsAtEnd() = testSpec.dockedStackDividerNotExistsAtEnd()
-
- @Presubmit
- @Test
- fun onlyNonResizableAppWindowIsVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowInvisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
deleted file mode 100644
index 6ac8683ac054..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenFromRecentSupportNonResizable.kt
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.resetMultiWindowConfig
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setSupportsNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test launch non-resizable activity via recent overview in split screen mode. When the device
- * supports non-resizable in multi window, it should show the non-resizable app in split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenFromRecentSupportNonResizable`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenFromRecentSupportNonResizable(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- cleanSetup(this)
- setup {
- eachRun {
- nonResizeableApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- }
- }
- transitions {
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(DOCKED_STACK_DIVIDER_COMPONENT, LAUNCHER_COMPONENT, LETTERBOX_COMPONENT,
- TOAST_COMPONENT, splitScreenApp.component, nonResizeableApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Before
- override fun setup() {
- super.setup()
- setSupportsNonResizableMultiWindow(instrumentation, 1)
- }
-
- @After
- override fun teardown() {
- super.teardown()
- resetMultiWindowConfig(instrumentation)
- }
-
- @Presubmit
- @Test
- fun nonResizableAppLayerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(nonResizeableApp.component)
- .then()
- .isVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun nonResizableAppWindowBecomesVisible() {
- testSpec.assertWm {
- // when the app is launched, first the activity becomes visible, then the
- // SnapshotStartingWindow appears and then the app window becomes visible.
- // Because we log WM once per frame, sometimes the activity and the window
- // become visible in the same entry, sometimes not, thus it is not possible to
- // assert the visibility of the activity here
- this.isAppWindowInvisible(nonResizeableApp.component)
- .then()
- // during re-parenting, the window may disappear and reappear from the
- // trace, this occurs because we log only 1x per frame
- .notContains(nonResizeableApp.component, isOptional = true)
- .then()
- // if the window reappears after re-parenting it will most likely not
- // be visible in the first log entry (because we log only 1x per frame)
- .isAppWindowInvisible(nonResizeableApp.component, isOptional = true)
- .then()
- .isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun bothAppsWindowsAreVisibleAtEnd() {
- testSpec.assertWmEnd {
- isAppWindowVisible(splitScreenApp.component)
- isAppWindowVisible(nonResizeableApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
deleted file mode 100644
index b01f41c9e2ec..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenRotateTransition.kt
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.view.Surface
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-
-abstract class LegacySplitScreenRotateTransition(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- }
- }
- teardown {
- eachRun {
- splitScreenApp.exit(wmHelper)
- secondaryApp.exit(wmHelper)
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
deleted file mode 100644
index fb1004bda0cb..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenToLauncher.kt
+++ /dev/null
@@ -1,160 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.exitSplitScreen
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.dockedStackDividerBecomesInvisible
-import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:LegacySplitScreenToLauncher`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class LegacySplitScreenToLauncher(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- private val testApp = SimpleAppHelper(instrumentation)
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- }
- eachRun {
- testApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.endRotation)
- device.launchSplitScreen(wmHelper)
- device.waitForIdle()
- }
- }
- teardown {
- eachRun {
- testApp.exit(wmHelper)
- }
- }
- transitions {
- device.exitSplitScreen()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
-
- @Presubmit
- @Test
- fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @FlakyTest
- @Test
- fun dockedStackDividerBecomesInvisible() = testSpec.dockedStackDividerBecomesInvisible()
-
- @FlakyTest
- @Test
- fun layerBecomesInvisible() {
- testSpec.assertLayers {
- this.isVisible(testApp.component)
- .then()
- .isInvisible(testApp.component)
- }
- }
-
- @FlakyTest
- @Test
- fun focusDoesNotChange() {
- testSpec.assertEventLog {
- this.focusDoesNotChange()
- }
- }
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- // b/161435597 causes the test not to work on 90 degrees
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
deleted file mode 100644
index a4a1f617e497..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/LegacySplitScreenTransition.kt
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.app.Instrumentation
-import android.content.Context
-import android.support.test.launcherhelper.LauncherStrategyFactory
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.wm.flicker.FlickerBuilderProvider
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.openQuickStepAndClearRecentAppsFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.getDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.MultiWindowHelper.Companion.setDevEnableNonResizableMultiWindow
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.After
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Test
-
-abstract class LegacySplitScreenTransition(protected val testSpec: FlickerTestParameter) {
- protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- protected val context: Context = instrumentation.context
- protected val splitScreenApp = SplitScreenHelper.getPrimary(instrumentation)
- protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
- protected val nonResizeableApp = SplitScreenHelper.getNonResizeable(instrumentation)
- protected val LAUNCHER_COMPONENT = FlickerComponentName("",
- LauncherStrategyFactory.getInstance(instrumentation)
- .launcherStrategy.supportedLauncherPackage)
- private var prevDevEnableNonResizableMultiWindow = 0
-
- @Before
- open fun setup() {
- // Only run legacy split tests when the system is using legacy split screen.
- assumeTrue(SplitScreenHelper.isUsingLegacySplit())
- // Legacy split is having some issue with Shell transition, and will be deprecated soon.
- assumeFalse(isShellTransitionsEnabled())
- prevDevEnableNonResizableMultiWindow = getDevEnableNonResizableMultiWindow(context)
- if (prevDevEnableNonResizableMultiWindow != 0) {
- // Turn off the development option
- setDevEnableNonResizableMultiWindow(context, 0)
- }
- }
-
- @After
- open fun teardown() {
- setDevEnableNonResizableMultiWindow(context, prevDevEnableNonResizableMultiWindow)
- }
-
- /**
- * List of windows that are ignored when verifying that visible elements appear on 2
- * consecutive entries in the trace.
- *
- * b/182720234
- */
- open val ignoredWindows: List<FlickerComponentName> = listOf(
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- protected open val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- secondaryApp.launchViaIntent(wmHelper)
- splitScreenApp.launchViaIntent(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
- teardown {
- eachRun {
- secondaryApp.exit(wmHelper)
- splitScreenApp.exit(wmHelper)
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-
- @FlickerBuilderProvider
- fun buildFlicker(): FlickerBuilder {
- return FlickerBuilder(instrumentation).apply {
- transition(this)
- }
- }
-
- internal open val cleanSetup: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- device.openQuickStepAndClearRecentAppsFromOverview(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
- teardown {
- eachRun {
- nonResizeableApp.exit(wmHelper)
- splitScreenApp.exit(wmHelper)
- device.pressHome()
- this.setRotation(Surface.ROTATION_0)
- }
- }
- }
-
- @FlakyTest(bugId = 178447631)
- @Test
- open fun visibleWindowsShownMoreThanOneConsecutiveEntry() {
- testSpec.assertWm {
- this.visibleWindowsShownMoreThanOneConsecutiveEntry(ignoredWindows)
- }
- }
-
- @FlakyTest(bugId = 178447631)
- @Test
- open fun visibleLayersShownMoreThanOneConsecutiveEntry() {
- testSpec.assertLayers {
- this.visibleLayersShownMoreThanOneConsecutiveEntry(ignoredWindows)
- }
- }
-
- companion object {
- internal val LIVE_WALLPAPER_COMPONENT = FlickerComponentName("",
- "com.breel.wallpapers18.soundviz.wallpaper.variations.SoundVizWallpaperV2")
- internal val LETTERBOX_COMPONENT = FlickerComponentName("", "Letterbox")
- internal val TOAST_COMPONENT = FlickerComponentName("", "Toast")
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS
deleted file mode 100644
index 8446b37dbf06..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# window manager > wm shell > Split Screen
-# Bug component: 928697
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
deleted file mode 100644
index 087b21c544c5..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/OpenAppToLegacySplitScreen.kt
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.traces.common.FlickerComponentName
-import com.android.wm.shell.flicker.appPairsDividerBecomesVisible
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:OpenAppToLegacySplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class OpenAppToLegacySplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- wmHelper.waitForAppTransitionIdle()
- }
- }
-
- override val ignoredWindows: List<FlickerComponentName>
- get() = listOf(LAUNCHER_COMPONENT, splitScreenApp.component,
- FlickerComponentName.SPLASH_SCREEN,
- FlickerComponentName.SNAPSHOT)
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(splitScreenApp.component)
- .then()
- .isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- @Presubmit
- @Test
- fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @Presubmit
- @Test
- fun appPairsDividerBecomesVisible() = testSpec.appPairsDividerBecomesVisible()
-
- @FlakyTest
- @Test
- fun layerBecomesVisible() {
- testSpec.assertLayers {
- this.isInvisible(splitScreenApp.component)
- .then()
- .isVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun focusChanges() {
- testSpec.assertEventLog {
- this.focusChanges(splitScreenApp.`package`,
- "recents_animation_input_consumer", "NexusLauncherActivity")
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0) // bugId = 179116910
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
deleted file mode 100644
index e2da1a4565c0..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt
+++ /dev/null
@@ -1,228 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.util.Rational
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import androidx.test.uiautomator.By
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.entireScreenCovered
-import com.android.server.wm.flicker.helpers.ImeAppHelper
-import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.resizeSplitScreen
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.navBarLayerIsVisible
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.server.wm.traces.common.region.Region
-import com.android.server.wm.traces.parser.toFlickerComponent
-import com.android.wm.shell.flicker.DOCKED_STACK_DIVIDER_COMPONENT
-import com.android.wm.shell.flicker.helpers.SimpleAppHelper
-import com.android.wm.shell.flicker.testapp.Components
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test split screen resizing window transitions.
- * To run this test: `atest WMShellFlickerTests:ResizeLegacySplitScreen`
- *
- * Currently it runs only in 0 degrees because of b/156100803
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@FlakyTest(bugId = 159096424)
-@Group2
-class ResizeLegacySplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenTransition(testSpec) {
- private val testAppTop = SimpleAppHelper(instrumentation)
- private val testAppBottom = ImeAppHelper(instrumentation)
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- eachRun {
- device.wakeUpAndGoToHomeScreen()
- this.setRotation(testSpec.startRotation)
- this.launcherStrategy.clearRecentAppsFromOverview()
- testAppBottom.launchViaIntent(wmHelper)
- device.pressHome()
- testAppTop.launchViaIntent(wmHelper)
- device.waitForIdle()
- device.launchSplitScreen(wmHelper)
- val snapshot =
- device.findObject(By.res(device.launcherPackageName, "snapshot"))
- snapshot.click()
- testAppBottom.openIME(device)
- device.pressBack()
- device.resizeSplitScreen(startRatio)
- }
- }
- teardown {
- eachRun {
- testAppTop.exit(wmHelper)
- testAppBottom.exit(wmHelper)
- }
- }
- transitions {
- device.resizeSplitScreen(stopRatio)
- }
- }
-
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest(bugId = 156223549)
- @Test
- fun topAppWindowIsAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @FlakyTest(bugId = 156223549)
- @Test
- fun bottomAppWindowIsAlwaysVisible() {
- testSpec.assertWm {
- this.isAppWindowVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @Test
- fun navBarLayerIsVisible() = testSpec.navBarLayerIsVisible()
-
- @Test
- fun statusBarLayerIsVisible() = testSpec.statusBarLayerIsVisible()
-
- @Test
- fun entireScreenCovered() = testSpec.entireScreenCovered()
-
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Test
- fun topAppLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @Test
- fun bottomAppLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(Components.ImeActivity.COMPONENT.toFlickerComponent())
- }
- }
-
- @Test
- fun dividerLayerIsAlwaysVisible() {
- testSpec.assertLayers {
- this.isVisible(DOCKED_STACK_DIVIDER_COMPONENT)
- }
- }
-
- @FlakyTest
- @Test
- fun appsStartingBounds() {
- testSpec.assertLayersStart {
- val displayBounds = WindowUtils.displayBounds
- val dividerBounds =
- layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
-
- val topAppBounds = Region.from(0, 0, dividerBounds.right,
- dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region.from(0,
- dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
- visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- .coversExactly(topAppBounds)
- visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
- .coversExactly(bottomAppBounds)
- }
- }
-
- @FlakyTest
- @Test
- fun appsEndingBounds() {
- testSpec.assertLayersStart {
- val displayBounds = WindowUtils.displayBounds
- val dividerBounds =
- layer(DOCKED_STACK_DIVIDER_COMPONENT).visibleRegion.region.bounds
-
- val topAppBounds = Region.from(0, 0, dividerBounds.right,
- dividerBounds.top + WindowUtils.dockedStackDividerInset)
- val bottomAppBounds = Region.from(0,
- dividerBounds.bottom - WindowUtils.dockedStackDividerInset,
- displayBounds.right,
- displayBounds.bottom - WindowUtils.navigationBarFrameHeight)
-
- visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent())
- .coversExactly(topAppBounds)
- visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent())
- .coversExactly(bottomAppBounds)
- }
- }
-
- @Test
- fun focusDoesNotChange() {
- testSpec.assertEventLog {
- focusDoesNotChange()
- }
- }
-
- companion object {
- private val startRatio = Rational(1, 3)
- private val stopRatio = Rational(2, 3)
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0))
- .map {
- val description = (startRatio.toString().replace("/", "-") + "_to_" +
- stopRatio.toString().replace("/", "-"))
- val newName = "${FlickerTestParameter.defaultName(it)}_$description"
- FlickerTestParameter(it.config, nameOverride = newName)
- }
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
deleted file mode 100644
index d703ea082c87..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppAndEnterSplitScreen.kt
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test dock activity to primary split screen and rotate
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppAndEnterSplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateOneLaunchedAppAndEnterSplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- device.launchSplitScreen(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(splitScreenApp.component)
- .then()
- .isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
deleted file mode 100644
index 6b1883914e59..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateOneLaunchedAppInSplitScreenMode.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Rotate
- * To run this test: `atest WMShellFlickerTests:RotateOneLaunchedAppInSplitScreenMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateOneLaunchedAppInSplitScreenMode(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- this.setRotation(testSpec.startRotation)
- device.launchSplitScreen(wmHelper)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() = testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(
- testSpec.startRotation, splitScreenApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(splitScreenApp.component)
- .then()
- .isAppWindowVisible(splitScreenApp.component)
- }
- }
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
deleted file mode 100644
index acd658b5ba56..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppAndEnterSplitScreen.kt
+++ /dev/null
@@ -1,136 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppAndEnterSplitScreen`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateTwoLaunchedAppAndEnterSplitScreen(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- transitions {
- this.setRotation(testSpec.startRotation)
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- secondaryApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @Presubmit
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- // when the app is launched, first the activity becomes visible, then the
- // SnapshotStartingWindow appears and then the app window becomes visible.
- // Because we log WM once per frame, sometimes the activity and the window
- // become visible in the same entry, sometimes not, thus it is not possible to
- // assert the visibility of the activity here
- this.isAppWindowInvisible(secondaryApp.component)
- .then()
- // during re-parenting, the window may disappear and reappear from the
- // trace, this occurs because we log only 1x per frame
- .notContains(secondaryApp.component, isOptional = true)
- .then()
- // if the window reappears after re-parenting it will most likely not
- // be visible in the first log entry (because we log only 1x per frame)
- .isAppWindowInvisible(secondaryApp.component, isOptional = true)
- .then()
- .isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
deleted file mode 100644
index b40be8b5f401..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/RotateTwoLaunchedAppInSplitScreenMode.kt
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.legacysplitscreen
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group2
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.reopenAppFromOverview
-import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.navBarWindowIsVisible
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
-import com.android.server.wm.flicker.statusBarWindowIsVisible
-import com.android.wm.shell.flicker.dockedStackDividerIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackPrimaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.dockedStackSecondaryBoundsIsVisibleAtEnd
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test open app to split screen.
- * To run this test: `atest WMShellFlickerTests:RotateTwoLaunchedAppInSplitScreenMode`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group2
-class RotateTwoLaunchedAppInSplitScreenMode(
- testSpec: FlickerTestParameter
-) : LegacySplitScreenRotateTransition(testSpec) {
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- super.transition(this)
- setup {
- eachRun {
- device.launchSplitScreen(wmHelper)
- device.reopenAppFromOverview(wmHelper)
- this.setRotation(testSpec.startRotation)
- }
- }
- transitions {
- this.setRotation(testSpec.startRotation)
- }
- }
-
- @Presubmit
- @Test
- fun dockedStackDividerIsVisibleAtEnd() = testSpec.dockedStackDividerIsVisibleAtEnd()
-
- @Presubmit
- @Test
- fun dockedStackPrimaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackPrimaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- splitScreenApp.component)
-
- @Presubmit
- @Test
- fun dockedStackSecondaryBoundsIsVisibleAtEnd() =
- testSpec.dockedStackSecondaryBoundsIsVisibleAtEnd(testSpec.startRotation,
- secondaryApp.component)
-
- @Presubmit
- @Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
-
- @FlakyTest(bugId = 206753786)
- @Test
- fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- @FlakyTest
- @Test
- fun appWindowBecomesVisible() {
- testSpec.assertWm {
- this.isAppWindowInvisible(secondaryApp.component)
- .then()
- .isAppWindowVisible(secondaryApp.component)
- }
- }
-
- @Presubmit
- @Test
- fun navBarWindowIsVisible() = testSpec.navBarWindowIsVisible()
-
- @Presubmit
- @Test
- fun statusBarWindowIsVisible() = testSpec.statusBarWindowIsVisible()
-
- @Presubmit
- @Test
- override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
- super.visibleLayersShownMoreThanOneConsecutiveEntry()
-
- @Presubmit
- @Test
- override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
- super.visibleWindowsShownMoreThanOneConsecutiveEntry()
-
- companion object {
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- repetitions = SplitScreenHelper.TEST_REPETITIONS,
- supportedRotations = listOf(Surface.ROTATION_0)) // b/178685668
- }
- }
-}
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
new file mode 100644
index 000000000000..ce624f2b5bbe
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import android.platform.test.annotations.FlakyTest
+import androidx.test.filters.RequiresDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via auto-enter property when navigating to home.
+ *
+ * To run this test: `atest WMShellFlickerTests:AutoEnterPipOnGoToHomeTest`
+ *
+ * Actions:
+ * Launch an app in full screen
+ * Select "Auto-enter PiP" radio button
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ *
+ * Notes:
+ * 1. All assertions are inherited from [EnterPipTest]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@FlakyTest(bugId = 238367575)
+@Group3
+class AutoEnterPipOnGoToHomeTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+ protected val taplInstrumentation = LauncherInstrumentation()
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setupAndTeardown(this)
+ setup {
+ eachRun {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableAutoEnterForPipActivity()
+ }
+ }
+ teardown {
+ eachRun {
+ // close gracefully so that onActivityUnpinned() can be called before force exit
+ pipApp.closePipWindow(wmHelper)
+ pipApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.goHome()
+ }
+ }
+
+ override fun pipLayerReduces() {
+ val layerName = pipApp.component.toLayerName()
+ testSpec.assertLayers {
+ val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.notBiggerThan(previous.visibleRegion.region)
+ }
+ }
+ }
+
+ /**
+ * Checks that [pipApp] window is animated towards default position in right bottom corner
+ */
+ @Test
+ fun pipLayerMovesTowardsRightBottomCorner() {
+ // in gestural nav the swipe makes PiP first go upwards
+ Assume.assumeFalse(testSpec.isGesturalNavigation)
+ val layerName = pipApp.component.toLayerName()
+ testSpec.assertLayers {
+ val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
+ // Pip animates towards the right bottom corner, but because it is being resized at the
+ // same time, it is possible it shrinks first quickly below the default position and get
+ // moved up after that in just few last frames
+ pipLayerList.zipWithNext { previous, current ->
+ current.visibleRegion.isToTheRightBottom(previous.visibleRegion.region, 3)
+ }
+ }
+ }
+
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up
+ Assume.assumeFalse(testSpec.isGesturalNavigation)
+ super.focusChanges()
+ }
+}
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
new file mode 100644
index 000000000000..953f59a1f70b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.pip
+
+import androidx.test.filters.RequiresDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.annotation.Group3
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import org.junit.Assume
+import org.junit.FixMethodOrder
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test entering pip from an app via [onUserLeaveHint] and by navigating to home.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterPipOnUserLeaveHintTest`
+ *
+ * Actions:
+ * Launch an app in full screen
+ * Select "Via code behind" radio button
+ * Press Home button or swipe up to go Home and put [pipApp] in pip mode
+ *
+ * Notes:
+ * 1. All assertions are inherited from [EnterPipTest]
+ * 2. Part of the test setup occurs automatically via
+ * [com.android.server.wm.flicker.TransitionRunnerWithRules],
+ * including configuring navigation mode, initial orientation and ensuring no
+ * apps are running before setup
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group3
+class EnterPipOnUserLeaveHintTest(testSpec: FlickerTestParameter) : EnterPipTest(testSpec) {
+ protected val taplInstrumentation = LauncherInstrumentation()
+ /**
+ * Defines the transition used to run the test
+ */
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setupAndTeardown(this)
+ setup {
+ eachRun {
+ pipApp.launchViaIntent(wmHelper)
+ pipApp.enableEnterPipOnUserLeaveHint()
+ }
+ }
+ teardown {
+ eachRun {
+ pipApp.exit(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.goHome()
+ }
+ }
+
+ override fun pipAppLayerAlwaysVisible() {
+ if (!testSpec.isGesturalNavigation) super.pipAppLayerAlwaysVisible() else {
+ // pip layer in gesture nav will disappear during transition
+ testSpec.assertLayers {
+ this.isVisible(pipApp.component)
+ .then().isInvisible(pipApp.component)
+ .then().isVisible(pipApp.component)
+ }
+ }
+ }
+
+ override fun pipLayerReduces() {
+ // in gestural nav the pip enters through alpha animation
+ Assume.assumeFalse(testSpec.isGesturalNavigation)
+ super.pipLayerReduces()
+ }
+
+ override fun focusChanges() {
+ // in gestural nav the focus goes to different activity on swipe up
+ Assume.assumeFalse(testSpec.isGesturalNavigation)
+ super.focusChanges()
+ }
+
+ override fun pipLayerRemainInsideVisibleBounds() {
+ if (!testSpec.isGesturalNavigation) super.pipLayerRemainInsideVisibleBounds() else {
+ // pip layer in gesture nav will disappear during transition
+ testSpec.assertLayersStart {
+ this.visibleRegion(pipApp.component).coversAtMost(displayBounds)
+ }
+ testSpec.assertLayersEnd {
+ this.visibleRegion(pipApp.component).coversAtMost(displayBounds)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
index 0640ac526bd0..9ba51661aa5f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTest.kt
@@ -18,7 +18,6 @@ package com.android.wm.shell.flicker.pip
import android.platform.test.annotations.Presubmit
import android.view.Surface
-import androidx.test.filters.FlakyTest
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -43,7 +42,7 @@ import org.junit.runners.Parameterized
*
* Notes:
* 1. Some default assertions (e.g., nav bar, status bar and screen covered)
- * are inherited [PipTransition]
+ * are inherited from [PipTransition]
* 2. Part of the test setup occurs automatically via
* [com.android.server.wm.flicker.TransitionRunnerWithRules],
* including configuring navigation mode, initial orientation and ensuring no
@@ -54,7 +53,7 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group3
-class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
+open class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
/**
* Defines the transition used to run the test
@@ -77,11 +76,6 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
}
}
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
/**
* Checks [pipApp] window remains visible throughout the animation
*/
@@ -98,7 +92,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun pipAppLayerAlwaysVisible() {
+ open fun pipAppLayerAlwaysVisible() {
testSpec.assertLayers {
this.isVisible(pipApp.component)
}
@@ -122,7 +116,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun pipLayerRemainInsideVisibleBounds() {
+ open fun pipLayerRemainInsideVisibleBounds() {
testSpec.assertLayersVisibleRegion(pipApp.component) {
coversAtMost(displayBounds)
}
@@ -133,7 +127,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun pipLayerReduces() {
+ open fun pipLayerReduces() {
val layerName = pipApp.component.toLayerName()
testSpec.assertLayers {
val pipLayerList = this.layers { it.name.contains(layerName) && it.isVisible }
@@ -175,7 +169,7 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
*/
@Presubmit
@Test
- fun focusChanges() {
+ open fun focusChanges() {
testSpec.assertEventLog {
this.focusChanges(pipApp.`package`, "NexusLauncherActivity")
}
@@ -192,8 +186,10 @@ class EnterPipTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
@JvmStatic
fun getParams(): List<FlickerTestParameter> {
return FlickerTestParameterFactory.getInstance()
- .getConfigNonRotationTests(supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = 3)
+ .getConfigNonRotationTests(
+ supportedRotations = listOf(Surface.ROTATION_0),
+ repetitions = 3
+ )
}
}
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
index accb524d3de1..f50097d4ce84 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientationTest.kt
@@ -28,13 +28,12 @@ import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.traces.common.FlickerComponentName
import com.android.wm.shell.flicker.helpers.FixedAppHelper
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import com.android.wm.shell.flicker.testapp.Components.FixedActivity.EXTRA_FIXED_ORIENTATION
+import com.android.wm.shell.flicker.testapp.Components.PipActivity.ACTION_ENTER_PIP
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -114,14 +113,6 @@ class EnterPipToOtherOrientationTest(
override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
- * Checks that the [FlickerComponentName.STATUS_BAR] has the correct position at
- * the start and end of the transition
- */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- /**
* Checks that all parts of the screen are covered at the start and end of the transition
*
* TODO b/197726599 Prevents all states from being checked
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
index a3ed79bf0409..0768e82e491c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaExpandButtonClickTest.kt
@@ -79,11 +79,6 @@ class ExitPipViaExpandButtonClickTest(
}
/** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- /** {@inheritDoc} */
@FlakyTest(bugId = 197726610)
@Test
override fun pipLayerExpands() = super.pipLayerExpands()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
index 37e9344348d9..c6a705dacb8d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipViaIntentTest.kt
@@ -80,7 +80,17 @@ class ExitPipViaIntentTest(testSpec: FlickerTestParameter) : ExitPipToAppTransit
/** {@inheritDoc} */
@FlakyTest(bugId = 206753786)
@Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
+ override fun statusBarLayerRotatesScales() {
+ Assume.assumeFalse(isShellTransitionsEnabled)
+ super.statusBarLayerRotatesScales()
+ }
+
+ @Presubmit
+ @Test
+ fun statusBarLayerRotatesScales_ShellTransit() {
+ Assume.assumeTrue(isShellTransitionsEnabled)
+ super.statusBarLayerRotatesScales()
+ }
/** {@inheritDoc} */
@FlakyTest(bugId = 197726610)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
index ab07ede5bb32..128703ad332c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipWithSwipeDownTest.kt
@@ -25,7 +25,6 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group3
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -78,10 +77,6 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
@Test
override fun pipLayerBecomesInvisible() = super.pipLayerBecomesInvisible()
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
/**
* Checks that the focus doesn't change between windows during the transition
*/
@@ -108,4 +103,4 @@ class ExitPipWithSwipeDownTest(testSpec: FlickerTestParameter) : ExitPipTransiti
repetitions = 3)
}
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
index 1a21d32f568c..fe51228230cb 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipKeyboardTestShellTransit.kt
@@ -16,7 +16,7 @@
package com.android.wm.shell.flicker.pip
-import androidx.test.filters.FlakyTest
+import android.platform.test.annotations.Presubmit
import androidx.test.filters.RequiresDevice
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
@@ -35,7 +35,6 @@ import org.junit.runners.Parameterized
@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
@Group4
-@FlakyTest(bugId = 217777115)
class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardTest(testSpec) {
@Before
@@ -43,7 +42,7 @@ class PipKeyboardTestShellTransit(testSpec: FlickerTestParameter) : PipKeyboardT
Assume.assumeTrue(isShellTransitionsEnabled)
}
- @FlakyTest(bugId = 214452854)
+ @Presubmit
@Test
override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
} \ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
deleted file mode 100644
index 21175a0767a5..000000000000
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipLegacySplitScreenTest.kt
+++ /dev/null
@@ -1,148 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.flicker.pip
-
-import android.platform.test.annotations.Presubmit
-import android.view.Surface
-import androidx.test.filters.FlakyTest
-import androidx.test.filters.RequiresDevice
-import com.android.server.wm.flicker.FlickerParametersRunnerFactory
-import com.android.server.wm.flicker.FlickerTestParameter
-import com.android.server.wm.flicker.FlickerTestParameterFactory
-import com.android.server.wm.flicker.annotation.Group4
-import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.launchSplitScreen
-import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
-import com.android.wm.shell.flicker.helpers.BaseAppHelper.Companion.isShellTransitionsEnabled
-import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import com.android.wm.shell.flicker.helpers.ImeAppHelper
-import com.android.wm.shell.flicker.helpers.SplitScreenHelper
-import com.android.wm.shell.flicker.testapp.Components.PipActivity.EXTRA_ENTER_PIP
-import org.junit.Assume.assumeFalse
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.FixMethodOrder
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.MethodSorters
-import org.junit.runners.Parameterized
-
-/**
- * Test Pip with split-screen.
- * To run this test: `atest WMShellFlickerTests:PipLegacySplitScreenTest`
- */
-@RequiresDevice
-@RunWith(Parameterized::class)
-@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
-@Group4
-class PipLegacySplitScreenTest(testSpec: FlickerTestParameter) : PipTransition(testSpec) {
- private val imeApp = ImeAppHelper(instrumentation)
- private val testApp = FixedAppHelper(instrumentation)
-
- @Before
- open fun setup() {
- // Only run legacy split tests when the system is using legacy split screen.
- assumeTrue(SplitScreenHelper.isUsingLegacySplit())
- // Legacy split is having some issue with Shell transition, and will be deprecated soon.
- assumeFalse(isShellTransitionsEnabled())
- }
-
- override val transition: FlickerBuilder.() -> Unit
- get() = {
- setup {
- test {
- removeAllTasksButHome()
- device.wakeUpAndGoToHomeScreen()
- pipApp.launchViaIntent(stringExtras = mapOf(EXTRA_ENTER_PIP to "true"),
- wmHelper = wmHelper)
- }
- }
- transitions {
- testApp.launchViaIntent(wmHelper)
- device.launchSplitScreen(wmHelper)
- imeApp.launchViaIntent(wmHelper)
- }
- teardown {
- eachRun {
- imeApp.exit(wmHelper)
- testApp.exit(wmHelper)
- }
- test {
- removeAllTasksButHome()
- }
- }
- }
-
- /** {@inheritDoc} */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
- @FlakyTest(bugId = 161435597)
- @Test
- fun pipWindowInsideDisplayBounds() {
- testSpec.assertWmVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
- }
-
- @Presubmit
- @Test
- fun bothAppWindowsVisible() {
- testSpec.assertWmEnd {
- isAppWindowVisible(testApp.component)
- isAppWindowVisible(imeApp.component)
- doNotOverlap(testApp.component, imeApp.component)
- }
- }
-
- @FlakyTest(bugId = 161435597)
- @Test
- fun pipLayerInsideDisplayBounds() {
- testSpec.assertLayersVisibleRegion(pipApp.component) {
- coversAtMost(displayBounds)
- }
- }
-
- @Presubmit
- @Test
- fun bothAppLayersVisible() {
- testSpec.assertLayersEnd {
- visibleRegion(testApp.component).coversAtMost(displayBounds)
- visibleRegion(imeApp.component).coversAtMost(displayBounds)
- }
- }
-
- @FlakyTest(bugId = 161435597)
- @Test
- override fun entireScreenCovered() = super.entireScreenCovered()
-
- companion object {
- const val TEST_REPETITIONS = 2
-
- @Parameterized.Parameters(name = "{0}")
- @JvmStatic
- fun getParams(): Collection<FlickerTestParameter> {
- return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
- supportedRotations = listOf(Surface.ROTATION_0),
- repetitions = TEST_REPETITIONS
- )
- }
- }
-}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
index c1ee1a7cbb35..9fad4997e63a 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipRotationTest.kt
@@ -27,12 +27,9 @@ import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
import com.android.server.wm.flicker.entireScreenCovered
import com.android.server.wm.flicker.helpers.WindowUtils
-import com.android.server.wm.flicker.helpers.isShellTransitionsEnabled
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.navBarLayerRotatesAndScales
-import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.wm.shell.flicker.helpers.FixedAppHelper
-import org.junit.Assume
import org.junit.FixMethodOrder
import org.junit.Test
import org.junit.runner.RunWith
@@ -98,13 +95,6 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
override fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
/**
- * Checks the position of the status bar at the start and end of the transition
- */
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
-
- /**
* Checks that [fixedApp] layer is within [screenBoundsStart] at the start of the transition
*/
@Presubmit
@@ -141,14 +131,6 @@ open class PipRotationTest(testSpec: FlickerTestParameter) : PipTransition(testS
@Presubmit
@Test
fun pipLayerRotates_StartingBounds() {
- Assume.assumeFalse(isShellTransitionsEnabled)
- pipLayerRotates_StartingBounds_internal()
- }
-
- @FlakyTest(bugId = 228024285)
- @Test
- fun pipLayerRotates_StartingBounds_ShellTransit() {
- Assume.assumeTrue(isShellTransitionsEnabled)
pipLayerRotates_StartingBounds_internal()
}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
index e40f2bc1ed5a..51339a1deb4b 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinnedTest.kt
@@ -25,9 +25,9 @@ import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group4
import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.helpers.setRotation
import com.android.server.wm.flicker.helpers.wakeUpAndGoToHomeScreen
-import com.android.server.wm.flicker.helpers.WindowUtils
import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule.Companion.removeAllTasksButHome
import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
import com.android.wm.shell.flicker.testapp.Components
@@ -113,10 +113,6 @@ open class SetRequestedOrientationWhilePinnedTest(
@Test
override fun navBarLayerRotatesAndScales() = super.navBarLayerRotatesAndScales()
- @FlakyTest(bugId = 206753786)
- @Test
- override fun statusBarLayerRotatesScales() = super.statusBarLayerRotatesScales()
-
@Presubmit
@Test
fun pipWindowInsideDisplay() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
new file mode 100644
index 000000000000..702710caded7
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromAllApps.kt
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging app icon from all apps.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromAllApps`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromAllApps(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ @Before
+ open fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ taplInstrumentation.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.launchedAppState.taskbar
+ .openAllApps()
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(secondaryApp.component.packageName,
+ primaryApp.component.packageName)
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */)
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */)
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(secondaryApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
new file mode 100644
index 000000000000..7323d992ecd4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromNotification.kt
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.Until
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging app icon from notification.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromNotification`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromNotification(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ private val sendNotificationApp = SplitScreenHelper.getSendNotification(instrumentation)
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ // Send a notification
+ sendNotificationApp.launchViaIntent(wmHelper)
+ val sendNotification = device.wait(
+ Until.findObject(By.text("Send Notification")),
+ SplitScreenHelper.TIMEOUT_MS
+ )
+ sendNotification?.click() ?: error("Send notification button not found")
+
+ taplInstrumentation.goHome()
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ SplitScreenHelper.dragFromNotificationToSplit(instrumentation, device, wmHelper)
+ }
+ teardown {
+ eachRun {
+ sendNotificationApp.exit(wmHelper)
+ }
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() =
+ testSpec.layerBecomesVisible(sendNotificationApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, sendNotificationApp.component, true /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowIsVisibleAtEnd() =
+ testSpec.appWindowIsVisibleAtEnd(sendNotificationApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
new file mode 100644
index 000000000000..05c6e24ee89d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromTaskbar.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.Presubmit
+import android.view.WindowManagerPolicyConstants
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.annotation.Group1
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging app icon from taskbar.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromTaskbar`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@Group1
+class EnterSplitScreenByDragFromTaskbar(
+ testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+ @Before
+ fun before() {
+ Assume.assumeTrue(taplInstrumentation.isTablet)
+ }
+
+ override val transition: FlickerBuilder.() -> Unit
+ get() = {
+ super.transition(this)
+ setup {
+ eachRun {
+ taplInstrumentation.goHome()
+ SplitScreenHelper.createShortcutOnHotseatIfNotExist(
+ taplInstrumentation, secondaryApp.appName
+ )
+ primaryApp.launchViaIntent(wmHelper)
+ }
+ }
+ transitions {
+ taplInstrumentation.launchedAppState.taskbar
+ .getAppIcon(secondaryApp.appName)
+ .dragToSplitscreen(
+ secondaryApp.component.packageName,
+ primaryApp.component.packageName
+ )
+ }
+ }
+
+ @Presubmit
+ @Test
+ fun dividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+ @Presubmit
+ @Test
+ fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp.component)
+
+ @Presubmit
+ @Test
+ fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+ testSpec.endRotation, primaryApp.component, false /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisible(
+ testSpec.endRotation, secondaryApp.component, true /* splitLeftTop */
+ )
+
+ @Presubmit
+ @Test
+ fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp.component)
+
+ @Presubmit
+ @Test
+ fun secondaryAppWindowBecomesVisible() =
+ testSpec.appWindowBecomesVisible(secondaryApp.component)
+
+ companion object {
+ @Parameterized.Parameters(name = "{0}")
+ @JvmStatic
+ fun getParams(): List<FlickerTestParameter> {
+ return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+ repetitions = SplitScreenHelper.TEST_REPETITIONS,
+ supportedNavigationModes =
+ listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY)
+ )
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
new file mode 100644
index 000000000000..52c2daf96a3c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.app.Instrumentation
+import android.content.Context
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.FlickerBuilderProvider
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.helpers.SplitScreenHelper
+
+abstract class SplitScreenBase(protected val testSpec: FlickerTestParameter) {
+ protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+ protected val taplInstrumentation = LauncherInstrumentation()
+ protected val context: Context = instrumentation.context
+ protected val primaryApp = SplitScreenHelper.getPrimary(instrumentation)
+ protected val secondaryApp = SplitScreenHelper.getSecondary(instrumentation)
+
+ @FlickerBuilderProvider
+ fun buildFlicker(): FlickerBuilder {
+ return FlickerBuilder(instrumentation).apply {
+ transition(this)
+ }
+ }
+
+ protected open val transition: FlickerBuilder.() -> Unit
+ get() = {
+ setup {
+ test {
+ taplInstrumentation.setEnableRotation(true)
+ setRotation(testSpec.startRotation)
+ taplInstrumentation.setExpectedRotation(testSpec.startRotation)
+ }
+ }
+ teardown {
+ eachRun {
+ primaryApp.exit(wmHelper)
+ secondaryApp.exit(wmHelper)
+ }
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
index bd98585b67ec..bc0b0b6292b4 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/AndroidManifest.xml
@@ -92,6 +92,17 @@
</intent-filter>
</activity>
+ <activity android:name=".SendNotificationActivity"
+ android:taskAffinity="com.android.wm.shell.flicker.testapp.SendNotificationActivity"
+ android:theme="@style/CutoutShortEdges"
+ android:label="SendNotificationApp"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
<activity android:name=".NonResizeableActivity"
android:resizeableActivity="false"
android:taskAffinity="com.android.wm.shell.flicker.testapp.NonResizeableActivity"
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
new file mode 100644
index 000000000000..8d59b567e59b
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_notification.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2021 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="@android:color/black">
+
+ <Button
+ android:id="@+id/button_send_notification"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_centerHorizontal="true"
+ android:layout_centerVertical="true"
+ android:text="Send Notification" />
+</RelativeLayout>
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
index 909b77c87894..229098313afa 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/res/layout/activity_pip.xml
@@ -44,6 +44,39 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
+ android:checkedButton="@id/enter_pip_on_leave_disabled">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Enter PiP on home press"/>
+
+ <RadioButton
+ android:id="@+id/enter_pip_on_leave_disabled"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Disabled"
+ android:onClick="onAutoPipSelected"/>
+
+ <RadioButton
+ android:id="@+id/enter_pip_on_leave_manual"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Via code behind"
+ android:onClick="onAutoPipSelected"/>
+
+ <RadioButton
+ android:id="@+id/enter_pip_on_leave_autoenter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Auto-enter PiP"
+ android:onClick="onAutoPipSelected"/>
+ </RadioGroup>
+
+ <RadioGroup
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
android:checkedButton="@id/ratio_default">
<TextView
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
index 0ed59bdafd1d..a2b580da5898 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/Components.java
@@ -88,6 +88,12 @@ public class Components {
PACKAGE_NAME + ".SplitScreenSecondaryActivity");
}
+ public static class SendNotificationActivity {
+ public static final String LABEL = "SendNotificationApp";
+ public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
+ PACKAGE_NAME + ".SendNotificationActivity");
+ }
+
public static class LaunchBubbleActivity {
public static final String LABEL = "LaunchBubbleApp";
public static final ComponentName COMPONENT = new ComponentName(PACKAGE_NAME,
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
index a6ba7823e22d..615b1730579c 100644
--- a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/PipActivity.java
@@ -48,6 +48,7 @@ import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
+import android.widget.RadioButton;
import java.util.ArrayList;
import java.util.Arrays;
@@ -201,6 +202,17 @@ public class PipActivity extends Activity {
super.onDestroy();
}
+ @Override
+ protected void onUserLeaveHint() {
+ // Only used when auto PiP is disabled. This is to simulate the behavior that an app
+ // supports regular PiP but not auto PiP.
+ final boolean manuallyEnterPip =
+ ((RadioButton) findViewById(R.id.enter_pip_on_leave_manual)).isChecked();
+ if (manuallyEnterPip) {
+ enterPictureInPictureMode();
+ }
+ }
+
private RemoteAction buildRemoteAction(Icon icon, String label, String action) {
final Intent intent = new Intent(action);
final PendingIntent pendingIntent =
@@ -216,6 +228,21 @@ public class PipActivity extends Activity {
enterPictureInPictureMode(mPipParamsBuilder.build());
}
+ public void onAutoPipSelected(View v) {
+ switch (v.getId()) {
+ case R.id.enter_pip_on_leave_manual:
+ // disable auto enter PiP
+ case R.id.enter_pip_on_leave_disabled:
+ mPipParamsBuilder.setAutoEnterEnabled(false);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ break;
+ case R.id.enter_pip_on_leave_autoenter:
+ mPipParamsBuilder.setAutoEnterEnabled(true);
+ setPictureInPictureParams(mPipParamsBuilder.build());
+ break;
+ }
+ }
+
public void onRatioSelected(View v) {
switch (v.getId()) {
case R.id.ratio_default:
diff --git a/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
new file mode 100644
index 000000000000..8020ef2270a0
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/test-apps/flickerapp/src/com/android/wm/shell/flicker/testapp/SendNotificationActivity.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.testapp;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+
+public class SendNotificationActivity extends Activity {
+ private NotificationManager mNotificationManager;
+ private String mChannelId = "Channel id";
+ private String mChannelName = "Channel name";
+ private NotificationChannel mChannel;
+ private int mNotifyId = 0;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_notification);
+ findViewById(R.id.button_send_notification).setOnClickListener(this::sendNotification);
+
+ mChannel = new NotificationChannel(mChannelId, mChannelName,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ mNotificationManager = getSystemService(NotificationManager.class);
+ mNotificationManager.createNotificationChannel(mChannel);
+ }
+
+ private void sendNotification(View v) {
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0,
+ new Intent(this, SendNotificationActivity.class),
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE);
+ Notification notification = new Notification.Builder(this, mChannelId)
+ .setContentTitle("Notification App")
+ .setContentText("Notification content")
+ .setWhen(System.currentTimeMillis())
+ .setSmallIcon(R.drawable.ic_message)
+ .setContentIntent(pendingIntent)
+ .build();
+
+ mNotificationManager.notify(mNotifyId, notification);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index ea10be564351..1a8b9540cbd0 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -28,6 +28,9 @@ android_test {
"**/*.java",
"**/*.kt",
],
+ resource_dirs: [
+ "res",
+ ],
static_libs: [
"WindowManager-Shell",
@@ -65,4 +68,9 @@ android_test {
optimize: {
enabled: false,
},
+
+ aaptflags: [
+ "--extra-packages",
+ "com.android.wm.shell.tests",
+ ],
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
new file mode 100644
index 000000000000..f8b3fb3def62
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/MockSurfaceControlHelper.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static org.mockito.Mockito.RETURNS_SELF;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.view.SurfaceControl;
+
+/**
+ * Helper class to provide mocks for {@link SurfaceControl.Builder} and
+ * {@link SurfaceControl.Transaction} with method chaining support.
+ */
+public class MockSurfaceControlHelper {
+ private MockSurfaceControlHelper() {}
+
+ /**
+ * Creates a mock {@link SurfaceControl.Builder} that supports method chaining and return the
+ * given {@link SurfaceControl} when calling {@link SurfaceControl.Builder#build()}.
+ *
+ * @param mockSurfaceControl the first {@link SurfaceControl} to return
+ * @return the mock of {@link SurfaceControl.Builder}
+ */
+ public static SurfaceControl.Builder createMockSurfaceControlBuilder(
+ SurfaceControl mockSurfaceControl) {
+ final SurfaceControl.Builder mockBuilder = mock(SurfaceControl.Builder.class, RETURNS_SELF);
+ doReturn(mockSurfaceControl)
+ .when(mockBuilder)
+ .build();
+ return mockBuilder;
+ }
+
+ /**
+ * Creates a mock {@link SurfaceControl.Transaction} that supports method chaining.
+ * @return the mock of {@link SurfaceControl.Transaction}
+ */
+ public static SurfaceControl.Transaction createMockSurfaceControlTransaction() {
+ return mock(SurfaceControl.Transaction.class, RETURNS_SELF);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
new file mode 100644
index 000000000000..219e5ab6c651
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellInitTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.activityembedding.ActivityEmbeddingController;
+import com.android.wm.shell.bubbles.BubbleController;
+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.ShellExecutor;
+import com.android.wm.shell.draganddrop.DragAndDropController;
+import com.android.wm.shell.freeform.FreeformTaskListener;
+import com.android.wm.shell.fullscreen.FullscreenTaskListener;
+import com.android.wm.shell.kidsmode.KidsModeTaskOrganizer;
+import com.android.wm.shell.pip.phone.PipTouchHandler;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.startingsurface.StartingWindowController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+import com.android.wm.shell.transition.Transitions;
+import com.android.wm.shell.unfold.UnfoldAnimationController;
+import com.android.wm.shell.unfold.UnfoldTransitionHandler;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.Optional;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ShellInitTest extends ShellTestCase {
+
+ @Mock private ShellController mShellController;
+ @Mock private DisplayController mDisplayController;
+ @Mock private DisplayImeController mDisplayImeController;
+ @Mock private DisplayInsetsController mDisplayInsetsController;
+ @Mock private DragAndDropController mDragAndDropController;
+ @Mock private ShellTaskOrganizer mShellTaskOrganizer;
+ @Mock private KidsModeTaskOrganizer mKidsModeTaskOrganizer;
+ @Mock private Optional<BubbleController> mBubblesOptional;
+ @Mock private Optional<SplitScreenController> mSplitScreenOptional;
+ @Mock private Optional<PipTouchHandler> mPipTouchHandlerOptional;
+ @Mock private FullscreenTaskListener mFullscreenTaskListener;
+ @Mock private Optional<UnfoldAnimationController> mUnfoldAnimationController;
+ @Mock private Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
+ @Mock private Optional<FreeformTaskListener<?>> mFreeformTaskListenerOptional;
+ @Mock private Optional<RecentTasksController> mRecentTasks;
+ @Mock private Optional<ActivityEmbeddingController> mActivityEmbeddingController;
+ @Mock private Transitions mTransitions;
+ @Mock private StartingWindowController mStartingWindow;
+ @Mock private ShellExecutor mMainExecutor;
+
+ private ShellInit mImpl;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mImpl = new ShellInit(mShellController, mDisplayController, mDisplayImeController,
+ mDisplayInsetsController, mDragAndDropController, mShellTaskOrganizer,
+ mKidsModeTaskOrganizer, mBubblesOptional, mSplitScreenOptional,
+ mPipTouchHandlerOptional, mFullscreenTaskListener, mUnfoldAnimationController,
+ mUnfoldTransitionHandler, mFreeformTaskListenerOptional, mRecentTasks,
+ mActivityEmbeddingController, mTransitions, mStartingWindow, mMainExecutor);
+ }
+
+ @Test
+ public void testAddInitCallbacks_expectCalledInOrder() {
+ ArrayList<Integer> results = new ArrayList<>();
+ mImpl.addInitCallback(() -> {
+ results.add(1);
+ }, new Object());
+ mImpl.addInitCallback(() -> {
+ results.add(2);
+ }, new Object());
+ mImpl.addInitCallback(() -> {
+ results.add(3);
+ }, new Object());
+ mImpl.init();
+ assertTrue(results.get(0) == 1);
+ assertTrue(results.get(1) == 2);
+ assertTrue(results.get(2) == 3);
+ }
+
+ @Test
+ public void testNoInitCallbacksAfterInit_expectException() {
+ mImpl.init();
+ try {
+ mImpl.addInitCallback(() -> {}, new Object());
+ fail("Expected exception when adding callback after init");
+ } catch (IllegalArgumentException e) {
+ // Expected
+ }
+ }
+
+ @Test
+ public void testDoubleInit_expectNoOp() {
+ ArrayList<Integer> results = new ArrayList<>();
+ mImpl.addInitCallback(() -> {
+ results.add(1);
+ }, new Object());
+ mImpl.init();
+ assertTrue(results.size() == 1);
+ mImpl.init();
+ assertTrue(results.size() == 1);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
index a6caefe6d3e7..3dd00329253c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTaskOrganizerTests.java
@@ -26,11 +26,13 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_FULLSCREEN;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_MULTI_WINDOW;
import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
+import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
@@ -46,6 +48,7 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.SurfaceControl;
+import android.view.SurfaceSession;
import android.window.ITaskOrganizer;
import android.window.ITaskOrganizerController;
import android.window.TaskAppearedInfo;
@@ -76,7 +79,7 @@ import java.util.Optional;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class ShellTaskOrganizerTests {
+public class ShellTaskOrganizerTests extends ShellTestCase {
@Mock
private ITaskOrganizerController mTaskOrganizerController;
@@ -133,17 +136,30 @@ public class ShellTaskOrganizerTests {
.when(mTaskOrganizerController).registerTaskOrganizer(any());
} catch (RemoteException e) {}
mOrganizer = spy(new ShellTaskOrganizer(mTaskOrganizerController, mTestExecutor, mContext,
- mCompatUI, Optional.empty()));
+ mCompatUI, Optional.empty(), Optional.empty()));
}
@Test
- public void registerOrganizer_sendRegisterTaskOrganizer() throws RemoteException {
+ public void testRegisterOrganizer_sendRegisterTaskOrganizer() throws RemoteException {
mOrganizer.registerOrganizer();
verify(mTaskOrganizerController).registerTaskOrganizer(any(ITaskOrganizer.class));
}
@Test
+ public void testTaskLeashReleasedAfterVanished() throws RemoteException {
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
+ RunningTaskInfo taskInfo = createTaskInfo(1, WINDOWING_MODE_MULTI_WINDOW);
+ SurfaceControl taskLeash = new SurfaceControl.Builder(new SurfaceSession())
+ .setName("task").build();
+ mOrganizer.registerOrganizer();
+ mOrganizer.onTaskAppeared(taskInfo, taskLeash);
+ assertTrue(taskLeash.isValid());
+ mOrganizer.onTaskVanished(taskInfo);
+ assertTrue(!taskLeash.isValid());
+ }
+
+ @Test
public void testOneListenerPerType() {
mOrganizer.addListenerForType(new TrackingTaskListener(), TASK_LISTENER_TYPE_MULTI_WINDOW);
try {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
index 403dbf9d9554..b5ee037892ba 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/ShellTestCase.java
@@ -24,6 +24,8 @@ import android.testing.TestableContext;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.internal.protolog.common.ProtoLog;
+
import org.junit.After;
import org.junit.Before;
import org.mockito.MockitoAnnotations;
@@ -37,6 +39,9 @@ public abstract class ShellTestCase {
@Before
public void shellSetup() {
+ // Disable protolog tool when running the tests from studio
+ ProtoLog.REQUIRE_PROTOLOGTOOL = false;
+
MockitoAnnotations.initMocks(this);
final Context context =
InstrumentationRegistry.getInstrumentation().getTargetContext();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
index 51eec27cfc0e..c0720cf04028 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/TestRunningTaskInfoBuilder.java
@@ -25,8 +25,10 @@ import static org.mockito.Mockito.mock;
import android.app.ActivityManager;
import android.app.WindowConfiguration;
+import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
+import android.view.Display;
import android.window.IWindowContainerToken;
import android.window.WindowContainerToken;
@@ -38,6 +40,10 @@ public final class TestRunningTaskInfoBuilder {
private int mParentTaskId = INVALID_TASK_ID;
private @WindowConfiguration.ActivityType int mActivityType = ACTIVITY_TYPE_STANDARD;
private @WindowConfiguration.WindowingMode int mWindowingMode = WINDOWING_MODE_UNDEFINED;
+ private int mDisplayId = Display.DEFAULT_DISPLAY;
+ private ActivityManager.TaskDescription.Builder mTaskDescriptionBuilder = null;
+ private final Point mPositionInParent = new Point();
+ private boolean mIsVisible = false;
public static WindowContainerToken createMockWCToken() {
final IWindowContainerToken itoken = mock(IWindowContainerToken.class);
@@ -68,17 +74,42 @@ public final class TestRunningTaskInfoBuilder {
return this;
}
+ public TestRunningTaskInfoBuilder setDisplayId(int displayId) {
+ mDisplayId = displayId;
+ return this;
+ }
+
+ public TestRunningTaskInfoBuilder setTaskDescriptionBuilder(
+ ActivityManager.TaskDescription.Builder builder) {
+ mTaskDescriptionBuilder = builder;
+ return this;
+ }
+
+ public TestRunningTaskInfoBuilder setPositionInParent(int x, int y) {
+ mPositionInParent.set(x, y);
+ return this;
+ }
+
+ public TestRunningTaskInfoBuilder setVisible(boolean isVisible) {
+ mIsVisible = isVisible;
+ return this;
+ }
+
public ActivityManager.RunningTaskInfo build() {
final ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
- info.parentTaskId = INVALID_TASK_ID;
info.taskId = sNextTaskId++;
info.parentTaskId = mParentTaskId;
+ info.displayId = mDisplayId;
info.configuration.windowConfiguration.setBounds(mBounds);
info.configuration.windowConfiguration.setActivityType(mActivityType);
info.configuration.windowConfiguration.setWindowingMode(mWindowingMode);
info.token = mToken;
info.isResizeable = true;
info.supportsMultiWindow = true;
+ info.taskDescription =
+ mTaskDescriptionBuilder != null ? mTaskDescriptionBuilder.build() : null;
+ info.positionInParent = mPositionInParent;
+ info.isVisible = mIsVisible;
return info;
}
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
deleted file mode 100644
index e73d9aaf190a..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairTests.java
+++ /dev/null
@@ -1,123 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.hardware.display.DisplayManager;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-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;
-
-/**
- * Tests for {@link AppPair}
- * Build/Install/Run:
- * atest WMShellUnitTests:AppPairTests
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AppPairTests extends ShellTestCase {
-
- private AppPairsController mController;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- when(mDisplayController.getDisplay(anyInt())).thenReturn(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController);
- spyOn(mController);
- }
-
- @After
- public void tearDown() {}
-
- @Test
- @UiThreadTest
- public void testContains() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- pair.unpair();
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- }
-
- @Test
- @UiThreadTest
- public void testVanishUnpairs() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- pair.onTaskVanished(task1);
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- }
-
- @Test
- @UiThreadTest
- public void testOnTaskInfoChanged_notSupportsMultiWindow() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- task1.supportsMultiWindow = false;
- pair.onTaskInfoChanged(task1);
- verify(mController).unpair(pair.getRootTaskId());
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
deleted file mode 100644
index 505c153eff9c..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsControllerTests.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static android.view.Display.DEFAULT_DISPLAY;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager;
-import android.hardware.display.DisplayManager;
-
-import androidx.test.annotation.UiThreadTest;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-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;
-
-/** Tests for {@link AppPairsController} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AppPairsControllerTests extends ShellTestCase {
- private TestAppPairsController mController;
- private TestAppPairsPool mPool;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- when(mDisplayController.getDisplay(anyInt())).thenReturn(
- mContext.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY));
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController);
- mPool = mController.getPool();
- }
-
- @After
- public void tearDown() {}
-
- @Test
- @UiThreadTest
- public void testPairUnpair() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
- assertThat(mPool.poolSize()).isGreaterThan(0);
-
- mController.unpair(task2.taskId);
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- assertThat(mPool.poolSize()).isGreaterThan(1);
- }
-
- @Test
- @UiThreadTest
- public void testUnpair_DontReleaseToPool() {
- final ActivityManager.RunningTaskInfo task1 = new TestRunningTaskInfoBuilder().build();
- final ActivityManager.RunningTaskInfo task2 = new TestRunningTaskInfoBuilder().build();
-
- final AppPair pair = mController.pairInner(task1, task2);
- assertThat(pair.contains(task1.taskId)).isTrue();
- assertThat(pair.contains(task2.taskId)).isTrue();
-
- mController.unpair(task2.taskId, false /* releaseToPool */);
- assertThat(pair.contains(task1.taskId)).isFalse();
- assertThat(pair.contains(task2.taskId)).isFalse();
- assertThat(mPool.poolSize()).isEqualTo(1);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
deleted file mode 100644
index a3f134ee97ed..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/AppPairsPoolTests.java
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.when;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-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;
-
-/** Tests for {@link AppPairsPool} */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class AppPairsPoolTests extends ShellTestCase {
- private TestAppPairsController mController;
- private TestAppPairsPool mPool;
- @Mock private SyncTransactionQueue mSyncQueue;
- @Mock private ShellTaskOrganizer mTaskOrganizer;
- @Mock private DisplayController mDisplayController;
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mDisplayController.getDisplayContext(anyInt())).thenReturn(mContext);
- mController = new TestAppPairsController(
- mTaskOrganizer,
- mSyncQueue,
- mDisplayController);
- mPool = mController.getPool();
- }
-
- @After
- public void tearDown() {}
-
- @Test
- public void testInitialState() {
- // Pool should always start off with at least 1 entry.
- assertThat(mPool.poolSize()).isGreaterThan(0);
- }
-
- @Test
- public void testAcquireRelease() {
- assertThat(mPool.poolSize()).isGreaterThan(0);
- final AppPair appPair = mPool.acquire();
- assertThat(mPool.poolSize()).isGreaterThan(0);
- mPool.release(appPair);
- assertThat(mPool.poolSize()).isGreaterThan(1);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
deleted file mode 100644
index 294bc1276291..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsController.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import static org.mockito.Mockito.mock;
-
-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.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-
-public class TestAppPairsController extends AppPairsController {
- private TestAppPairsPool mPool;
-
- public TestAppPairsController(ShellTaskOrganizer organizer, SyncTransactionQueue syncQueue,
- DisplayController displayController) {
- super(organizer, syncQueue, displayController, mock(ShellExecutor.class),
- mock(DisplayImeController.class), mock(DisplayInsetsController.class));
- mPool = new TestAppPairsPool(this);
- setPairsPool(mPool);
- }
-
- TestAppPairsPool getPool() {
- return mPool;
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
deleted file mode 100644
index 1ee7fff44892..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/apppairs/TestAppPairsPool.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.apppairs;
-
-import android.app.ActivityManager;
-
-import com.android.wm.shell.TestRunningTaskInfoBuilder;
-
-public class TestAppPairsPool extends AppPairsPool{
- TestAppPairsPool(AppPairsController controller) {
- super(controller);
- }
-
- @Override
- void incrementPool() {
- final AppPair entry = new AppPair(mController);
- final ActivityManager.RunningTaskInfo info =
- new TestRunningTaskInfoBuilder().build();
- entry.onTaskAppeared(info, null /* leash */);
- release(entry);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index e7c5cb2183db..31e55e7116f0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -57,6 +57,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import org.junit.Before;
@@ -74,7 +75,7 @@ import org.mockito.MockitoAnnotations;
@TestableLooper.RunWithLooper
@SmallTest
@RunWith(AndroidTestingRunner.class)
-public class BackAnimationControllerTest {
+public class BackAnimationControllerTest extends ShellTestCase {
private static final String ANIMATION_ENABLED = "1";
private final TestShellExecutor mShellExecutor = new TestShellExecutor();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
new file mode 100644
index 000000000000..44ff35466ae2
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesNavBarMotionEventHandlerTest.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static android.view.MotionEvent.ACTION_CANCEL;
+import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_MOVE;
+import static android.view.MotionEvent.ACTION_UP;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.floatThat;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.os.SystemClock;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+/**
+ * Test {@link MotionEvent} handling in {@link BubblesNavBarMotionEventHandler}.
+ * Verifies that swipe events
+ */
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner.class)
+public class BubblesNavBarMotionEventHandlerTest extends ShellTestCase {
+
+ private BubblesNavBarMotionEventHandler mMotionEventHandler;
+ @Mock
+ private WindowManager mWindowManager;
+ @Mock
+ private Runnable mInterceptTouchRunnable;
+ @Mock
+ private MotionEventListener mMotionEventListener;
+ private long mMotionEventTime;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
+ mWindowManager);
+ mMotionEventHandler = new BubblesNavBarMotionEventHandler(getContext(), positioner,
+ mInterceptTouchRunnable, mMotionEventListener);
+ mMotionEventTime = SystemClock.uptimeMillis();
+ }
+
+ @Test
+ public void testMotionEvent_swipeUpInGestureZone_handled() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 690));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 490));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 390));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 390));
+
+ verify(mMotionEventListener).onDown(0, 990);
+ verify(mMotionEventListener).onMove(0, -300);
+ verify(mMotionEventListener).onMove(0, -500);
+ verify(mMotionEventListener).onMove(0, -600);
+ // Check that velocity up is about 5000
+ verify(mMotionEventListener).onUp(eq(0f), floatThat(f -> Math.round(f) == -5000));
+ verifyZeroInteractions(mMotionEventListener);
+ verify(mInterceptTouchRunnable).run();
+ }
+
+ @Test
+ public void testMotionEvent_swipeUpOutsideGestureZone_ignored() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 500));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 100));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 100));
+
+ verifyZeroInteractions(mMotionEventListener);
+ verifyZeroInteractions(mInterceptTouchRunnable);
+ }
+
+ @Test
+ public void testMotionEvent_horizontalMoveMoreThanTouchSlop_handled() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 100, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 100, 990));
+
+ verify(mMotionEventListener).onDown(0, 990);
+ verify(mMotionEventListener).onMove(100, 0);
+ verify(mMotionEventListener).onUp(0, 0);
+ verifyZeroInteractions(mMotionEventListener);
+ verify(mInterceptTouchRunnable).run();
+ }
+
+ @Test
+ public void testMotionEvent_moveLessThanTouchSlop_ignored() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_MOVE, 0, 989));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_UP, 0, 989));
+
+ verify(mMotionEventListener).onDown(0, 990);
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyZeroInteractions(mInterceptTouchRunnable);
+ }
+
+ @Test
+ public void testMotionEvent_actionCancel_listenerNotified() {
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_DOWN, 0, 990));
+ mMotionEventHandler.onMotionEvent(newEvent(ACTION_CANCEL, 0, 990));
+ verify(mMotionEventListener).onDown(0, 990);
+ verify(mMotionEventListener).onCancel();
+ verifyNoMoreInteractions(mMotionEventListener);
+ verifyZeroInteractions(mInterceptTouchRunnable);
+ }
+
+ private MotionEvent newEvent(int actionDown, float x, float y) {
+ MotionEvent event = MotionEvent.obtain(0L, mMotionEventTime, actionDown, x, y, 0);
+ mMotionEventTime += 10;
+ return event;
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java
index d5fbe556045a..0537d0ea2404 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubblesTestActivity.java
@@ -20,7 +20,7 @@ import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
-import com.android.wm.shell.R;
+import com.android.wm.shell.tests.R;
/**
* Referenced by NotificationTestHelper#makeBubbleMetadata
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
new file mode 100644
index 000000000000..991913afbb90
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.wm.shell.bubbles.animation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.ViewConfiguration;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.bubbles.BubbleExpandedView;
+import com.android.wm.shell.bubbles.TestableBubblePositioner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ExpandedViewAnimationControllerTest extends ShellTestCase {
+
+ private ExpandedViewAnimationController mController;
+
+ @Mock
+ private WindowManager mWindowManager;
+
+ @Mock
+ private BubbleExpandedView mMockExpandedView;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ TestableBubblePositioner positioner = new TestableBubblePositioner(getContext(),
+ mWindowManager);
+ mController = new ExpandedViewAnimationControllerImpl(getContext(), positioner);
+
+ mController.setExpandedView(mMockExpandedView);
+ when(mMockExpandedView.getContentHeight()).thenReturn(1000);
+ }
+
+ @Test
+ public void testUpdateDrag_expandedViewMovesUpAndClipped() {
+ // Drag by 50 pixels which corresponds to 10 pixels with overscroll
+ int dragDistance = 50;
+ int dampenedDistance = 10;
+
+ mController.updateDrag(dragDistance);
+
+ verify(mMockExpandedView).setTopClip(dampenedDistance);
+ verify(mMockExpandedView).setContentTranslationY(-dampenedDistance);
+ verify(mMockExpandedView).setManageButtonTranslationY(-dampenedDistance);
+ }
+
+ @Test
+ public void testUpdateDrag_zOrderUpdates() {
+ mController.updateDrag(10);
+ mController.updateDrag(20);
+
+ verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(true);
+ verify(mMockExpandedView, times(1)).setAnimating(true);
+ }
+
+ @Test
+ public void testUpdateDrag_moveBackToZero_zOrderRestored() {
+ mController.updateDrag(50);
+ reset(mMockExpandedView);
+ mController.updateDrag(0);
+ mController.updateDrag(0);
+
+ verify(mMockExpandedView, times(1)).setSurfaceZOrderedOnTop(false);
+ verify(mMockExpandedView, times(1)).setAnimating(false);
+ }
+
+ @Test
+ public void testUpdateDrag_hapticFeedbackOnlyOnce() {
+ // Drag by 10 which is below the collapse threshold - no feedback
+ mController.updateDrag(10);
+ verify(mMockExpandedView, times(0)).performHapticFeedback(anyInt());
+ // 150 takes it over the threshold - perform feedback
+ mController.updateDrag(150);
+ verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
+ // Continue dragging, no more feedback
+ mController.updateDrag(200);
+ verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
+ // Drag below threshold and over again - no more feedback
+ mController.updateDrag(10);
+ mController.updateDrag(150);
+ verify(mMockExpandedView, times(1)).performHapticFeedback(anyInt());
+ }
+
+ @Test
+ public void testShouldCollapse_doNotCollapseIfNotDragged() {
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ @Test
+ public void testShouldCollapse_doNotCollapseIfVelocityDown() {
+ assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
+ mController.setSwipeVelocity(getVelocityAboveMinFling());
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ @Test
+ public void tesShouldCollapse_doNotCollapseIfVelocityUpIsSmall() {
+ assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
+ mController.setSwipeVelocity(-getVelocityBelowMinFling());
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ @Test
+ public void testShouldCollapse_collapseIfVelocityUpIsLarge() {
+ assumeTrue("Min fling velocity should be > 1 for this test", getMinFlingVelocity() > 1);
+ mController.setSwipeVelocity(-getVelocityAboveMinFling());
+ assertThat(mController.shouldCollapse()).isTrue();
+ }
+
+ @Test
+ public void testShouldCollapse_collapseIfPastThreshold() {
+ mController.updateDrag(500);
+ assertThat(mController.shouldCollapse()).isTrue();
+ }
+
+ @Test
+ public void testReset() {
+ mController.updateDrag(100);
+ reset(mMockExpandedView);
+ mController.reset();
+ verify(mMockExpandedView, atLeastOnce()).setAnimating(false);
+ verify(mMockExpandedView).setContentAlpha(1);
+ verify(mMockExpandedView).setBackgroundAlpha(1);
+ verify(mMockExpandedView).setManageButtonAlpha(1);
+ verify(mMockExpandedView).setManageButtonAlpha(1);
+ verify(mMockExpandedView).setTopClip(0);
+ verify(mMockExpandedView).setContentTranslationY(-0f);
+ verify(mMockExpandedView).setManageButtonTranslationY(-0f);
+ verify(mMockExpandedView).setBottomClip(0);
+ verify(mMockExpandedView).movePointerBy(0, 0);
+ assertThat(mController.shouldCollapse()).isFalse();
+ }
+
+ private int getVelocityBelowMinFling() {
+ return getMinFlingVelocity() - 1;
+ }
+
+ private int getVelocityAboveMinFling() {
+ return getMinFlingVelocity() + 1;
+ }
+
+ private int getMinFlingVelocity() {
+ return ViewConfiguration.get(getContext()).getScaledMinimumFlingVelocity();
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
index 0972cf2c032f..1636c5f73133 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/storage/BubblePersistentRepositoryTest.kt
@@ -25,6 +25,7 @@ import com.android.wm.shell.bubbles.storage.BubbleXmlHelperTest.Companion.sparse
import junit.framework.Assert.assertEquals
import junit.framework.Assert.assertNotNull
import junit.framework.Assert.assertTrue
+import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
@@ -61,6 +62,12 @@ class BubblePersistentRepositoryTest : ShellTestCase() {
bubbles.put(1, user1Bubbles)
}
+ @After
+ fun teardown() {
+ // Clean up the any persisted bubbles for the next run
+ repository.persistsToDisk(SparseArray())
+ }
+
@Test
fun testReadWriteOperation() {
// Verify read before write doesn't cause FileNotFoundException
@@ -71,4 +78,4 @@ class BubblePersistentRepositoryTest : ShellTestCase() {
repository.persistsToDisk(bubbles)
assertTrue(sparseArraysEqual(bubbles, repository.readFromDisk()))
}
-} \ No newline at end of file
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index b88845044263..587782cb79ad 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -39,6 +39,7 @@ import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
import com.android.internal.view.IInputMethodManager;
+import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
import org.junit.Test;
@@ -46,7 +47,7 @@ import org.junit.Test;
import java.util.concurrent.Executor;
@SmallTest
-public class DisplayImeControllerTest {
+public class DisplayImeControllerTest extends ShellTestCase {
private SurfaceControl.Transaction mT;
private DisplayImeController.PerDisplay mPerDisplay;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
index 3bf06cc0ede3..4a7fd3d259da 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import android.content.ComponentName;
import android.os.RemoteException;
import android.util.SparseArray;
import android.view.IDisplayWindowInsetsController;
@@ -34,6 +35,7 @@ import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import org.junit.Before;
@@ -45,7 +47,7 @@ import org.mockito.MockitoAnnotations;
import java.util.List;
@SmallTest
-public class DisplayInsetsControllerTest {
+public class DisplayInsetsControllerTest extends ShellTestCase {
private static final int SECOND_DISPLAY = DEFAULT_DISPLAY + 10;
@@ -164,7 +166,7 @@ public class DisplayInsetsControllerTest {
int hideInsetsCount = 0;
@Override
- public void topFocusedWindowChanged(String packageName,
+ public void topFocusedWindowChanged(ComponentName component,
InsetsVisibilities requestedVisibilities) {
topFocusedWindowChangedCount++;
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
index 0ffa5b35331d..514390fa52f9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayLayoutTest.java
@@ -41,6 +41,7 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;
import com.android.internal.policy.SystemBarUtils;
+import com.android.wm.shell.ShellTestCase;
import org.junit.After;
import org.junit.Before;
@@ -54,7 +55,7 @@ import org.mockito.MockitoSession;
* atest WMShellUnitTests:DisplayLayoutTest
*/
@SmallTest
-public class DisplayLayoutTest {
+public class DisplayLayoutTest extends ShellTestCase {
private MockitoSession mMockitoSession;
@Before
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
index 96938ebc27df..1347e061eb45 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/TaskStackListenerImplTest.java
@@ -35,6 +35,8 @@ import android.window.TaskSnapshot;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -47,7 +49,7 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
-public class TaskStackListenerImplTest {
+public class TaskStackListenerImplTest extends ShellTestCase {
@Mock
private IActivityTaskManager mActivityTaskManager;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index f1e602fcf778..95725bbfd855 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -133,7 +133,7 @@ public class SplitLayoutTests extends ShellTestCase {
mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
waitDividerFlingFinished();
- verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false));
+ verify(mSplitLayoutHandler).onSnappedToDismiss(eq(false), anyInt());
}
@Test
@@ -145,7 +145,7 @@ public class SplitLayoutTests extends ShellTestCase {
mSplitLayout.snapToTarget(0 /* currentPosition */, snapTarget);
waitDividerFlingFinished();
- verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true));
+ verify(mSplitLayoutHandler).onSnappedToDismiss(eq(true), anyInt());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
index 596100dcdead..828c13ecfda6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/CompatUIControllerTest.java
@@ -53,6 +53,7 @@ import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.compatui.letterboxedu.LetterboxEduWindowManager;
+import com.android.wm.shell.sysui.ShellController;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -78,6 +79,7 @@ public class CompatUIControllerTest extends ShellTestCase {
private static final int TASK_ID = 12;
private CompatUIController mController;
+ private @Mock ShellController mMockShellController;
private @Mock DisplayController mMockDisplayController;
private @Mock DisplayInsetsController mMockDisplayInsetsController;
private @Mock DisplayLayout mMockDisplayLayout;
@@ -105,7 +107,7 @@ public class CompatUIControllerTest extends ShellTestCase {
doReturn(TASK_ID).when(mMockLetterboxEduLayout).getTaskId();
doReturn(true).when(mMockLetterboxEduLayout).createLayout(anyBoolean());
doReturn(true).when(mMockLetterboxEduLayout).updateCompatInfo(any(), any(), anyBoolean());
- mController = new CompatUIController(mContext, mMockDisplayController,
+ mController = new CompatUIController(mContext, mMockShellController, mMockDisplayController,
mMockDisplayInsetsController, mMockImeController, mMockSyncQueue, mMockExecutor,
mMockTransitionsLazy) {
@Override
@@ -124,6 +126,11 @@ public class CompatUIControllerTest extends ShellTestCase {
}
@Test
+ public void instantiateController_registerKeyguardChangeListener() {
+ verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
public void testListenerRegistered() {
verify(mMockDisplayController).addDisplayWindowListener(mController);
verify(mMockImeController).addPositionProcessor(mController);
@@ -324,7 +331,7 @@ public class CompatUIControllerTest extends ShellTestCase {
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
// Verify that the restart button is hidden after keyguard becomes showing.
- mController.onKeyguardShowingChanged(true);
+ mController.onKeyguardVisibilityChanged(true, false, false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -340,7 +347,7 @@ public class CompatUIControllerTest extends ShellTestCase {
false);
// Verify button is shown after keyguard becomes not showing.
- mController.onKeyguardShowingChanged(false);
+ mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
@@ -352,7 +359,7 @@ public class CompatUIControllerTest extends ShellTestCase {
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
- mController.onKeyguardShowingChanged(true);
+ mController.onKeyguardVisibilityChanged(true, false, false);
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -360,7 +367,7 @@ public class CompatUIControllerTest extends ShellTestCase {
clearInvocations(mMockCompatLayout, mMockLetterboxEduLayout);
// Verify button remains hidden after keyguard becomes not showing since IME is showing.
- mController.onKeyguardShowingChanged(false);
+ mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(false);
verify(mMockLetterboxEduLayout).updateVisibility(false);
@@ -378,7 +385,7 @@ public class CompatUIControllerTest extends ShellTestCase {
/* hasSizeCompat= */ true, CAMERA_COMPAT_CONTROL_HIDDEN), mMockTaskListener);
mController.onImeVisibilityChanged(DISPLAY_ID, /* isShowing= */ true);
- mController.onKeyguardShowingChanged(true);
+ mController.onKeyguardVisibilityChanged(true, false, false);
verify(mMockCompatLayout, times(2)).updateVisibility(false);
verify(mMockLetterboxEduLayout, times(2)).updateVisibility(false);
@@ -392,7 +399,7 @@ public class CompatUIControllerTest extends ShellTestCase {
verify(mMockLetterboxEduLayout).updateVisibility(false);
// Verify button is shown after keyguard becomes not showing.
- mController.onKeyguardShowingChanged(false);
+ mController.onKeyguardVisibilityChanged(false, false, false);
verify(mMockCompatLayout).updateVisibility(true);
verify(mMockLetterboxEduLayout).updateVisibility(true);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
index aaeebef03d0f..e209971998c8 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropControllerTest.java
@@ -21,6 +21,7 @@ import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.DragEvent.ACTION_DRAG_STARTED;
import static org.junit.Assert.assertFalse;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -44,9 +45,11 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.UiEventLogger;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.splitscreen.SplitScreenController;
+import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
import org.junit.Test;
@@ -61,28 +64,38 @@ import java.util.Optional;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class DragAndDropControllerTest {
+public class DragAndDropControllerTest extends ShellTestCase {
@Mock
private Context mContext;
-
+ @Mock
+ private ShellController mShellController;
@Mock
private DisplayController mDisplayController;
-
@Mock
private UiEventLogger mUiEventLogger;
-
@Mock
private DragAndDropController.DragAndDropListener mDragAndDropListener;
+ @Mock
+ private IconProvider mIconProvider;
+ @Mock
+ private ShellExecutor mMainExecutor;
+ @Mock
+ private SplitScreenController mSplitScreenController;
private DragAndDropController mController;
@Before
public void setUp() throws RemoteException {
MockitoAnnotations.initMocks(this);
- mController = new DragAndDropController(mContext, mDisplayController, mUiEventLogger,
- mock(IconProvider.class), mock(ShellExecutor.class));
- mController.initialize(Optional.of(mock(SplitScreenController.class)));
+ mController = new DragAndDropController(mContext, mShellController, mDisplayController,
+ mUiEventLogger, mIconProvider, mMainExecutor);
+ mController.initialize(Optional.of(mSplitScreenController));
+ }
+
+ @Test
+ public void instantiateController_registerConfigChangeListener() {
+ verify(mShellController, times(1)).addConfigurationChangeListener(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index bb6026c36c97..9e988e8e8726 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -34,7 +34,6 @@ import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPL
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
-import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
import static junit.framework.Assert.fail;
@@ -57,7 +56,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
-import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Insets;
@@ -68,6 +66,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.InstanceId;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.draganddrop.DragAndDropPolicy.Target;
import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -87,7 +86,7 @@ import java.util.HashSet;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class DragAndDropPolicyTest {
+public class DragAndDropPolicyTest extends ShellTestCase {
@Mock
private Context mContext;
@@ -182,8 +181,10 @@ public class DragAndDropPolicyTest {
info.configuration.windowConfiguration.setActivityType(actType);
info.configuration.windowConfiguration.setWindowingMode(winMode);
info.isResizeable = true;
- info.baseActivity = new ComponentName(getInstrumentation().getContext().getPackageName(),
+ info.baseActivity = new ComponentName(getInstrumentation().getContext(),
".ActivityWithMode" + winMode);
+ info.baseIntent = new Intent();
+ info.baseIntent.setComponent(info.baseActivity);
ActivityInfo activityInfo = new ActivityInfo();
activityInfo.packageName = info.baseActivity.getPackageName();
activityInfo.name = info.baseActivity.getClassName();
@@ -263,62 +264,6 @@ public class DragAndDropPolicyTest {
}
}
- @Test
- public void testLaunchMultipleTask_differentActivity() {
- setRunningTask(mFullscreenAppTask);
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
- Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
- assertNull(fillInIntent);
- }
-
- @Test
- public void testLaunchMultipleTask_differentActivity_inSplitscreen() {
- setRunningTask(mFullscreenAppTask);
- doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
- doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
- Intent fillInIntent = mPolicy.getStartIntentFillInIntent(mock(PendingIntent.class), 0);
- assertNull(fillInIntent);
- }
-
- @Test
- public void testLaunchMultipleTask_sameActivity() {
- setRunningTask(mFullscreenAppTask);
-
- // Replace the mocked drag pending intent and ensure it resolves to the same activity
- PendingIntent launchIntent = mock(PendingIntent.class);
- ResolveInfo launchInfo = new ResolveInfo();
- launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
- doReturn(Collections.singletonList(launchInfo))
- .when(launchIntent).queryIntentComponents(anyInt());
- mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
- launchIntent);
-
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
- Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
- assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
- }
-
- @Test
- public void testLaunchMultipleTask_sameActivity_inSplitScreen() {
- setRunningTask(mFullscreenAppTask);
-
- // Replace the mocked drag pending intent and ensure it resolves to the same activity
- PendingIntent launchIntent = mock(PendingIntent.class);
- ResolveInfo launchInfo = new ResolveInfo();
- launchInfo.activityInfo = mFullscreenAppTask.topActivityInfo;
- doReturn(Collections.singletonList(launchInfo))
- .when(launchIntent).queryIntentComponents(anyInt());
- mActivityClipData.getItemAt(0).getIntent().putExtra(ClipDescription.EXTRA_PENDING_INTENT,
- launchIntent);
-
- doReturn(true).when(mSplitScreenStarter).isSplitScreenVisible();
- doReturn(mFullscreenAppTask).when(mSplitScreenStarter).getTaskInfo(anyInt());
- mPolicy.start(mLandscapeDisplayLayout, mActivityClipData, mLoggerSessionId);
- Intent fillInIntent = mPolicy.getStartIntentFillInIntent(launchIntent, 0);
- assertTrue((fillInIntent.getFlags() & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
- }
-
private Target filterTargetByType(ArrayList<Target> targets, int type) {
for (Target t : targets) {
if (type == t.type) {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
deleted file mode 100644
index 4523e2c9cba5..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/fullscreen/FullscreenTaskListenerTest.java
+++ /dev/null
@@ -1,166 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.fullscreen;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
-
-import static org.junit.Assume.assumeFalse;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.app.ActivityManager.RunningTaskInfo;
-import android.app.WindowConfiguration;
-import android.content.res.Configuration;
-import android.graphics.Point;
-import android.os.SystemProperties;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.recents.RecentTasksController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.Optional;
-
-@SmallTest
-public class FullscreenTaskListenerTest {
- private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
-
- @Mock
- private SyncTransactionQueue mSyncQueue;
- @Mock
- private FullscreenUnfoldController mUnfoldController;
- @Mock
- private RecentTasksController mRecentTasksController;
- @Mock
- private SurfaceControl mSurfaceControl;
-
- private Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
-
- private FullscreenTaskListener mListener;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- mFullscreenUnfoldController = Optional.of(mUnfoldController);
- mListener = new FullscreenTaskListener(mSyncQueue, mFullscreenUnfoldController,
- Optional.empty());
- }
-
- @Test
- public void testAnimatableTaskAppeared_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ true, /* taskId */ 0);
-
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- verify(mUnfoldController).onTaskAppeared(eq(info), any());
- }
-
- @Test
- public void testMultipleAnimatableTasksAppeared_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo animatable1 = createTaskInfo(/* visible */ true, /* taskId */ 0);
- RunningTaskInfo animatable2 = createTaskInfo(/* visible */ true, /* taskId */ 1);
-
- mListener.onTaskAppeared(animatable1, mSurfaceControl);
- mListener.onTaskAppeared(animatable2, mSurfaceControl);
-
- InOrder order = inOrder(mUnfoldController);
- order.verify(mUnfoldController).onTaskAppeared(eq(animatable1), any());
- order.verify(mUnfoldController).onTaskAppeared(eq(animatable2), any());
- }
-
- @Test
- public void testNonAnimatableTaskAppeared_doesNotNotifyUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
-
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- verifyNoMoreInteractions(mUnfoldController);
- }
-
- @Test
- public void testNonAnimatableTaskChanged_doesNotNotifyUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- mListener.onTaskInfoChanged(info);
-
- verifyNoMoreInteractions(mUnfoldController);
- }
-
- @Test
- public void testNonAnimatableTaskVanished_doesNotNotifyUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo info = createTaskInfo(/* visible */ false, /* taskId */ 0);
- mListener.onTaskAppeared(info, mSurfaceControl);
-
- mListener.onTaskVanished(info);
-
- verifyNoMoreInteractions(mUnfoldController);
- }
-
- @Test
- public void testAnimatableTaskBecameInactive_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo animatableTask = createTaskInfo(/* visible */ true, /* taskId */ 0);
- mListener.onTaskAppeared(animatableTask, mSurfaceControl);
- RunningTaskInfo notAnimatableTask = createTaskInfo(/* visible */ false, /* taskId */ 0);
-
- mListener.onTaskInfoChanged(notAnimatableTask);
-
- verify(mUnfoldController).onTaskVanished(eq(notAnimatableTask));
- }
-
- @Test
- public void testAnimatableTaskVanished_notifiesUnfoldController() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- RunningTaskInfo taskInfo = createTaskInfo(/* visible */ true, /* taskId */ 0);
- mListener.onTaskAppeared(taskInfo, mSurfaceControl);
-
- mListener.onTaskVanished(taskInfo);
-
- verify(mUnfoldController).onTaskVanished(eq(taskInfo));
- }
-
- private RunningTaskInfo createTaskInfo(boolean visible, int taskId) {
- final RunningTaskInfo info = spy(new RunningTaskInfo());
- info.isVisible = visible;
- info.positionInParent = new Point();
- when(info.getWindowingMode()).thenReturn(WindowConfiguration.WINDOWING_MODE_FULLSCREEN);
- final Configuration configuration = new Configuration();
- configuration.windowConfiguration.setActivityType(ACTIVITY_TYPE_STANDARD);
- when(info.getConfiguration()).thenReturn(configuration);
- info.taskId = taskId;
- return info;
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
index b976c1287aca..dcc504ad0cdb 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutControllerTest.java
@@ -16,10 +16,11 @@
package com.android.wm.shell.hidedisplaycutout;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
-import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -27,7 +28,8 @@ import android.testing.TestableLooper;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
-import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
import org.junit.Test;
@@ -35,25 +37,30 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class HideDisplayCutoutControllerTest {
+public class HideDisplayCutoutControllerTest extends ShellTestCase {
private TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
- private HideDisplayCutoutController mHideDisplayCutoutController;
@Mock
- private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
+ private ShellController mShellController;
@Mock
- private ShellExecutor mMockMainExecutor;
+ private HideDisplayCutoutOrganizer mMockDisplayAreaOrganizer;
+
+ private HideDisplayCutoutController mHideDisplayCutoutController;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mHideDisplayCutoutController = new HideDisplayCutoutController(
- mContext, mMockDisplayAreaOrganizer, mMockMainExecutor);
+ mContext, mShellController, mMockDisplayAreaOrganizer);
+ }
+
+ @Test
+ public void instantiateController_registerConfigChangeListener() {
+ verify(mShellController, times(1)).addConfigurationChangeListener(any());
}
@Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
index 16e92395c85e..49521cfbb34d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutoutOrganizerTest.java
@@ -32,7 +32,6 @@ import android.content.res.Configuration;
import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Binder;
-import android.platform.test.annotations.Presubmit;
import android.testing.AndroidTestingRunner;
import android.testing.TestableContext;
import android.testing.TestableLooper;
@@ -48,6 +47,7 @@ import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
@@ -61,11 +61,10 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-@Presubmit
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
-public class HideDisplayCutoutOrganizerTest {
+public class HideDisplayCutoutOrganizerTest extends ShellTestCase {
private TestableContext mContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
index 440a6f8fb59a..184a8dfecff9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java
@@ -44,6 +44,7 @@ import android.window.WindowContainerTransaction;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayInsetsController;
import com.android.wm.shell.common.ShellExecutor;
@@ -61,7 +62,7 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class KidsModeTaskOrganizerTest {
+public class KidsModeTaskOrganizerTest extends ShellTestCase {
@Mock private ITaskOrganizerController mTaskOrganizerController;
@Mock private Context mContext;
@Mock private Handler mHandler;
@@ -88,7 +89,7 @@ public class KidsModeTaskOrganizerTest {
// NOTE: KidsModeTaskOrganizer should have a null CompatUIController.
mOrganizer = spy(new KidsModeTaskOrganizer(mTaskOrganizerController, mTestExecutor,
mHandler, mContext, mSyncTransactionQueue, mDisplayController,
- mDisplayInsetsController, Optional.empty(), mObserver));
+ mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver));
mOrganizer.initialize(mStartingWindowController);
doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction();
doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index ecf1c5d41864..dbf93ae35c18 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -30,10 +30,10 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.om.IOverlayManager;
import android.graphics.Rect;
import android.os.Handler;
import android.os.UserHandle;
@@ -41,16 +41,15 @@ import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
import android.view.Surface;
-import android.view.SurfaceControl;
import android.window.WindowContainerTransaction;
import androidx.test.filters.SmallTest;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
import org.junit.Test;
@@ -71,6 +70,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
OneHandedState mSpiedTransitionState;
@Mock
+ ShellController mMockShellController;
+ @Mock
DisplayLayout mDisplayLayout;
@Mock
DisplayController mMockDisplayController;
@@ -87,16 +88,10 @@ public class OneHandedControllerTest extends OneHandedTestCase {
@Mock
OneHandedUiEventLogger mMockUiEventLogger;
@Mock
- InteractionJankMonitor mMockJankMonitor;
- @Mock
- IOverlayManager mMockOverlayManager;
- @Mock
TaskStackListenerImpl mMockTaskStackListener;
@Mock
ShellExecutor mMockShellMainExecutor;
@Mock
- SurfaceControl mMockLeash;
- @Mock
Handler mMockShellMainHandler;
final boolean mDefaultEnabled = true;
@@ -132,6 +127,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
mMockTouchHandler,
@@ -140,9 +136,7 @@ public class OneHandedControllerTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil,
mSpiedTimeoutHandler,
mSpiedTransitionState,
- mMockJankMonitor,
mMockUiEventLogger,
- mMockOverlayManager,
mMockTaskStackListener,
mMockShellMainExecutor,
mMockShellMainHandler)
@@ -150,6 +144,16 @@ public class OneHandedControllerTest extends OneHandedTestCase {
}
@Test
+ public void testControllerRegistersConfigChangeListener() {
+ verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
+ }
+
+ @Test
+ public void testControllerRegistersKeyguardChangeListener() {
+ verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
public void testDefaultShouldNotInOneHanded() {
// Assert default transition state is STATE_NONE
assertThat(mSpiedTransitionState.getState()).isEqualTo(STATE_NONE);
@@ -345,8 +349,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
false);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
@@ -358,8 +362,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
false);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
@@ -371,8 +375,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
true);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, never()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
@@ -384,8 +388,8 @@ public class OneHandedControllerTest extends OneHandedTestCase {
when(mMockSettingsUitl.getSettingsSwipeToNotificationEnabled(any(), anyInt())).thenReturn(
false);
final WindowContainerTransaction handlerWCT = new WindowContainerTransaction();
- mSpiedOneHandedController.onRotateDisplay(mDisplay.getDisplayId(), Surface.ROTATION_0,
- Surface.ROTATION_90, handlerWCT);
+ mSpiedOneHandedController.onDisplayChange(mDisplay.getDisplayId(), Surface.ROTATION_0,
+ Surface.ROTATION_90, null /* newDisplayAreaInfo */, handlerWCT);
verify(mMockDisplayAreaOrganizer, atLeastOnce()).onRotateDisplay(eq(mContext),
eq(Surface.ROTATION_90), any(WindowContainerTransaction.class));
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
index dba1b8b86261..e6a8220e081b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedStateTest.java
@@ -29,22 +29,19 @@ import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.content.om.IOverlayManager;
import android.graphics.Rect;
import android.os.Handler;
-import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
import android.util.ArrayMap;
import android.view.Display;
-import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
-import com.android.internal.jank.InteractionJankMonitor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.DisplayLayout;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
import org.junit.Test;
@@ -55,7 +52,6 @@ import org.mockito.MockitoAnnotations;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class OneHandedStateTest extends OneHandedTestCase {
- private int mCurrentUser = UserHandle.myUserId();
Display mDisplay;
DisplayLayout mDisplayLayout;
@@ -65,6 +61,8 @@ public class OneHandedStateTest extends OneHandedTestCase {
OneHandedState mSpiedState;
@Mock
+ ShellController mMockShellController;
+ @Mock
DisplayController mMockDisplayController;
@Mock
OneHandedDisplayAreaOrganizer mMockDisplayAreaOrganizer;
@@ -77,16 +75,10 @@ public class OneHandedStateTest extends OneHandedTestCase {
@Mock
OneHandedUiEventLogger mMockUiEventLogger;
@Mock
- InteractionJankMonitor mMockJankMonitor;
- @Mock
- IOverlayManager mMockOverlayManager;
- @Mock
TaskStackListenerImpl mMockTaskStackListener;
@Mock
ShellExecutor mMockShellMainExecutor;
@Mock
- SurfaceControl mMockLeash;
- @Mock
Handler mMockShellMainHandler;
final boolean mDefaultEnabled = true;
@@ -119,6 +111,7 @@ public class OneHandedStateTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil = new OneHandedAccessibilityUtil(mContext);
mSpiedOneHandedController = spy(new OneHandedController(
mContext,
+ mMockShellController,
mMockDisplayController,
mMockDisplayAreaOrganizer,
mMockTouchHandler,
@@ -127,9 +120,7 @@ public class OneHandedStateTest extends OneHandedTestCase {
mOneHandedAccessibilityUtil,
mSpiedTimeoutHandler,
mSpiedState,
- mMockJankMonitor,
mMockUiEventLogger,
- mMockOverlayManager,
mMockTaskStackListener,
mMockShellMainExecutor,
mMockShellMainHandler)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTestCase.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTestCase.java
index 8b03dc58c3bf..808ab2167dc7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTestCase.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedTestCase.java
@@ -30,6 +30,8 @@ import android.view.WindowManager;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.ShellTestCase;
+
import org.junit.Before;
import org.junit.Rule;
import org.mockito.Answers;
@@ -38,7 +40,7 @@ import org.mockito.Mock;
/**
* Base class that does One Handed specific setup.
*/
-public abstract class OneHandedTestCase {
+public abstract class OneHandedTestCase extends ShellTestCase {
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
protected Context mContext;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
index c685fdc1f09c..5880ffb0dce2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipAnimationControllerTest.java
@@ -21,6 +21,7 @@ import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_270;
import static android.view.Surface.ROTATION_90;
+import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_LEAVE_PIP;
import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
@@ -36,7 +37,9 @@ import android.testing.TestableLooper;
import android.view.SurfaceControl;
import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.MockSurfaceControlHelper;
import com.android.wm.shell.ShellTestCase;
import org.junit.Before;
@@ -60,19 +63,18 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Mock
private TaskInfo mTaskInfo;
-
@Mock
private PipAnimationController.PipAnimationCallback mPipAnimationCallback;
@Before
public void setUp() throws Exception {
- mPipAnimationController = new PipAnimationController(
- new PipSurfaceTransactionHelper());
+ MockitoAnnotations.initMocks(this);
+ mPipAnimationController = new PipAnimationController(new PipSurfaceTransactionHelper(
+ InstrumentationRegistry.getInstrumentation().getTargetContext()));
mLeash = new SurfaceControl.Builder()
.setContainerLayer()
.setName("FakeLeash")
.build();
- MockitoAnnotations.initMocks(this);
}
@Test
@@ -103,7 +105,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final PipAnimationController.PipTransitionAnimator oldAnimator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue1, null,
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
- oldAnimator.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new);
+ oldAnimator.setSurfaceControlTransactionFactory(
+ MockSurfaceControlHelper::createMockSurfaceControlTransaction);
oldAnimator.start();
final PipAnimationController.PipTransitionAnimator newAnimator = mPipAnimationController
@@ -133,7 +136,7 @@ public class PipAnimationControllerTest extends ShellTestCase {
@Test
public void pipTransitionAnimator_rotatedEndValue() {
- final PipDummySurfaceControlTx tx = new PipDummySurfaceControlTx();
+ final SurfaceControl.Transaction tx = createMockSurfaceControlTransaction();
final Rect startBounds = new Rect(200, 700, 400, 800);
final Rect endBounds = new Rect(0, 0, 500, 1000);
// Fullscreen to PiP.
@@ -183,7 +186,8 @@ public class PipAnimationControllerTest extends ShellTestCase {
final PipAnimationController.PipTransitionAnimator animator = mPipAnimationController
.getAnimator(mTaskInfo, mLeash, baseValue, startValue, endValue, null,
TRANSITION_DIRECTION_TO_PIP, 0, ROTATION_0);
- animator.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new);
+ animator.setSurfaceControlTransactionFactory(
+ MockSurfaceControlHelper::createMockSurfaceControlTransaction);
animator.setPipAnimationCallback(mPipAnimationCallback);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java
deleted file mode 100644
index ccf8f6e03844..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipDummySurfaceControlTx.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.pip;
-
-import android.graphics.Matrix;
-import android.view.SurfaceControl;
-
-/**
- * A dummy {@link SurfaceControl.Transaction} class for testing purpose and supports
- * method chaining.
- */
-public class PipDummySurfaceControlTx extends SurfaceControl.Transaction {
- @Override
- public SurfaceControl.Transaction setAlpha(SurfaceControl leash, float alpha) {
- return this;
- }
-
- @Override
- public SurfaceControl.Transaction setPosition(SurfaceControl leash, float x, float y) {
- return this;
- }
-
- @Override
- public SurfaceControl.Transaction setWindowCrop(SurfaceControl leash, int w, int h) {
- return this;
- }
-
- @Override
- public SurfaceControl.Transaction setCornerRadius(SurfaceControl leash, float radius) {
- return this;
- }
-
- @Override
- public SurfaceControl.Transaction setShadowRadius(SurfaceControl leash, float radius) {
- return this;
- }
-
- @Override
- public SurfaceControl.Transaction setMatrix(SurfaceControl leash, Matrix matrix,
- float[] float9) {
- return this;
- }
-
- @Override
- public SurfaceControl.Transaction setFrameTimelineVsync(long frameTimelineVsyncId) {
- return this;
- }
-
- @Override
- public void apply() {}
-}
-
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
index e8e6254697c2..857f578fd8ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/PipTaskOrganizerTest.java
@@ -42,8 +42,10 @@ import android.testing.TestableLooper;
import android.util.Rational;
import android.util.Size;
import android.view.DisplayInfo;
+import android.view.SurfaceControl;
import android.window.WindowContainerToken;
+import com.android.wm.shell.MockSurfaceControlHelper;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
@@ -149,7 +151,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
final Rational aspectRatio = new Rational(2, 1);
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(aspectRatio)), null /* leash */);
+ createPipParams(aspectRatio)), mock(SurfaceControl.class));
assertEquals(aspectRatio.floatValue(), mPipBoundsState.getAspectRatio(), 0.01f);
}
@@ -157,7 +159,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void onTaskAppeared_updatesLastPipComponentName() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1, createPipParams(null)),
- null /* leash */);
+ mock(SurfaceControl.class));
assertEquals(mComponent1, mPipBoundsState.getLastPipComponentName());
}
@@ -168,7 +170,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mSpiedPipTaskOrganizer.onTaskAppeared(
createTaskInfo(mComponent1, createPipParams(null), minSize),
- null /* leash */);
+ mock(SurfaceControl.class));
assertEquals(minSize, mPipBoundsState.getOverrideMinSize());
}
@@ -178,7 +180,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
final Rational startAspectRatio = new Rational(2, 1);
final Rational newAspectRatio = new Rational(1, 2);
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(startAspectRatio)), null /* leash */);
+ createPipParams(startAspectRatio)), mock(SurfaceControl.class));
// It is in entering transition, should defer onTaskInfoChanged callback in this case.
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1,
@@ -196,7 +198,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
final Rational startAspectRatio = new Rational(2, 1);
final Rational newAspectRatio = new Rational(1, 2);
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(startAspectRatio)), null /* leash */);
+ createPipParams(startAspectRatio)), mock(SurfaceControl.class));
mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent1,
@@ -209,7 +211,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void onTaskInfoChanged_inPip_updatesLastPipComponentName() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), null /* leash */);
+ createPipParams(null)), mock(SurfaceControl.class));
mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
mSpiedPipTaskOrganizer.onTaskInfoChanged(createTaskInfo(mComponent2,
@@ -221,7 +223,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void onTaskInfoChanged_inPip_updatesOverrideMinSize() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), null /* leash */);
+ createPipParams(null)), mock(SurfaceControl.class));
mSpiedPipTaskOrganizer.sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
final Size minSize = new Size(400, 320);
@@ -234,7 +236,7 @@ public class PipTaskOrganizerTest extends ShellTestCase {
@Test
public void onTaskVanished_clearsPipBounds() {
mSpiedPipTaskOrganizer.onTaskAppeared(createTaskInfo(mComponent1,
- createPipParams(null)), null /* leash */);
+ createPipParams(null)), mock(SurfaceControl.class));
mPipBoundsState.setBounds(new Rect(100, 100, 200, 150));
mSpiedPipTaskOrganizer.onTaskVanished(createTaskInfo(mComponent1, createPipParams(null)));
@@ -246,7 +248,8 @@ public class PipTaskOrganizerTest extends ShellTestCase {
mPipBoundsState.setDisplayLayout(new DisplayLayout(info,
mContext.getResources(), true, true));
mSpiedPipTaskOrganizer.setOneShotAnimationType(PipAnimationController.ANIM_TYPE_ALPHA);
- mSpiedPipTaskOrganizer.setSurfaceControlTransactionFactory(PipDummySurfaceControlTx::new);
+ mSpiedPipTaskOrganizer.setSurfaceControlTransactionFactory(
+ MockSurfaceControlHelper::createMockSurfaceControlTransaction);
doNothing().when(mSpiedPipTaskOrganizer).enterPipWithAlphaAnimation(any(), anyLong());
doNothing().when(mSpiedPipTaskOrganizer).scheduleAnimateResizePip(any(), anyInt(), any());
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index df18133adcfb..f192514c37ab 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -53,6 +54,8 @@ import com.android.wm.shell.pip.PipParamsChangedForwarder;
import com.android.wm.shell.pip.PipSnapAlgorithm;
import com.android.wm.shell.pip.PipTaskOrganizer;
import com.android.wm.shell.pip.PipTransitionController;
+import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
import org.junit.Test;
@@ -72,13 +75,16 @@ import java.util.Set;
public class PipControllerTest extends ShellTestCase {
private PipController mPipController;
+ @Mock private ShellController mMockShellController;
@Mock private DisplayController mMockDisplayController;
@Mock private PhonePipMenuController mMockPhonePipMenuController;
@Mock private PipAppOpsListener mMockPipAppOpsListener;
@Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm;
+ @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm;
@Mock private PipSnapAlgorithm mMockPipSnapAlgorithm;
@Mock private PipMediaController mMockPipMediaController;
@Mock private PipTaskOrganizer mMockPipTaskOrganizer;
+ @Mock private PipTransitionState mMockPipTransitionState;
@Mock private PipTransitionController mMockPipTransitionController;
@Mock private PipTouchHandler mMockPipTouchHandler;
@Mock private PipMotionHelper mMockPipMotionHelper;
@@ -99,11 +105,12 @@ public class PipControllerTest extends ShellTestCase {
((Runnable) invocation.getArgument(0)).run();
return null;
}).when(mMockExecutor).execute(any());
- mPipController = new PipController(mContext, mMockDisplayController,
+ mPipController = new PipController(mContext, mMockShellController, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipBoundsState, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
- mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockPipKeepClearAlgorithm,
+ mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
+ mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor);
when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm);
@@ -111,6 +118,16 @@ public class PipControllerTest extends ShellTestCase {
}
@Test
+ public void instantiatePipController_registerConfigChangeListener() {
+ verify(mMockShellController, times(1)).addConfigurationChangeListener(any());
+ }
+
+ @Test
+ public void instantiatePipController_registerKeyguardChangeListener() {
+ verify(mMockShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
public void instantiatePipController_registersPipTransitionCallback() {
verify(mMockPipTransitionController).registerPipTransitionCallback(any());
}
@@ -132,11 +149,12 @@ public class PipControllerTest extends ShellTestCase {
when(mockPackageManager.hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)).thenReturn(false);
when(spyContext.getPackageManager()).thenReturn(mockPackageManager);
- assertNull(PipController.create(spyContext, mMockDisplayController,
+ assertNull(PipController.create(spyContext, mMockShellController, mMockDisplayController,
mMockPipAppOpsListener, mMockPipBoundsAlgorithm,
- mMockPipBoundsState, mMockPipMediaController,
- mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler,
- mMockPipTransitionController, mMockWindowManagerShellWrapper,
+ mMockPipKeepClearAlgorithm,
+ mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController,
+ mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTransitionState,
+ mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper,
mMockTaskStackListener, mPipParamsChangedForwarder,
mMockOneHandedController, mMockExecutor));
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
new file mode 100644
index 000000000000..e0f7e35f8d02
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip.phone;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * Unit tests against {@link PipKeepClearAlgorithm}.
+ */
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class PipKeepClearAlgorithmTest extends ShellTestCase {
+
+ private PipKeepClearAlgorithm mPipKeepClearAlgorithm;
+ private static final Rect DISPLAY_BOUNDS = new Rect(0, 0, 1000, 1000);
+
+ @Before
+ public void setUp() throws Exception {
+ mPipKeepClearAlgorithm = new PipKeepClearAlgorithm();
+ }
+
+ @Test
+ public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+ Set.of(), DISPLAY_BOUNDS);
+
+ assertFalse(outBounds.contains(keepClearRect));
+ }
+
+ @Test
+ public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect),
+ Set.of(), DISPLAY_BOUNDS);
+
+ assertEquals(inBounds, outBounds);
+ }
+
+ @Test
+ public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ // TODO(b/183746978): update this test to accommodate for the updated algorithm
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(50, 50, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ Set.of(keepClearRect), DISPLAY_BOUNDS);
+
+ assertEquals(inBounds, outBounds);
+ }
+
+ @Test
+ public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() {
+ final Rect inBounds = new Rect(0, 0, 100, 100);
+ final Rect keepClearRect = new Rect(100, 100, 150, 150);
+
+ final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(),
+ Set.of(keepClearRect), DISPLAY_BOUNDS);
+
+ assertEquals(inBounds, outBounds);
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index 50f6bd7b4927..2b4d1a6390c9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -47,7 +47,7 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.util.GroupedRecentTaskInfo;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.util.SplitBounds;
import org.junit.Before;
import org.junit.Test;
@@ -80,7 +80,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
mRecentTasksController = spy(new RecentTasksController(mContext, mTaskStackListener,
mMainExecutor));
mShellTaskOrganizer = new ShellTaskOrganizer(mMainExecutor, mContext,
- null /* sizeCompatUI */, Optional.of(mRecentTasksController));
+ null /* sizeCompatUI */, Optional.empty(), Optional.of(mRecentTasksController));
}
@Test
@@ -89,7 +89,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
ActivityManager.RecentTaskInfo t2 = makeTaskInfo(2);
setRawList(t1, t2);
- mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(StagedSplitBounds.class));
+ mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, mock(SplitBounds.class));
verify(mRecentTasksController).notifyRecentTasksChanged();
reset(mRecentTasksController);
@@ -104,10 +104,10 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2);
// Verify only one update if the split info is the same
- StagedSplitBounds bounds1 = new StagedSplitBounds(new Rect(0, 0, 50, 50),
+ SplitBounds bounds1 = new SplitBounds(new Rect(0, 0, 50, 50),
new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds1);
- StagedSplitBounds bounds2 = new StagedSplitBounds(new Rect(0, 0, 50, 50),
+ SplitBounds bounds2 = new SplitBounds(new Rect(0, 0, 50, 50),
new Rect(50, 50, 100, 100), t1.taskId, t2.taskId);
mRecentTasksController.addSplitPair(t1.taskId, t2.taskId, bounds2);
verify(mRecentTasksController, times(1)).notifyRecentTasksChanged();
@@ -139,8 +139,8 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3, t4, t5, t6);
// Mark a couple pairs [t2, t4], [t3, t5]
- StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 4);
- StagedSplitBounds pair2Bounds = new StagedSplitBounds(new Rect(), new Rect(), 3, 5);
+ SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 4);
+ SplitBounds pair2Bounds = new SplitBounds(new Rect(), new Rect(), 3, 5);
mRecentTasksController.addSplitPair(t2.taskId, t4.taskId, pair1Bounds);
mRecentTasksController.addSplitPair(t3.taskId, t5.taskId, pair2Bounds);
@@ -162,7 +162,7 @@ public class RecentTasksControllerTest extends ShellTestCase {
setRawList(t1, t2, t3);
// Add a pair
- StagedSplitBounds pair1Bounds = new StagedSplitBounds(new Rect(), new Rect(), 2, 3);
+ SplitBounds pair1Bounds = new SplitBounds(new Rect(), new Rect(), 2, 3);
mRecentTasksController.addSplitPair(t2.taskId, t3.taskId, pair1Bounds);
reset(mRecentTasksController);
@@ -245,15 +245,15 @@ public class RecentTasksControllerTest extends ShellTestCase {
: -1;
if (pair.mTaskInfo2 != null) {
- assertNotNull(pair.mStagedSplitBounds);
- int leftTopTaskId = pair.mStagedSplitBounds.leftTopTaskId;
- int bottomRightTaskId = pair.mStagedSplitBounds.rightBottomTaskId;
+ assertNotNull(pair.mSplitBounds);
+ int leftTopTaskId = pair.mSplitBounds.leftTopTaskId;
+ int bottomRightTaskId = pair.mSplitBounds.rightBottomTaskId;
// Unclear if pairs are ordered by split position, most likely not.
assertTrue(leftTopTaskId == taskId1 || leftTopTaskId == pair.mTaskInfo2.taskId);
assertTrue(bottomRightTaskId == taskId1
|| bottomRightTaskId == pair.mTaskInfo2.taskId);
} else {
- assertNull(pair.mStagedSplitBounds);
+ assertNull(pair.mSplitBounds);
}
}
assertTrue("Expected: " + Arrays.toString(expectedTaskIds)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index ad73c56950bd..50d02ae0dccd 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/StagedSplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -9,7 +9,8 @@ import android.graphics.Rect;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.wm.shell.util.StagedSplitBounds;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.util.SplitBounds;
import org.junit.Before;
import org.junit.Test;
@@ -17,7 +18,7 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
-public class StagedSplitBoundsTest {
+public class SplitBoundsTest extends ShellTestCase {
private static final int DEVICE_WIDTH = 100;
private static final int DEVICE_LENGTH = 200;
private static final int DIVIDER_SIZE = 20;
@@ -42,21 +43,21 @@ public class StagedSplitBoundsTest {
@Test
public void testVerticalStacked() {
- StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+ SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
TASK_ID_1, TASK_ID_2);
assertTrue(ssb.appsStackedVertically);
}
@Test
public void testHorizontalStacked() {
- StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+ SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
TASK_ID_1, TASK_ID_2);
assertFalse(ssb.appsStackedVertically);
}
@Test
public void testHorizontalDividerBounds() {
- StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+ SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
TASK_ID_1, TASK_ID_2);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(0, dividerBounds.left);
@@ -67,7 +68,7 @@ public class StagedSplitBoundsTest {
@Test
public void testVerticalDividerBounds() {
- StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+ SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
TASK_ID_1, TASK_ID_2);
Rect dividerBounds = ssb.visualDividerBounds;
assertEquals(DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2, dividerBounds.left);
@@ -78,7 +79,7 @@ public class StagedSplitBoundsTest {
@Test
public void testEqualVerticalTaskPercent() {
- StagedSplitBounds ssb = new StagedSplitBounds(mTopRect, mBottomRect,
+ SplitBounds ssb = new SplitBounds(mTopRect, mBottomRect,
TASK_ID_1, TASK_ID_2);
float topPercentSpaceTaken = (float) (DEVICE_LENGTH / 2 - DIVIDER_SIZE / 2) / DEVICE_LENGTH;
assertEquals(topPercentSpaceTaken, ssb.topTaskPercent, 0.01);
@@ -86,7 +87,7 @@ public class StagedSplitBoundsTest {
@Test
public void testEqualHorizontalTaskPercent() {
- StagedSplitBounds ssb = new StagedSplitBounds(mLeftRect, mRightRect,
+ SplitBounds ssb = new SplitBounds(mLeftRect, mRightRect,
TASK_ID_1, TASK_ID_2);
float leftPercentSpaceTaken = (float) (DEVICE_WIDTH / 2 - DIVIDER_SIZE / 2) / DEVICE_WIDTH;
assertEquals(leftPercentSpaceTaken, ssb.leftTaskPercent, 0.01);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
index 0639ad5d0a62..68cb57c14d8c 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/MainStageTests.java
@@ -61,7 +61,7 @@ public class MainStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
- mSyncQueue, mSurfaceSession, mIconProvider, null);
+ mSyncQueue, mSurfaceSession, mIconProvider);
mMainStage.onTaskAppeared(mRootTaskInfo, mRootLeash);
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
index a31aa58bdc26..3b42a48b5a40 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SideStageTests.java
@@ -66,7 +66,7 @@ public class SideStageTests extends ShellTestCase {
MockitoAnnotations.initMocks(this);
mRootTask = new TestRunningTaskInfoBuilder().build();
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mCallbacks,
- mSyncQueue, mSurfaceSession, mIconProvider, null);
+ mSyncQueue, mSurfaceSession, mIconProvider);
mSideStage.onTaskAppeared(mRootTask, mRootLeash);
}
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
new file mode 100644
index 000000000000..c7a261f32e43
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.splitscreen;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+
+import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+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.ShellExecutor;
+import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.transition.Transitions;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Optional;
+
+/**
+ * Tests for {@link SplitScreenController}
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SplitScreenControllerTests extends ShellTestCase {
+
+ @Mock ShellController mShellController;
+ @Mock ShellTaskOrganizer mTaskOrganizer;
+ @Mock SyncTransactionQueue mSyncQueue;
+ @Mock RootTaskDisplayAreaOrganizer mRootTDAOrganizer;
+ @Mock ShellExecutor mMainExecutor;
+ @Mock DisplayController mDisplayController;
+ @Mock DisplayImeController mDisplayImeController;
+ @Mock DisplayInsetsController mDisplayInsetsController;
+ @Mock Transitions mTransitions;
+ @Mock TransactionPool mTransactionPool;
+ @Mock IconProvider mIconProvider;
+ @Mock Optional<RecentTasksController> mRecentTasks;
+
+ private SplitScreenController mSplitScreenController;
+
+ @Before
+ public void setup() {
+ MockitoAnnotations.initMocks(this);
+ mSplitScreenController = spy(new SplitScreenController(mShellController, mTaskOrganizer,
+ mSyncQueue, mContext, mRootTDAOrganizer, mMainExecutor, mDisplayController,
+ mDisplayImeController, mDisplayInsetsController, mTransitions, mTransactionPool,
+ mIconProvider, mRecentTasks));
+ }
+
+ @Test
+ public void testControllerRegistersKeyguardChangeListener() {
+ when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
+ mSplitScreenController.onOrganizerRegistered();
+ verify(mShellController, times(1)).addKeyguardChangeListener(any());
+ }
+
+ @Test
+ public void testIsLaunchingAdjacently_notInSplitScreen() {
+ doReturn(false).when(mSplitScreenController).isSplitScreenVisible();
+ doReturn(true).when(mSplitScreenController).isValidToEnterSplitScreen(any());
+
+ // Verify launching the same activity returns true.
+ Intent startIntent = createStartIntent("startActivity");
+ ActivityManager.RunningTaskInfo focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
+ assertTrue(mSplitScreenController.isLaunchingAdjacently(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+
+ // Verify launching different activity returns false.
+ Intent diffIntent = createStartIntent("diffActivity");
+ focusTaskInfo =
+ createTaskInfo(WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, diffIntent);
+ doReturn(focusTaskInfo).when(mSplitScreenController).getFocusingTaskInfo();
+ assertFalse(mSplitScreenController.isLaunchingAdjacently(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ }
+
+ @Test
+ public void testIsLaunchingAdjacently_inSplitScreen() {
+ doReturn(true).when(mSplitScreenController).isSplitScreenVisible();
+
+ // Verify launching the same activity returns true.
+ Intent startIntent = createStartIntent("startActivity");
+ ActivityManager.RunningTaskInfo pairingTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, startIntent);
+ doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt());
+ assertTrue(mSplitScreenController.isLaunchingAdjacently(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+
+ // Verify launching different activity returns false.
+ Intent diffIntent = createStartIntent("diffActivity");
+ pairingTaskInfo =
+ createTaskInfo(WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD, diffIntent);
+ doReturn(pairingTaskInfo).when(mSplitScreenController).getTaskInfo(anyInt());
+ assertFalse(mSplitScreenController.isLaunchingAdjacently(
+ startIntent, SPLIT_POSITION_TOP_OR_LEFT));
+ }
+
+ private Intent createStartIntent(String activityName) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(mContext, activityName));
+ return intent;
+ }
+
+ private ActivityManager.RunningTaskInfo createTaskInfo(int winMode, int actType,
+ Intent strIntent) {
+ ActivityManager.RunningTaskInfo info = new ActivityManager.RunningTaskInfo();
+ info.configuration.windowConfiguration.setActivityType(actType);
+ info.configuration.windowConfiguration.setWindowingMode(winMode);
+ info.supportsMultiWindow = true;
+ info.baseIntent = strIntent;
+ info.baseActivity = strIntent.getComponent();
+ ActivityInfo activityInfo = new ActivityInfo();
+ activityInfo.packageName = info.baseActivity.getPackageName();
+ activityInfo.name = info.baseActivity.getClassName();
+ info.topActivityInfo = activityInfo;
+ return info;
+ }
+}
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 eb9d3a11d285..a67853cfe745 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
@@ -40,8 +40,6 @@ import com.android.wm.shell.transition.Transitions;
import java.util.Optional;
-import javax.inject.Provider;
-
public class SplitTestUtils {
static SplitLayout createMockSplitLayout() {
@@ -74,12 +72,10 @@ public class SplitTestUtils {
DisplayInsetsController insetsController, SplitLayout splitLayout,
Transitions transitions, TransactionPool transactionPool,
SplitscreenEventLogger logger, ShellExecutor mainExecutor,
- Optional<RecentTasksController> recentTasks,
- Provider<Optional<StageTaskUnfoldController>> unfoldController) {
+ Optional<RecentTasksController> recentTasks) {
super(context, displayId, syncQueue, taskOrganizer, mainStage,
sideStage, displayController, imeController, insetsController, splitLayout,
- transitions, transactionPool, logger, mainExecutor, recentTasks,
- unfoldController);
+ transitions, transactionPool, logger, mainExecutor, recentTasks);
// 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 ffaab652aa99..1d038f4ee377 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
@@ -118,16 +118,16 @@ public class SplitTransitionTests extends ShellTestCase {
mSplitLayout = SplitTestUtils.createMockSplitLayout();
mMainStage = new MainStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider, null);
+ mIconProvider);
mMainStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mSideStage = new SideStage(mContext, mTaskOrganizer, DEFAULT_DISPLAY, mock(
StageTaskListener.StageListenerCallbacks.class), mSyncQueue, mSurfaceSession,
- mIconProvider, null);
+ mIconProvider);
mSideStage.onTaskAppeared(new TestRunningTaskInfoBuilder().build(), createMockSurface());
mStageCoordinator = new SplitTestUtils.TestStageCoordinator(mContext, DEFAULT_DISPLAY,
mSyncQueue, mTaskOrganizer, mMainStage, mSideStage, mDisplayController,
mDisplayImeController, mDisplayInsetsController, mSplitLayout, mTransitions,
- mTransactionPool, mLogger, mMainExecutor, Optional.empty(), Optional::empty);
+ mTransactionPool, mLogger, mMainExecutor, Optional.empty());
mSplitScreenTransitions = mStageCoordinator.getSplitTransitions();
doAnswer((Answer<IBinder>) invocation -> mock(IBinder.class))
.when(mTransitions).startTransition(anyInt(), any(), any());
@@ -182,7 +182,7 @@ public class SplitTransitionTests extends ShellTestCase {
IBinder transition = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(testRemote), mStageCoordinator);
+ new RemoteTransition(testRemote), mStageCoordinator, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -340,7 +340,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
info.addChange(mainChange);
info.addChange(sideChange);
- IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+ IBinder transition = mSplitScreenTransitions.startDismissTransition(
new WindowContainerTransaction(), mStageCoordinator,
EXIT_REASON_APP_DOES_NOT_SUPPORT_MULTIWINDOW, STAGE_TYPE_SIDE);
boolean accepted = mStageCoordinator.startAnimation(transition, info,
@@ -363,7 +363,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo info = new TransitionInfo(TRANSIT_TO_BACK, 0);
info.addChange(mainChange);
info.addChange(sideChange);
- IBinder transition = mSplitScreenTransitions.startDismissTransition(null,
+ IBinder transition = mSplitScreenTransitions.startDismissTransition(
new WindowContainerTransaction(), mStageCoordinator, EXIT_REASON_DRAG_DIVIDER,
STAGE_TYPE_SIDE);
mMainStage.onTaskVanished(mMainChild);
@@ -422,7 +422,7 @@ public class SplitTransitionTests extends ShellTestCase {
TransitionInfo enterInfo = createEnterPairInfo();
IBinder enterTransit = mSplitScreenTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_PAIR_OPEN, new WindowContainerTransaction(),
- new RemoteTransition(new TestRemoteTransition()), mStageCoordinator);
+ new RemoteTransition(new TestRemoteTransition()), mStageCoordinator, null);
mMainStage.onTaskAppeared(mMainChild, createMockSurface());
mSideStage.onTaskAppeared(mSideChild, createMockSurface());
mStageCoordinator.startAnimation(enterTransit, enterInfo,
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 42d998f6b0ee..4b68870d4129 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
@@ -34,6 +34,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -58,6 +59,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TransactionPool;
import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
@@ -68,8 +70,6 @@ import org.mockito.MockitoAnnotations;
import java.util.Optional;
-import javax.inject.Provider;
-
/**
* Tests for {@link StageCoordinator}
*/
@@ -85,10 +85,6 @@ public class StageCoordinatorTests extends ShellTestCase {
@Mock
private SideStage mSideStage;
@Mock
- private StageTaskUnfoldController mMainUnfoldController;
- @Mock
- private StageTaskUnfoldController mSideUnfoldController;
- @Mock
private SplitLayout mSplitLayout;
@Mock
private DisplayController mDisplayController;
@@ -107,6 +103,7 @@ public class StageCoordinatorTests extends ShellTestCase {
private final Rect mBounds1 = new Rect(10, 20, 30, 40);
private final Rect mBounds2 = new Rect(5, 10, 15, 20);
+ private final Rect mRootBounds = new Rect(0, 0, 45, 60);
private SurfaceSession mSurfaceSession = new SurfaceSession();
private SurfaceControl mRootLeash;
@@ -119,16 +116,20 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
mTaskOrganizer, mMainStage, mSideStage, mDisplayController, mDisplayImeController,
mDisplayInsetsController, mSplitLayout, mTransitions, mTransactionPool, mLogger,
- mMainExecutor, Optional.empty(), new UnfoldControllerProvider()));
+ mMainExecutor, Optional.empty()));
doNothing().when(mStageCoordinator).updateActivityOptions(any(), anyInt());
when(mSplitLayout.getBounds1()).thenReturn(mBounds1);
when(mSplitLayout.getBounds2()).thenReturn(mBounds2);
+ when(mSplitLayout.getRootBounds()).thenReturn(mRootBounds);
when(mSplitLayout.isLandscape()).thenReturn(false);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootLeash = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
mStageCoordinator.onTaskAppeared(mRootTask, mRootLeash);
+
+ mSideStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
+ mMainStage.mRootTaskInfo = new TestRunningTaskInfoBuilder().build();
}
@Test
@@ -168,13 +169,6 @@ public class StageCoordinatorTests extends ShellTestCase {
}
@Test
- public void testRootTaskAppeared_initializesUnfoldControllers() {
- verify(mMainUnfoldController).init();
- verify(mSideUnfoldController).init();
- verify(mStageCoordinator).onRootTaskAppeared();
- }
-
- @Test
public void testRootTaskInfoChanged_updatesSplitLayout() {
mStageCoordinator.onTaskInfoChanged(mRootTask);
@@ -184,26 +178,25 @@ public class StageCoordinatorTests extends ShellTestCase {
@Test
public void testLayoutChanged_topLeftSplitPosition_updatesUnfoldStageBounds() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_TOP_OR_LEFT, null);
- clearInvocations(mMainUnfoldController, mSideUnfoldController);
+ final SplitScreenListener listener = mock(SplitScreenListener.class);
+ mStageCoordinator.registerSplitScreenListener(listener);
+ clearInvocations(listener);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- false);
- verify(mSideUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT, false);
+ verify(listener).onSplitBoundsChanged(mRootBounds, mBounds2, mBounds1);
}
@Test
public void testLayoutChanged_bottomRightSplitPosition_updatesUnfoldStageBounds() {
mStageCoordinator.setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, null);
- clearInvocations(mMainUnfoldController, mSideUnfoldController);
+ final SplitScreenListener listener = mock(SplitScreenListener.class);
+ mStageCoordinator.registerSplitScreenListener(listener);
+ clearInvocations(listener);
mStageCoordinator.onLayoutSizeChanged(mSplitLayout);
- verify(mMainUnfoldController).onLayoutChanged(mBounds1, SPLIT_POSITION_TOP_OR_LEFT,
- false);
- verify(mSideUnfoldController).onLayoutChanged(mBounds2, SPLIT_POSITION_BOTTOM_OR_RIGHT,
- false);
+ verify(listener).onSplitBoundsChanged(mRootBounds, mBounds1, mBounds2);
}
@Test
@@ -234,8 +227,8 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME);
verify(mMainStage).reorderChild(eq(testTaskId), eq(true),
any(WindowContainerTransaction.class));
- verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(false));
- verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(true));
+ verify(mSideStage).dismiss(any(WindowContainerTransaction.class), eq(false));
+ verify(mMainStage).resetBounds(any(WindowContainerTransaction.class));
}
@Test
@@ -247,8 +240,8 @@ public class StageCoordinatorTests extends ShellTestCase {
mStageCoordinator.exitSplitScreen(testTaskId, EXIT_REASON_RETURN_HOME);
verify(mSideStage).reorderChild(eq(testTaskId), eq(true),
any(WindowContainerTransaction.class));
- verify(mSideStage).removeAllTasks(any(WindowContainerTransaction.class), eq(true));
- verify(mMainStage).deactivate(any(WindowContainerTransaction.class), eq(false));
+ verify(mSideStage).resetBounds(any(WindowContainerTransaction.class));
+ verify(mMainStage).dismiss(any(WindowContainerTransaction.class), eq(false));
}
@Test
@@ -314,20 +307,4 @@ public class StageCoordinatorTests extends ShellTestCase {
verify(mSplitLayout).applySurfaceChanges(any(), any(), any(), any(), any(), eq(false));
}
-
- private class UnfoldControllerProvider implements
- Provider<Optional<StageTaskUnfoldController>> {
-
- private boolean isMain = true;
-
- @Override
- public Optional<StageTaskUnfoldController> get() {
- if (isMain) {
- isMain = false;
- return Optional.of(mMainUnfoldController);
- } else {
- return Optional.of(mSideUnfoldController);
- }
- }
- }
}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
index 157c30bcb6c7..5ee8bf3006a3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageTaskListenerTests.java
@@ -25,7 +25,6 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -72,8 +71,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
private SyncTransactionQueue mSyncQueue;
@Mock
private IconProvider mIconProvider;
- @Mock
- private StageTaskUnfoldController mStageTaskUnfoldController;
@Captor
private ArgumentCaptor<SyncTransactionQueue.TransactionRunnable> mRunnableCaptor;
private SurfaceSession mSurfaceSession = new SurfaceSession();
@@ -92,8 +89,7 @@ public final class StageTaskListenerTests extends ShellTestCase {
mCallbacks,
mSyncQueue,
mSurfaceSession,
- mIconProvider,
- mStageTaskUnfoldController);
+ mIconProvider);
mRootTask = new TestRunningTaskInfoBuilder().build();
mRootTask.parentTaskId = INVALID_TASK_ID;
mSurfaceControl = new SurfaceControl.Builder(mSurfaceSession).setName("test").build();
@@ -130,30 +126,6 @@ public final class StageTaskListenerTests extends ShellTestCase {
verify(mCallbacks).onStatusChanged(eq(mRootTask.isVisible), eq(true));
}
- @Test
- public void testTaskAppeared_notifiesUnfoldListener() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- final ActivityManager.RunningTaskInfo task =
- new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
-
- mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
-
- verify(mStageTaskUnfoldController).onTaskAppeared(eq(task), eq(mSurfaceControl));
- }
-
- @Test
- public void testTaskVanished_notifiesUnfoldListener() {
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
- final ActivityManager.RunningTaskInfo task =
- new TestRunningTaskInfoBuilder().setParentTaskId(mRootTask.taskId).build();
- mStageTaskListener.onTaskAppeared(task, mSurfaceControl);
- clearInvocations(mStageTaskUnfoldController);
-
- mStageTaskListener.onTaskVanished(task);
-
- verify(mStageTaskUnfoldController).onTaskVanished(eq(task));
- }
-
@Test(expected = IllegalArgumentException.class)
public void testUnknownTaskVanished() {
final ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder().build();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index 630d0d2c827c..46b040fd4325 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -73,6 +73,7 @@ import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.common.HandlerExecutor;
import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.TransactionPool;
@@ -91,7 +92,7 @@ import java.util.function.IntSupplier;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class StartingSurfaceDrawerTests {
+public class StartingSurfaceDrawerTests extends ShellTestCase {
@Mock
private IBinder mBinder;
@Mock
@@ -249,7 +250,8 @@ public class StartingSurfaceDrawerTests {
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
any() /* requestedVisibility */, any() /* outInputChannel */,
- any() /* outInsetsState */, any() /* outActiveControls */);
+ any() /* outInsetsState */, any() /* outActiveControls */,
+ any() /* outAttachedFrame */);
TaskSnapshotWindow mockSnapshotWindow = TaskSnapshotWindow.create(windowInfo,
mBinder,
snapshot, mTestExecutor, () -> {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
index 78e27c956807..3de50bb60470 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/TaskSnapshotWindowTest.java
@@ -47,6 +47,7 @@ import android.window.TaskSnapshot;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import org.junit.Test;
@@ -58,7 +59,7 @@ import org.junit.runner.RunWith;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class TaskSnapshotWindowTest {
+public class TaskSnapshotWindowTest extends ShellTestCase {
private TaskSnapshotWindow mWindow;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
new file mode 100644
index 000000000000..1c0e46f7264e
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/sysui/ShellControllerTest.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.sysui;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Configuration;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.common.ShellExecutor;
+
+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 java.util.Locale;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class ShellControllerTest extends ShellTestCase {
+
+ @Mock
+ private ShellExecutor mExecutor;
+
+ private ShellController mController;
+ private TestConfigurationChangeListener mConfigChangeListener;
+ private TestKeyguardChangeListener mKeyguardChangeListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mKeyguardChangeListener = new TestKeyguardChangeListener();
+ mConfigChangeListener = new TestConfigurationChangeListener();
+ mController = new ShellController(mExecutor);
+ mController.onConfigurationChanged(getConfigurationCopy());
+ }
+
+ @After
+ public void tearDown() {
+ // Do nothing
+ }
+
+ @Test
+ public void testAddKeyguardChangeListener_ensureCallback() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, false, false);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testDoubleAddKeyguardChangeListener_ensureSingleCallback() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, false, false);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testAddRemoveKeyguardChangeListener_ensureNoCallback() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+ mController.removeKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, false, false);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testKeyguardVisibilityChanged() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardVisibilityChanged(true, true, true);
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 1);
+ assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+ assertTrue(mKeyguardChangeListener.lastOccluded);
+ assertTrue(mKeyguardChangeListener.lastAnimatingDismiss);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 0);
+ }
+
+ @Test
+ public void testKeyguardDismissAnimationFinished() {
+ mController.addKeyguardChangeListener(mKeyguardChangeListener);
+
+ mController.onKeyguardDismissAnimationFinished();
+ assertTrue(mKeyguardChangeListener.visibilityChanged == 0);
+ assertTrue(mKeyguardChangeListener.dismissAnimationFinished == 1);
+ }
+
+ @Test
+ public void testAddConfigurationChangeListener_ensureCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.densityDpi = 200;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ }
+
+ @Test
+ public void testDoubleAddConfigurationChangeListener_ensureSingleCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.densityDpi = 200;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ }
+
+ @Test
+ public void testAddRemoveConfigurationChangeListener_ensureNoCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+ mController.removeConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.densityDpi = 200;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 0);
+ }
+
+ @Test
+ public void testMultipleConfigurationChangeListeners() {
+ TestConfigurationChangeListener listener2 = new TestConfigurationChangeListener();
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+ mController.addConfigurationChangeListener(listener2);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.densityDpi = 200;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(listener2.configChanges == 1);
+ }
+
+ @Test
+ public void testRemoveListenerDuringCallback() {
+ TestConfigurationChangeListener badListener = new TestConfigurationChangeListener() {
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ mController.removeConfigurationChangeListener(this);
+ }
+ };
+ mController.addConfigurationChangeListener(badListener);
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ // Ensure we don't fail just because a listener was removed mid-callback
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.densityDpi = 200;
+ mController.onConfigurationChanged(newConfig);
+ }
+
+ @Test
+ public void testDensityChangeCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.densityDpi = 200;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 1);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
+ }
+
+ @Test
+ public void testFontScaleChangeCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.fontScale = 2;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 1);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
+ }
+
+ @Test
+ public void testSmallestWidthChangeCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.smallestScreenWidthDp = 100;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 1);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
+ }
+
+ @Test
+ public void testThemeChangeCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.assetsSeq++;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 1);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
+ }
+
+ @Test
+ public void testNightModeChangeCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ newConfig.uiMode = Configuration.UI_MODE_NIGHT_YES;
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 1);
+ assertTrue(mConfigChangeListener.localeChanges == 0);
+ }
+
+ @Test
+ public void testLocaleChangeCallback() {
+ mController.addConfigurationChangeListener(mConfigChangeListener);
+
+ Configuration newConfig = getConfigurationCopy();
+ // Just change the locales to be different
+ if (newConfig.locale == Locale.CANADA) {
+ newConfig.locale = Locale.US;
+ } else {
+ newConfig.locale = Locale.CANADA;
+ }
+ mController.onConfigurationChanged(newConfig);
+ assertTrue(mConfigChangeListener.configChanges == 1);
+ assertTrue(mConfigChangeListener.densityChanges == 0);
+ assertTrue(mConfigChangeListener.smallestWidthChanges == 0);
+ assertTrue(mConfigChangeListener.themeChanges == 0);
+ assertTrue(mConfigChangeListener.localeChanges == 1);
+ }
+
+ private Configuration getConfigurationCopy() {
+ final Configuration c = new Configuration(InstrumentationRegistry.getInstrumentation()
+ .getTargetContext().getResources().getConfiguration());
+ // In tests this might be undefined so make sure it's valid
+ c.assetsSeq = 1;
+ return c;
+ }
+
+ private class TestConfigurationChangeListener implements ConfigurationChangeListener {
+ // Counts of number of times each of the callbacks are called
+ public int configChanges;
+ public int densityChanges;
+ public int smallestWidthChanges;
+ public int themeChanges;
+ public int localeChanges;
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfiguration) {
+ configChanges++;
+ }
+
+ @Override
+ public void onDensityOrFontScaleChanged() {
+ densityChanges++;
+ }
+
+ @Override
+ public void onSmallestScreenWidthChanged() {
+ smallestWidthChanges++;
+ }
+
+ @Override
+ public void onThemeChanged() {
+ themeChanges++;
+ }
+
+ @Override
+ public void onLocaleOrLayoutDirectionChanged() {
+ localeChanges++;
+ }
+ }
+
+ private class TestKeyguardChangeListener implements KeyguardChangeListener {
+ // Counts of number of times each of the callbacks are called
+ public int visibilityChanged;
+ public boolean lastVisibility;
+ public boolean lastOccluded;
+ public boolean lastAnimatingDismiss;
+ public int dismissAnimationFinished;
+
+ @Override
+ public void onKeyguardVisibilityChanged(boolean visible, boolean occluded,
+ boolean animatingDismiss) {
+ lastVisibility = visible;
+ lastOccluded = occluded;
+ lastAnimatingDismiss = animatingDismiss;
+ visibilityChanged++;
+ }
+
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ dismissAnimationFinished++;
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
deleted file mode 100644
index d6142753b48a..000000000000
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/tasksurfacehelper/TaskSurfaceHelperControllerTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.wm.shell.tasksurfacehelper;
-
-import static org.mockito.Mockito.verify;
-
-import android.platform.test.annotations.Presubmit;
-import android.testing.AndroidTestingRunner;
-import android.view.SurfaceControl;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.common.ShellExecutor;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@Presubmit
-@RunWith(AndroidTestingRunner.class)
-@SmallTest
-public class TaskSurfaceHelperControllerTest {
- private TaskSurfaceHelperController mTaskSurfaceHelperController;
- @Mock
- private ShellTaskOrganizer mMockTaskOrganizer;
- @Mock
- private ShellExecutor mMockShellExecutor;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mTaskSurfaceHelperController = new TaskSurfaceHelperController(
- mMockTaskOrganizer, mMockShellExecutor);
- }
-
- @Test
- public void testSetGameModeForTask() {
- mTaskSurfaceHelperController.setGameModeForTask(/*taskId*/1, /*gameMode*/3);
- verify(mMockTaskOrganizer).setSurfaceMetadata(1, SurfaceControl.METADATA_GAME_MODE, 3);
- }
-}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index a0b12976b467..e2f2b71cea04 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -79,6 +79,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SmallTest;
import androidx.test.platform.app.InstrumentationRegistry;
+import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.common.DisplayController;
import com.android.wm.shell.common.ShellExecutor;
@@ -98,7 +99,7 @@ import java.util.ArrayList;
*/
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class ShellTransitionTests {
+public class ShellTransitionTests extends ShellTestCase {
private final WindowOrganizer mOrganizer = mock(WindowOrganizer.class);
private final TransactionPool mTransactionPool = mock(TransactionPool.class);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
new file mode 100644
index 000000000000..46de60772766
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.unfold;
+
+import static com.android.wm.shell.unfold.UnfoldAnimationControllerTest.TestUnfoldTaskAnimator.UNSET;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManager.RunningTaskInfo;
+import android.app.TaskInfo;
+import android.testing.AndroidTestingRunner;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
+
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Predicate;
+
+/**
+ * Tests for {@link UnfoldAnimationController}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:UnfoldAnimationControllerTest
+ */
+@RunWith(AndroidTestingRunner.class)
+public class UnfoldAnimationControllerTest extends ShellTestCase {
+
+ @Mock
+ private TransactionPool mTransactionPool;
+ @Mock
+ private UnfoldTransitionHandler mUnfoldTransitionHandler;
+ @Mock
+ private SurfaceControl mLeash;
+
+ private UnfoldAnimationController mUnfoldAnimationController;
+
+ private final TestShellUnfoldProgressProvider mProgressProvider =
+ new TestShellUnfoldProgressProvider();
+ private final TestShellExecutor mShellExecutor = new TestShellExecutor();
+
+ private final TestUnfoldTaskAnimator mTaskAnimator1 = new TestUnfoldTaskAnimator();
+ private final TestUnfoldTaskAnimator mTaskAnimator2 = new TestUnfoldTaskAnimator();
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ when(mTransactionPool.acquire()).thenReturn(mock(SurfaceControl.Transaction.class));
+
+ final List<UnfoldTaskAnimator> animators = new ArrayList<>();
+ animators.add(mTaskAnimator1);
+ animators.add(mTaskAnimator2);
+ mUnfoldAnimationController = new UnfoldAnimationController(
+ mTransactionPool,
+ mProgressProvider,
+ animators,
+ () -> Optional.of(mUnfoldTransitionHandler),
+ mShellExecutor
+ );
+ }
+
+ @Test
+ public void testAppearedMatchingTask_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedMatchingTaskTwoDifferentAnimators_appliesUnfoldProgressToBoth() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 1);
+ mTaskAnimator2.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo1 = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(1).build();
+ RunningTaskInfo taskInfo2 = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo1, mLeash);
+ mUnfoldAnimationController.onTaskAppeared(taskInfo2, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ assertThat(mTaskAnimator2.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedNonMatchingTask_doesNotApplyUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET);
+ }
+
+ @Test
+ public void testAppearedAndChangedToNonMatchingTask_doesNotApplyUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(0);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET);
+ }
+
+ @Test
+ public void testAppearedAndChangedToNonMatchingTaskAndBack_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(0);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(2);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedNonMatchingTaskAndChangedToMatching_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ taskInfo.configuration.windowConfiguration.setWindowingMode(2);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testAppearedMatchingTaskAndChanged_appliesUnfoldProgress() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ mUnfoldAnimationController.onTaskInfoChanged(taskInfo);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(0.5f);
+ }
+
+ @Test
+ public void testShellTransitionRunning_doesNotApplyUnfoldProgress() {
+ when(mUnfoldTransitionHandler.willHandleTransition()).thenReturn(true);
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+
+ mUnfoldAnimationController.onStateChangeProgress(0.5f);
+ assertThat(mTaskAnimator1.mLastAppliedProgress).isEqualTo(UNSET);
+ }
+
+ @Test
+ public void testApplicableTaskDisappeared_resetsSurface() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 0);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+
+ assertThat(mTaskAnimator1.mResetTasks).contains(taskInfo.taskId);
+ }
+
+ @Test
+ public void testApplicablePinnedTaskDisappeared_doesNotResetSurface() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(2).build();
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+
+ assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+ }
+
+ @Test
+ public void testNonApplicableTaskAppearedDisappeared_doesNotResetSurface() {
+ mTaskAnimator1.setTaskMatcher((info) -> info.getWindowingMode() == 2);
+ RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setWindowingMode(0).build();
+
+ mUnfoldAnimationController.onTaskAppeared(taskInfo, mLeash);
+ mUnfoldAnimationController.onTaskVanished(taskInfo);
+
+ assertThat(mTaskAnimator1.mResetTasks).doesNotContain(taskInfo.taskId);
+ }
+
+ @Test
+ public void testInit_initsAndStartsAnimators() {
+ mUnfoldAnimationController.init();
+
+ assertThat(mTaskAnimator1.mInitialized).isTrue();
+ assertThat(mTaskAnimator1.mStarted).isTrue();
+ }
+
+ private static class TestShellUnfoldProgressProvider implements ShellUnfoldProgressProvider,
+ ShellUnfoldProgressProvider.UnfoldListener {
+
+ private final List<UnfoldListener> mListeners = new ArrayList<>();
+
+ @Override
+ public void addListener(Executor executor, UnfoldListener listener) {
+ mListeners.add(listener);
+ }
+
+ @Override
+ public void onStateChangeStarted() {
+ mListeners.forEach(UnfoldListener::onStateChangeStarted);
+ }
+
+ @Override
+ public void onStateChangeProgress(float progress) {
+ mListeners.forEach(unfoldListener -> unfoldListener.onStateChangeProgress(progress));
+ }
+
+ @Override
+ public void onStateChangeFinished() {
+ mListeners.forEach(UnfoldListener::onStateChangeFinished);
+ }
+ }
+
+ public static class TestUnfoldTaskAnimator implements UnfoldTaskAnimator {
+
+ public static final float UNSET = -1f;
+ private Predicate<TaskInfo> mTaskMatcher = (info) -> false;
+
+ Map<Integer, TaskInfo> mTasksMap = new HashMap<>();
+ Set<Integer> mResetTasks = new HashSet<>();
+
+ boolean mInitialized = false;
+ boolean mStarted = false;
+ float mLastAppliedProgress = UNSET;
+
+ @Override
+ public void init() {
+ mInitialized = true;
+ }
+
+ @Override
+ public void start() {
+ mStarted = true;
+ }
+
+ @Override
+ public void stop() {
+ mStarted = false;
+ }
+
+ @Override
+ public boolean isApplicableTask(TaskInfo taskInfo) {
+ return mTaskMatcher.test(taskInfo);
+ }
+
+ @Override
+ public void applyAnimationProgress(float progress, Transaction transaction) {
+ mLastAppliedProgress = progress;
+ }
+
+ public void setTaskMatcher(Predicate<TaskInfo> taskMatcher) {
+ mTaskMatcher = taskMatcher;
+ }
+
+ @Override
+ public void onTaskAppeared(TaskInfo taskInfo, SurfaceControl leash) {
+ mTasksMap.put(taskInfo.taskId, taskInfo);
+ }
+
+ @Override
+ public void onTaskVanished(TaskInfo taskInfo) {
+ mTasksMap.remove(taskInfo.taskId);
+ }
+
+ @Override
+ public void onTaskChanged(TaskInfo taskInfo) {
+ mTasksMap.put(taskInfo.taskId, taskInfo);
+ }
+
+ @Override
+ public void resetSurface(TaskInfo taskInfo, Transaction transaction) {
+ mResetTasks.add(taskInfo.taskId);
+ }
+
+ @Override
+ public void resetAllSurfaces(Transaction transaction) {
+ mTasksMap.values().forEach((t) -> mResetTasks.add(t.taskId));
+ }
+
+ @Override
+ public boolean hasActiveTasks() {
+ return mTasksMap.size() > 0;
+ }
+
+ public List<TaskInfo> getCurrentTasks() {
+ return new ArrayList<>(mTasksMap.values());
+ }
+ }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
new file mode 100644
index 000000000000..d1b837ef42f1
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -0,0 +1,326 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.windowdecor;
+
+import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlBuilder;
+import static com.android.wm.shell.MockSurfaceControlHelper.createMockSurfaceControlTransaction;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyBoolean;
+import static org.mockito.Mockito.argThat;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.verify;
+
+import android.app.ActivityManager;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.testing.AndroidTestingRunner;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.InsetsState;
+import android.view.SurfaceControl;
+import android.view.SurfaceControlViewHost;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.WindowManager.LayoutParams;
+import android.window.WindowContainerTransaction;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.DisplayController;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Supplier;
+
+/**
+ * Tests for {@link WindowDecoration}.
+ *
+ * Build/Install/Run:
+ * atest WMShellUnitTests:WindowDecorationTests
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class WindowDecorationTests extends ShellTestCase {
+ private static final int CAPTION_HEIGHT_DP = 32;
+ private static final int SHADOW_RADIUS_DP = 5;
+ private static final Rect TASK_BOUNDS = new Rect(100, 300, 400, 400);
+ private static final Point TASK_POSITION_IN_PARENT = new Point(40, 60);
+
+ private final Rect mOutsetsDp = new Rect();
+ private final WindowDecoration.RelayoutResult<TestView> mRelayoutResult =
+ new WindowDecoration.RelayoutResult<>();
+
+ @Mock
+ private DisplayController mMockDisplayController;
+ @Mock
+ private ShellTaskOrganizer mMockShellTaskOrganizer;
+ @Mock
+ private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
+ @Mock
+ private SurfaceControlViewHost mMockSurfaceControlViewHost;
+ @Mock
+ private TestView mMockView;
+ @Mock
+ private WindowContainerTransaction mMockWindowContainerTransaction;
+
+ private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
+ private SurfaceControl.Transaction mMockSurfaceControlTransaction;
+
+ @Before
+ public void setUp() {
+ mMockSurfaceControlTransaction = createMockSurfaceControlTransaction();
+
+ doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
+ .create(any(), any(), any(), anyBoolean());
+ }
+
+ @Test
+ public void testLayoutResultCalculation_invisibleTask() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(false)
+ .build();
+ taskInfo.isFocused = false;
+ // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
+ // 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mOutsetsDp.set(10, 20, 30, 40);
+
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(decorContainerSurfaceBuilder, never()).build();
+ verify(taskBackgroundSurfaceBuilder, never()).build();
+ verify(mMockSurfaceControlViewHostFactory, never())
+ .create(any(), any(), any(), anyBoolean());
+
+ verify(mMockSurfaceControlTransaction).hide(taskSurface);
+
+ assertNull(mRelayoutResult.mRootView);
+ }
+
+ @Test
+ public void testLayoutResultCalculation_visibleFocusedTask() {
+ final Display defaultDisplay = mock(Display.class);
+ doReturn(defaultDisplay).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder decorContainerSurfaceBuilder =
+ createMockSurfaceControlBuilder(decorContainerSurface);
+ mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+ final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+ final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+ createMockSurfaceControlBuilder(taskBackgroundSurface);
+ mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.YELLOW);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(Display.DEFAULT_DISPLAY)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setBounds(TASK_BOUNDS)
+ .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+ .setVisible(true)
+ .build();
+ taskInfo.isFocused = true;
+ // Density is 2. Outsets are (20, 40, 60, 80) px. Shadow radius is 10px. Caption height is
+ // 64px.
+ taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+ mOutsetsDp.set(10, 20, 30, 40);
+
+ final SurfaceControl taskSurface = mock(SurfaceControl.class);
+ final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+
+ windowDecor.relayout(taskInfo);
+
+ verify(decorContainerSurfaceBuilder).setParent(taskSurface);
+ verify(decorContainerSurfaceBuilder).setContainerLayer();
+ verify(mMockSurfaceControlTransaction).setTrustedOverlay(decorContainerSurface, true);
+ verify(mMockSurfaceControlTransaction).setPosition(decorContainerSurface, -20, -40);
+ verify(mMockSurfaceControlTransaction).setWindowCrop(decorContainerSurface, 380, 220);
+
+ verify(taskBackgroundSurfaceBuilder).setParent(taskSurface);
+ verify(taskBackgroundSurfaceBuilder).setEffectLayer();
+ verify(mMockSurfaceControlTransaction).setWindowCrop(taskBackgroundSurface, 300, 100);
+ verify(mMockSurfaceControlTransaction)
+ .setColor(taskBackgroundSurface, new float[] {1.f, 1.f, 0.f});
+ verify(mMockSurfaceControlTransaction).setShadowRadius(taskBackgroundSurface, 10);
+
+ verify(mMockSurfaceControlViewHostFactory)
+ .create(any(), eq(defaultDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHost)
+ .setView(same(mMockView),
+ argThat(lp -> lp.height == 64
+ && lp.width == 300
+ && (lp.flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0));
+ if (ViewRootImpl.CAPTION_ON_SHELL) {
+ verify(mMockView).setTaskFocusState(true);
+ verify(mMockWindowContainerTransaction)
+ .addRectInsetsProvider(taskInfo.token,
+ new Rect(100, 300, 400, 364),
+ new int[] { InsetsState.ITYPE_CAPTION_BAR });
+ }
+
+ verify(mMockSurfaceControlTransaction)
+ .setPosition(taskSurface, TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y);
+ verify(mMockSurfaceControlTransaction)
+ .setCrop(taskSurface, new Rect(-20, -40, 360, 180));
+ verify(mMockSurfaceControlTransaction)
+ .show(taskSurface);
+
+ assertEquals(380, mRelayoutResult.mWidth);
+ assertEquals(220, mRelayoutResult.mHeight);
+ assertEquals(2, mRelayoutResult.mDensity, 0.f);
+ }
+
+ @Test
+ public void testNotCrashWhenDisplayAppearsAfterTask() {
+ doReturn(mock(Display.class)).when(mMockDisplayController)
+ .getDisplay(Display.DEFAULT_DISPLAY);
+
+ final int displayId = Display.DEFAULT_DISPLAY + 1;
+ final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+ new ActivityManager.TaskDescription.Builder()
+ .setBackgroundColor(Color.BLACK);
+ final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+ .setDisplayId(displayId)
+ .setTaskDescriptionBuilder(taskDescriptionBuilder)
+ .setVisible(true)
+ .build();
+
+ final TestWindowDecoration windowDecor =
+ createWindowDecoration(taskInfo, new SurfaceControl());
+ windowDecor.relayout(taskInfo);
+
+ // It shouldn't show the window decoration when it can't obtain the display instance.
+ assertThat(mRelayoutResult.mRootView).isNull();
+
+ final ArgumentCaptor<DisplayController.OnDisplaysChangedListener> listenerArgumentCaptor =
+ ArgumentCaptor.forClass(DisplayController.OnDisplaysChangedListener.class);
+ verify(mMockDisplayController).addDisplayWindowListener(listenerArgumentCaptor.capture());
+ final DisplayController.OnDisplaysChangedListener listener =
+ listenerArgumentCaptor.getValue();
+
+ // Adding an irrelevant display shouldn't change the result.
+ listener.onDisplayAdded(Display.DEFAULT_DISPLAY);
+ assertThat(mRelayoutResult.mRootView).isNull();
+
+ final Display mockDisplay = mock(Display.class);
+ doReturn(mockDisplay).when(mMockDisplayController).getDisplay(displayId);
+
+ listener.onDisplayAdded(displayId);
+
+ // The listener should be removed when the display shows up.
+ verify(mMockDisplayController).removeDisplayWindowListener(same(listener));
+
+ assertThat(mRelayoutResult.mRootView).isSameInstanceAs(mMockView);
+ verify(mMockSurfaceControlViewHostFactory)
+ .create(any(), eq(mockDisplay), any(), anyBoolean());
+ verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
+ }
+
+ private TestWindowDecoration createWindowDecoration(
+ ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
+ return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
+ taskInfo, testSurface, new MockSurfaceControlBuilderSupplier(),
+ mMockSurfaceControlViewHostFactory);
+ }
+
+ private class MockSurfaceControlBuilderSupplier implements Supplier<SurfaceControl.Builder> {
+ private int mNumOfCalls = 0;
+
+ @Override
+ public SurfaceControl.Builder get() {
+ final SurfaceControl.Builder builder =
+ mNumOfCalls < mMockSurfaceControlBuilders.size()
+ ? mMockSurfaceControlBuilders.get(mNumOfCalls)
+ : createMockSurfaceControlBuilder(mock(SurfaceControl.class));
+ ++mNumOfCalls;
+ return builder;
+ }
+ }
+
+ private static class TestView extends View implements TaskFocusStateConsumer {
+ private TestView(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void setTaskFocusState(boolean focused) {}
+ }
+
+ private class TestWindowDecoration extends WindowDecoration<TestView> {
+ TestWindowDecoration(Context context, DisplayController displayController,
+ ShellTaskOrganizer taskOrganizer, ActivityManager.RunningTaskInfo taskInfo,
+ SurfaceControl taskSurface,
+ Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier,
+ SurfaceControlViewHostFactory surfaceControlViewHostFactory) {
+ super(context, displayController, taskOrganizer, taskInfo, taskSurface,
+ surfaceControlBuilderSupplier, surfaceControlViewHostFactory);
+ }
+
+ @Override
+ void relayout(ActivityManager.RunningTaskInfo taskInfo) {
+ relayout(null /* taskInfo */, 0 /* layoutResId */, mMockView, CAPTION_HEIGHT_DP,
+ mOutsetsDp, SHADOW_RADIUS_DP, mMockSurfaceControlTransaction,
+ mMockWindowContainerTransaction, mRelayoutResult);
+ }
+ }
+}
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 1e5be6c3eed7..4b0ddd2fa2ef 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -201,8 +201,9 @@ void JankTracker::finishFrame(FrameInfo& frame, std::unique_ptr<FrameMetricsRepo
// If we are in triple buffering, we have enough buffers in queue to sustain a single frame
// drop without jank, so adjust the frame interval to the deadline.
if (isTripleBuffered) {
- deadline += frameInterval;
- frame.set(FrameInfoIndex::FrameDeadline) += frameInterval;
+ int64_t originalDeadlineDuration = deadline - frame[FrameInfoIndex::IntendedVsync];
+ deadline = mNextFrameStartUnstuffed + originalDeadlineDuration;
+ frame.set(FrameInfoIndex::FrameDeadline) = deadline;
}
// If we hit the deadline, cool!
diff --git a/libs/hwui/tests/unit/JankTrackerTests.cpp b/libs/hwui/tests/unit/JankTrackerTests.cpp
index 5b397de36a86..b67e419e7d4a 100644
--- a/libs/hwui/tests/unit/JankTrackerTests.cpp
+++ b/libs/hwui/tests/unit/JankTrackerTests.cpp
@@ -195,3 +195,68 @@ TEST(JankTracker, doubleStuffedThenPauseThenJank) {
ASSERT_EQ(3, container.get()->totalFrameCount());
ASSERT_EQ(2, container.get()->jankFrameCount());
}
+
+TEST(JankTracker, doubleStuffedTwoIntervalBehind) {
+ std::mutex mutex;
+ ProfileDataContainer container(mutex);
+ JankTracker jankTracker(&container);
+ std::unique_ptr<FrameMetricsReporter> reporter = std::make_unique<FrameMetricsReporter>();
+
+ uint64_t frameNumber = 0;
+ uint32_t surfaceId = 0;
+
+ // First frame janks
+ FrameInfo* info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 100_ms;
+ info->set(FrameInfoIndex::Vsync) = 101_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 107_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 117_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 117_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 116_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(1, container.get()->jankFrameCount());
+
+ // Second frame is long, but doesn't jank because double-stuffed.
+ // Second frame duration is between 1*interval ~ 2*interval
+ info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 116_ms;
+ info->set(FrameInfoIndex::Vsync) = 116_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 129_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 133_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 133_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 132_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(1, container.get()->jankFrameCount());
+
+ // Third frame is even longer, cause a jank
+ // Third frame duration is between 2*interval ~ 3*interval
+ info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 132_ms;
+ info->set(FrameInfoIndex::Vsync) = 132_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 160_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 165_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 165_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 148_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(2, container.get()->jankFrameCount());
+
+ // 4th frame is double-stuffed with a 2 * interval latency
+ // 4th frame duration is between 2*interval ~ 3*interval
+ info = jankTracker.startFrame();
+ info->set(FrameInfoIndex::IntendedVsync) = 148_ms;
+ info->set(FrameInfoIndex::Vsync) = 148_ms;
+ info->set(FrameInfoIndex::SwapBuffersCompleted) = 170_ms;
+ info->set(FrameInfoIndex::GpuCompleted) = 181_ms;
+ info->set(FrameInfoIndex::FrameCompleted) = 181_ms;
+ info->set(FrameInfoIndex::FrameInterval) = 16_ms;
+ info->set(FrameInfoIndex::FrameDeadline) = 164_ms;
+ jankTracker.finishFrame(*info, reporter, frameNumber, surfaceId);
+
+ ASSERT_EQ(2, container.get()->jankFrameCount());
+}
diff --git a/media/TEST_MAPPING b/media/TEST_MAPPING
index 4ec4767a35cc..05fbc7a52ec6 100644
--- a/media/TEST_MAPPING
+++ b/media/TEST_MAPPING
@@ -22,11 +22,15 @@
}
],
"file_patterns": ["(?i)drm|crypto"]
- }
- ],
- "imports": [
+ },
{
- "path": "frameworks/av/drm/mediadrm/plugins"
+ "name": "CtsMediaDrmFrameworkTestCases",
+ "options" : [
+ {
+ "include-annotation": "android.platform.test.annotations.Presubmit"
+ }
+ ],
+ "file_patterns": ["(?i)drm|crypto"]
}
]
}
diff --git a/media/java/android/media/projection/IMediaProjection.aidl b/media/java/android/media/projection/IMediaProjection.aidl
index b136d5bc4db3..2bdd5c8bc977 100644
--- a/media/java/android/media/projection/IMediaProjection.aidl
+++ b/media/java/android/media/projection/IMediaProjection.aidl
@@ -17,7 +17,7 @@
package android.media.projection;
import android.media.projection.IMediaProjectionCallback;
-import android.window.WindowContainerToken;
+import android.os.IBinder;
/** {@hide} */
interface IMediaProjection {
@@ -31,14 +31,14 @@ interface IMediaProjection {
void unregisterCallback(IMediaProjectionCallback callback);
/**
- * Returns the {@link android.window.WindowContainerToken} identifying the task to record, or
- * {@code null} if there is none.
+ * Returns the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+ * there is none.
*/
- WindowContainerToken getTaskRecordingWindowContainerToken();
+ IBinder getLaunchCookie();
/**
- * Updates the {@link android.window.WindowContainerToken} identifying the task to record, or
- * {@code null} if there is none.
+ * Updates the {@link android.os.IBinder} identifying the task to record, or {@code null} if
+ * there is none.
*/
- void setTaskRecordingWindowContainerToken(in WindowContainerToken token);
+ void setLaunchCookie(in IBinder launchCookie);
}
diff --git a/media/java/android/media/projection/MediaProjection.java b/media/java/android/media/projection/MediaProjection.java
index ba7bf3f5f5d3..ae44fc575f7c 100644
--- a/media/java/android/media/projection/MediaProjection.java
+++ b/media/java/android/media/projection/MediaProjection.java
@@ -25,13 +25,13 @@ import android.hardware.display.DisplayManager;
import android.hardware.display.VirtualDisplay;
import android.hardware.display.VirtualDisplayConfig;
import android.os.Handler;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.ArrayMap;
import android.util.Log;
import android.view.ContentRecordingSession;
import android.view.Surface;
-import android.window.WindowContainerToken;
import java.util.Map;
@@ -172,18 +172,16 @@ public final class MediaProjection {
@NonNull VirtualDisplayConfig.Builder virtualDisplayConfig,
@Nullable VirtualDisplay.Callback callback, @Nullable Handler handler) {
try {
- final WindowContainerToken taskWindowContainerToken =
- mImpl.getTaskRecordingWindowContainerToken();
+ final IBinder launchCookie = mImpl.getLaunchCookie();
Context windowContext = null;
ContentRecordingSession session;
- if (taskWindowContainerToken == null) {
+ if (launchCookie == null) {
windowContext = mContext.createWindowContext(mContext.getDisplayNoVerify(),
TYPE_APPLICATION, null /* options */);
session = ContentRecordingSession.createDisplaySession(
windowContext.getWindowContextToken());
} else {
- session = ContentRecordingSession.createTaskSession(
- taskWindowContainerToken.asBinder());
+ session = ContentRecordingSession.createTaskSession(launchCookie);
}
virtualDisplayConfig.setWindowManagerMirroring(true);
final DisplayManager dm = mContext.getSystemService(DisplayManager.class);
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index a389bfc42725..cb85ae4fe66b 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -48,7 +48,7 @@
<string name="helper_title_app_streaming">Cross-device services</string>
<!-- Description of the helper dialog for APP_STREAMING profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
+ <string name="helper_summary_app_streaming"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
<!-- ================= DEVICE_PROFILE_AUTOMOTIVE_PROJECTION ================= -->
@@ -82,7 +82,7 @@
<string name="helper_title_computer">Google Play services</string>
<!-- Description of the helper dialog for COMPUTER profile. [CHAR LIMIT=NONE] -->
- <string name="helper_summary_computer"> <xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to stream apps between your devices</string>
+ <string name="helper_summary_computer"><xliff:g id="app_name" example="GMS">%1$s</xliff:g> is requesting permission on behalf of your <xliff:g id="device_type" example="Chromebook">%2$s</xliff:g> to access your phone\u2019s photos, media, and notifications</string>
<!-- ================= null profile ================= -->
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index 9a80b0267976..87e61b527477 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -50,6 +50,7 @@ android_library {
"SettingsLibTwoTargetPreference",
"SettingsLibSettingsTransition",
"SettingsLibButtonPreference",
+ "SettingsLibDeviceStateRotationLock",
"setupdesign",
"zxing-core-1.7",
],
diff --git a/packages/SettingsLib/DeviceStateRotationLock/Android.bp b/packages/SettingsLib/DeviceStateRotationLock/Android.bp
new file mode 100644
index 000000000000..c642bd14ed79
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/Android.bp
@@ -0,0 +1,16 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+ name: "SettingsLibDeviceStateRotationLock",
+
+ srcs: ["src/**/*.java"],
+
+ min_sdk_version: "21",
+}
diff --git a/packages/SettingsLib/DeviceStateRotationLock/AndroidManifest.xml b/packages/SettingsLib/DeviceStateRotationLock/AndroidManifest.xml
new file mode 100644
index 000000000000..bce6599721e3
--- /dev/null
+++ b/packages/SettingsLib/DeviceStateRotationLock/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.settingslib.devicestate">
+
+</manifest>
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java
index 8aee576c3d04..8aee576c3d04 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/AndroidSecureSettings.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/AndroidSecureSettings.java
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
index 4ed7e19f341d..4ed7e19f341d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManager.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java
diff --git a/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java
index 10528739b2b0..10528739b2b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/devicestate/SecureSettings.java
+++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/SecureSettings.java
diff --git a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
index 50f69d1d692a..c629d96bcf4b 100644
--- a/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
+++ b/packages/SettingsLib/LayoutPreference/res/layout/settings_entity_header.xml
@@ -71,6 +71,8 @@
<TextView
android:id="@+id/entity_header_second_summary"
style="@style/TextAppearance.EntityHeaderSummary"
+ android:singleLine="false"
+ android:maxLines="4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
index d2a4de4aa86f..b9a6ecc8adf9 100644
--- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
+++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml
@@ -20,6 +20,11 @@
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:background="?android:attr/colorBackground"
+ android:minHeight="?android:attr/listPreferredItemHeight"
+ android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
+ android:paddingStart="?android:attr/listPreferredItemPaddingStart"
+ android:paddingTop="@dimen/settingslib_switchbar_margin"
+ android:paddingBottom="@dimen/settingslib_switchbar_margin"
android:orientation="vertical">
<LinearLayout
@@ -27,7 +32,6 @@
android:minHeight="@dimen/settingslib_min_switch_bar_height"
android:layout_height="wrap_content"
android:layout_width="match_parent"
- android:layout_margin="@dimen/settingslib_switchbar_margin"
android:paddingStart="@dimen/settingslib_switchbar_padding_left"
android:paddingEnd="@dimen/settingslib_switchbar_padding_right">
diff --git a/packages/SettingsLib/res/values-af/strings.xml b/packages/SettingsLib/res/values-af/strings.xml
index 378e1bb18f52..9f5cf35db62a 100644
--- a/packages/SettingsLib/res/values-af/strings.xml
+++ b/packages/SettingsLib/res/values-af/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Stel terug"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Verwyder"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Stel tans gassessie terug …"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Stel gastesessie terug?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Dit sal ’n nuwe gastesessie begin en alle programme en data van die huidige sessie uitvee"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Verlaat gasmodus?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Dit sal programme en data in die huidige gastesessie uitvee"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Gaan uit"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Stoor gasaktiwiteit?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Jy kan aktiwiteit in die huidige sessie stoor of alle programme en data uitvee"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Vee uit"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Stoor"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Verlaat gasmodus"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Stel jou gastesessie terug"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Verlaat gasmodus"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Alle aktiwiteit sal uitgevee word wanneer jy uitgaan"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Jy kan jou aktiwiteit stoor of uitvee wanneer jy uitgaan"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Stel terug om sessie-aktiwiteit nou uit te vee, of jy kan aktiwiteit stoor of uitvee wanneer jy uitgaan"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Neem \'n foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Kies \'n prent"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Kies foto"</string>
diff --git a/packages/SettingsLib/res/values-am/strings.xml b/packages/SettingsLib/res/values-am/strings.xml
index 6d833f97764a..747a935fa740 100644
--- a/packages/SettingsLib/res/values-am/strings.xml
+++ b/packages/SettingsLib/res/values-am/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ዳግም አስጀምር"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"አስወግድ"</string>
<string name="guest_resetting" msgid="7822120170191509566">"እንግዳን ዳግም በማስጀመር ላይ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"የእንግዳ ክፍለ-ጊዜ ዳግም ይጀመር?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ይህ አዲስ የእንግዳ ክፍለ-ጊዜ ይጀምራል እና ሁሉንም መተግበሪያዎች እና ውሂብ አሁን ካለው ክፍለ-ጊዜ ይሰርዛል"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ከእንግዳ ሁኔታ ይውጣ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ይህ አሁን ካለው የእንግዳ ክፍለ-ጊዜ መተግበሪያዎችን እና ውሂብን ይሰርዛል"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ውጣ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"የእንግዳ እንቅስቃሴ ይቀመጥ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"እንቅስቃሴን አሁን ካለው ክፍለ-ጊዜ ማስቀመጥ ወይም ሁሉንም መተግበሪያዎች እና ውሂብ መሰረዝ ይችላሉ"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ሰርዝ"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"አስቀምጥ"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ከእንግዳ ሁኔታ ውጣ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"የእንግዳ ክፍለ-ጊዜን ዳግም አስጀምር"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"እንግዳ ያስወጡ"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"በሚወጡበት ጊዜ ሁሉም እንቅስቃሴዎች ይሰረዛሉ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"በሚወጡበት ጊዜ እንቅስቃሴዎን ማስቀመጥ ወይም መሰረዝ ይችላሉ"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"የክፍለ-ጊዜ እንቅስቃሴን አሁን ለመሰረዝ ዳግም ያስጀምሩ፣ ወይም በሚወጡበት ጊዜ እንቅስቃሴን ማስቀመጥ ወይም መሰረዝ ይችላሉ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ፎቶ አንሳ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ምስል ይምረጡ"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ፎቶ ይምረጡ"</string>
diff --git a/packages/SettingsLib/res/values-ar/strings.xml b/packages/SettingsLib/res/values-ar/strings.xml
index a3c4ac59d314..f9585ad106a7 100644
--- a/packages/SettingsLib/res/values-ar/strings.xml
+++ b/packages/SettingsLib/res/values-ar/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"إعادة الضبط"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"إزالة"</string>
<string name="guest_resetting" msgid="7822120170191509566">"جارٍ إعادة ضبط جلسة الضيف…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"هل تريد إعادة ضبط جلسة الضيف؟"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"سيؤدي إجراء إعادة الضبط إلى بدء جلسة ضيف جديدة وحذف جميع التطبيقات والبيانات من الجلسة الحالية."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"هل تريد الخروج من وضع الضيف؟"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"سيؤدي الخروج من وضع الضيف إلى حذف التطبيقات والبيانات من جلسة الضيف الحالية."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"خروج"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"هل تريد حفظ النشاط في وضع الضيف؟"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"يمكنك حفظ نشاط من الجلسة الحالية أو حذف كلّ التطبيقات والبيانات."</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"حذف"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"حِفظ"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"الخروج من وضع الضيف"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"إعادة ضبط جلسة الضيف"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"الخروج من وضع الضيف"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"سيتم حذف جميع الأنشطة عند الخروج من وضع الضيف."</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"يمكنك حفظ نشاطك أو حذفه عند الخروج من وضع الضيف."</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"يمكنك إجراء إعادة ضبط لحذف نشاط الجلسة الآن، أو حِفظ النشاط أو حذفه عند الخروج من وضع الضيف."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"التقاط صورة"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"اختيار صورة"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"اختيار صورة"</string>
diff --git a/packages/SettingsLib/res/values-as/strings.xml b/packages/SettingsLib/res/values-as/strings.xml
index b3bba95f949b..7d4e6abb584c 100644
--- a/packages/SettingsLib/res/values-as/strings.xml
+++ b/packages/SettingsLib/res/values-as/strings.xml
@@ -400,9 +400,9 @@
<string name="app_process_limit_title" msgid="8361367869453043007">"নেপথ্যত চলা প্ৰক্ৰিয়াৰ সীমা"</string>
<string name="show_all_anrs" msgid="9160563836616468726">"নেপথ্য এএনআৰবোৰ দেখুৱাওক"</string>
<string name="show_all_anrs_summary" msgid="8562788834431971392">"নেপথ্য এপসমূহৰ বাবে এপে সঁহাৰি দিয়া নাই ডায়ল\'গ প্ৰদৰ্শন কৰক"</string>
- <string name="show_notification_channel_warnings" msgid="3448282400127597331">"জাননী চ্চেনেলৰ সকীয়নিসমূহ দেখুৱাওক"</string>
+ <string name="show_notification_channel_warnings" msgid="3448282400127597331">"জাননী চেনেলৰ সকীয়নিবোৰ দেখুৱাওক"</string>
<string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"কোনো এপে বৈধ চেনেল নোহোৱাকৈ কোনো জাননী প\'ষ্ট কৰিলে স্ক্ৰীনত সকীয়নি প্ৰদৰ্শন হয়"</string>
- <string name="force_allow_on_external" msgid="9187902444231637880">"বাহ্যিক সঞ্চয়াগাৰত এপক বলেৰে অনুমতি দিয়ক"</string>
+ <string name="force_allow_on_external" msgid="9187902444231637880">"বাহ্যিক ষ্ট\'ৰেজত এপক বলেৰে অনুমতি দিয়ক"</string>
<string name="force_allow_on_external_summary" msgid="8525425782530728238">"মেনিফেষ্টৰ মান যিয়েই নহওক, বাহ্যিক ষ্ট’ৰেজত লিখিবলৈ যিকোনো এপক উপযুক্ত কৰি তোলে"</string>
<string name="force_resizable_activities" msgid="7143612144399959606">"বলেৰে কাৰ্যকলাপসমূহৰ আকাৰ সলনি কৰিব পৰা কৰক"</string>
<string name="force_resizable_activities_summary" msgid="2490382056981583062">"মেনিফেষ্টৰ মান যিয়েই নহওক, মাল্টি-ৱিণ্ডৰ বাবে আটাইবোৰ কাৰ্যকলাপৰ আকাৰ সলনি কৰিব পৰা কৰক।"</string>
@@ -590,7 +590,7 @@
<string name="add_guest_failed" msgid="8074548434469843443">"নতুন অতিথি সৃষ্টি কৰিব পৰা নগ’ল"</string>
<string name="user_nickname" msgid="262624187455825083">"উপনাম"</string>
<string name="user_add_user" msgid="7876449291500212468">"ব্যৱহাৰকাৰী যোগ দিয়ক"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ কৰক"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"অতিথি যোগ দিয়ক"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"অতিথি আঁতৰাওক"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"অতিথিৰ ছেশ্বন ৰিছেট কৰক"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"অতিথিৰ ছেশ্বন ৰিছেট কৰিবনে?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ৰিছেট কৰক"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"আঁতৰাওক"</string>
<string name="guest_resetting" msgid="7822120170191509566">"অতিথিৰ ছেশ্বন ৰিছেট কৰি থকা হৈছে…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"অতিথিৰ ছেশ্বন ৰিছেট কৰিবনে?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"এইটোৱে এটা অতিথিৰ ছেশ্বন আৰম্ভ কৰিব আৰু বৰ্তমানৰ ছেশ্বনটোৰ পৰা আটাইবোৰ এপ্ আৰু ডেটা মচিব"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"অতিথি ম’ডৰ পৰা বাহিৰ হ’বনে?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"এইটোৱে বৰ্তমানৰ অতিথিৰ ছেশ্বনটোৰ পৰা এপ্ আৰু ডেটা মচিব"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"বাহিৰ হওক"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"অতিথিৰ কাৰ্যকলাপ ছেভ কৰিবনে?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"আপুনি বৰ্তমানৰ ছেশ্বনটোৰ পৰা কাৰ্যকলাপ ছেভ কৰিব পাৰে অথবা আটাইবোৰ এপ্ আৰু ডেটা মচিব পাৰে"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"মচক"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ছেভ কৰক"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"অতিথি ম’ডৰ পৰা বাহিৰ হওক"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"অতিথিৰ ছেশ্বন ৰিছেট কৰক"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"অতিথিৰ ছেশ্বনৰ পৰা বাহিৰ হওক"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"বাহিৰ হওঁতে আটাইবোৰ কাৰ্যকলাপ মচা হ’ব"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"আপুনি বাহিৰ হওঁতে নিজৰ কাৰ্যকলাপ ছেভ কৰিব অথবা মচিব পাৰে"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"এতিয়াই ছেশ্বনৰ কাৰ্যকলাপ ৰিছেট কৰক অথবা মচক অথবা আপুনি বাহিৰ হওঁতে কাৰ্যকলাপ ছেভ কৰিব অথবা মচিব পাৰে"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"এখন ফট’ তোলক"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"এখন প্ৰতিচ্ছবি বাছনি কৰক"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ফট’ বাছনি কৰক"</string>
diff --git a/packages/SettingsLib/res/values-az/strings.xml b/packages/SettingsLib/res/values-az/strings.xml
index ca78c68c83df..ed7c4b4cd847 100644
--- a/packages/SettingsLib/res/values-az/strings.xml
+++ b/packages/SettingsLib/res/values-az/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Sıfırlayın"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Silin"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Qonaq məlumatı sıfırlanır…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Qonaq sessiyası sıfırlansın?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Bu, yeni qonaq sessiyası başladacaq və cari sessiyadan bütün tətbiqləri və datanı siləcək"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Qonaq rejimindən çıxılsın?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Bununla cari qonaq sessiyasındakı bütün tətbiqlər və data silinəcək"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Çıxın"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Qonaq fəaliyyəti saxlansın?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Cari sessiyadakı fəaliyyəti saxlaya və ya bütün tətbiq və datanı silə bilərsiniz"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Silin"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Yadda saxlayın"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Qonaq rejimindən çıxın"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Qonaq sessiyasını sıfırlayın"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Qonaq rejimindən çıxın"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Çıxış zamanı bütün fəaliyyətlər silinəcək"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Çıxışda fəaliyyətinizi saxlaya və ya silə bilərsiniz"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Sessiya fəaliyyətini indi silmək üçün sıfırlayın və ya çıxışda fəaliyyəti saxlaya və ya silə bilərsiniz"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Foto çəkin"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Şəkil seçin"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Foto seçin"</string>
diff --git a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
index 0da57e4e7746..f85d31451f6f 100644
--- a/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
+++ b/packages/SettingsLib/res/values-b+sr+Latn/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetuj"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ukloni"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Sesija gosta se resetuje…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Želite da resetujete sesiju gosta?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Time ćete pokrenuti novu sesiju gosta i izbrisati sve aplikacije i podatke iz aktuelne sesije"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Izaći ćete iz režima gosta?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Time ćete izbrisati sve aplikacije i podatke iz aktuelne sesije gosta"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izađi"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Sačuvaćete aktivnosti gosta?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Sačuvajte aktivnosti iz aktuelne sesije ili izbrišite sve aplikacije i podatke"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Sačuvaj"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Izađi iz režima gosta"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Resetuj sesiju gosta"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Izađi iz režima gosta"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Sve aktivnosti će biti izbrisane pri izlazu"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Možete da sačuvate ili izbrišete aktivnosti pri izlazu"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Resetujete da biste izbrisali aktivnosti sesije odmah ili možete da sačuvate ili izbrišete aktivnosti pri izlazu"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Slikaj"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Izaberite sliku"</string>
diff --git a/packages/SettingsLib/res/values-be/strings.xml b/packages/SettingsLib/res/values-be/strings.xml
index 7c58ee7de9dc..061b1ae92120 100644
--- a/packages/SettingsLib/res/values-be/strings.xml
+++ b/packages/SettingsLib/res/values-be/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Скінуць"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Выдаліць"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Ідзе скід гасцявога сеанса…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Скінуць гасцявы сеанс?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Будзе запушчаны новы гасцявы сеанс. Усе праграмы і даныя бягучага сеанса будуць выдалены"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Выйсці з гасцявога рэжыму?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Будуць выдалены праграмы і даныя бягучага гасцявога сеанса"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Выйсці"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Захаваць дзеянні госця?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Можна захаваць даныя пра дзеянні ў бягучым сеансе ці выдаліць праграмы і даныя"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Выдаліць"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Захаваць"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Выйсці з гасцявога рэжыму"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Скінуць гасцявы сеанс"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Выйсці з гасцявога рэжыму"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Падчас выхаду будуць выдалены ўсе звесткі пра дзеянні"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Падчас выхаду можна захаваць ці выдаліць звесткі пра дзеянні"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Скіньце налады, каб выдаліць звесткі пра дзеянні падчас сеанса. Таксама можна захаваць ці выдаліць гэтыя звесткі ў час выхаду"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Зрабіць фота"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Выбраць відарыс"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Выбраць фота"</string>
diff --git a/packages/SettingsLib/res/values-bg/strings.xml b/packages/SettingsLib/res/values-bg/strings.xml
index f35ed0e8d0a4..4375eb653884 100644
--- a/packages/SettingsLib/res/values-bg/strings.xml
+++ b/packages/SettingsLib/res/values-bg/strings.xml
@@ -568,7 +568,7 @@
<string name="user_add_profile_item_summary" msgid="5418602404308968028">"Можете да ограничите достъпа до приложенията и съдържанието от профила си"</string>
<string name="user_add_user_item_title" msgid="2394272381086965029">"Потребител"</string>
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Ограничен потр. профил"</string>
- <string name="user_add_user_title" msgid="5457079143694924885">"Да се добави ли нов потреб.?"</string>
+ <string name="user_add_user_title" msgid="5457079143694924885">"Добавяне на нов потребител?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Можете да споделите това устройство с други хора, като създадете допълнителни потребители. Всеки от тях има собствено работно пространство, което може да персонализира с приложения, тапет и др. Потребителите могат също да коригират настройки на устройството, които засягат всички – например за Wi‑Fi.\n\nКогато добавите нов потребител, той трябва да настрои работното си пространство.\n\nВсеки потребител може да актуализира приложенията за всички останали потребители. Настройките и услугите за достъпност може да не се прехвърлят за новия потребител."</string>
<string name="user_add_user_message_short" msgid="3295959985795716166">"Когато добавите нов потребител, той трябва да настрои работното си пространство.\n\nВсеки потребител може да актуализира приложенията за всички останали потребители."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Настройване на потребителя?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Нулиране"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Премахване"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Сесията като гост се нулира…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Да се нулира ли сесията като гост?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Така ще стартирате нова сесия като гост и ще изтриете всички приложения и данни от текущата сесия"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Изход от режима на гост?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Така ще изтриете приложенията и данните от текущата сесия като гост"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Изход"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Активност като гост – запазване?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Можете да запазите активността от сесията или да изтриете всички прил. и данни"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Изтриване"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Запазване"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Изход от режима на гост"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Нулиране на сесията като гост"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Изход от сесията като гост"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Цялата активност ще бъде изтрита при изход"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"При изход можете да запазите активността или да я изтриете"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Нулирайте, за да изтриете активността в сесията сега. Можете също да я запазите или изтриете при изход"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Правене на снимка"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Избиране на изображение"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Избиране на снимката"</string>
diff --git a/packages/SettingsLib/res/values-bn/strings.xml b/packages/SettingsLib/res/values-bn/strings.xml
index 7d4208120491..f3f5b3d23dc1 100644
--- a/packages/SettingsLib/res/values-bn/strings.xml
+++ b/packages/SettingsLib/res/values-bn/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"ডিসকানেক্ট হচ্ছে..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"কানেক্ট হচ্ছে..."</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"কানেক্ট করা আছে<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"চেনানো হচ্ছে..."</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"যুক্ত করা হচ্ছে..."</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"কানেক্ট করা আছে (ফোনের অডিও ছাড়া)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"কানেক্ট করা আছে (মিডিয়ার অডিও ছাড়া)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"কানেক্ট করা আছে (মেসেজে অ্যাকসেস নেই)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"সীমাবদ্ধ প্রোফাইল"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"নতুন ব্যবহারকারী জুড়বেন?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"আপনি একাধিক ব্যবহারকারীর আইডি তৈরি করে অন্যদের সাথে এই ডিভাইসটি শেয়ার করতে পারেন। ডিভাইসের স্টোরেজে প্রত্যেক ব্যবহারকারী তার নিজস্ব জায়গা পাবেন যা তিনি অ্যাপ, ওয়ালপেপার এবং আরও অনেক কিছু দিয়ে কাস্টমাইজ করতে পারেন। ওয়াই-ফাই এর মতো ডিভাইস সেটিংস, যেগুলি সকলের ক্ষেত্রে প্রযোজ্য হয়, সেগুলি ব্যবহারকারীরা পরিবর্তন করতে পারবেন।\n\nনতুন ব্যবহারকারীর আইডি যোগ করলে সেই ব্যক্তিকে স্টোরেজে তার নিজের জায়গা সেট-আপ করতে হবে।\n\nঅন্যান্য ব্যবহারকারীদের হয়ে যে কোনও ব্যবহারকারী অ্যাপ আপডেট করতে পারবেন। তবে ব্যবহারযোগ্যতার সেটিংস এবং পরিষেবা নতুন ব্যবহারকারীর ক্ষেত্রে প্রযোজ্য নাও হতে পারে।"</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"আপনি একজন নতুন ব্যবহারকারী জুড়লে তাকে তার জায়গা সেট-আপ করে নিতে হবে।\n\nযেকোনো ব্যবহারকারী অন্য সব ব্যবহারকারীর জন্য অ্যাপ্লিকেশান আপডেট করতে পারবেন।"</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"আপনি একজন নতুন ব্যবহারকারী যোগ করলে তাকে তার জায়গা সেট-আপ করে নিতে হবে৷\n\nযেকোনও ব্যবহারকারী অন্য সব ব্যবহারকারীর জন্য অ্যাপ আপডেট করতে পারবেন৷"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"এখন ব্যবহারকারী সেট-আপ করবেন?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"নিশ্চিত করুন, যে ব্যক্তিটি ডিভাইসটি নেওয়ার জন্য এবং তার জায়গা সেট-আপ করার জন্য উপলব্ধ আছেন"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"এখনই প্রোফাইল সেট-আপ করবেন?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"রিসেট করুন"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"সরান"</string>
<string name="guest_resetting" msgid="7822120170191509566">"গেস্ট সেশন রিসেট করা হচ্ছে..."</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"অতিথি সেশন রিসেট করতে চান?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"এটি নতুন অতিথি সেশন চালু করবে এবং বর্তমান সেশন থেকে সব অ্যাপ ও ডেটা মুছে দেবে"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"\'অতিথি মোড\' ছেড়ে বেরিয়ে আসবেন?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"এটি বর্তমান অতিথি সেশন থেকে অ্যাপ ও ডেটা মুছে দেবে"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"বেরিয়ে আসুন"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"অতিথি অ্যাক্টিভিটি সেভ করবেন?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"আপনি বর্তমান সেশন থেকে অ্যাক্টিভিটি সেভ করতে বা সব অ্যাপ ও ডেটা মুছতে পারবেন"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"মুছুন"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"সেভ করুন"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"\'অতিথি মোড\' ছেড়ে বেরিয়ে আসুন"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"অতিথি সেশন রিসেট করুন"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"\'অতিথি মোড\' ছেড়ে বেরিয়ে আসুন"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ছেড়ে বেরিয়ে যাওয়ার সময় সব অ্যাক্টিভিটি মুছে দেওয়া হবে"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ছেড়ে বেরিয়ে যাওয়ার সময় আপনি অ্যাক্টিভিটি সেভ করতে পারবেন বা মুছতে পারবেন"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"সেশন অ্যাক্টিভিটি মুছে দিতে এখন রিসেট করুন বা ছেড়ে বেরিয়ে আসার সময় আপনি অ্যাক্টিভিটি সেভ করতে বা মুছতে পারবেন"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ফটো তুলুন"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"একটি ইমেজ বেছে নিন"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ফটো বেছে নিন"</string>
diff --git a/packages/SettingsLib/res/values-bs/strings.xml b/packages/SettingsLib/res/values-bs/strings.xml
index 93941218546a..8dcaa4044af6 100644
--- a/packages/SettingsLib/res/values-bs/strings.xml
+++ b/packages/SettingsLib/res/values-bs/strings.xml
@@ -589,8 +589,8 @@
<string name="add_user_failed" msgid="4809887794313944872">"Kreiranje novog korisnika nije uspjelo"</string>
<string name="add_guest_failed" msgid="8074548434469843443">"Kreiranje novog gosta nije uspjelo"</string>
<string name="user_nickname" msgid="262624187455825083">"Nadimak"</string>
- <string name="user_add_user" msgid="7876449291500212468">"Dodaj korisnika"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
+ <string name="user_add_user" msgid="7876449291500212468">"Dodajte korisnika"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Dodajte gosta"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Ukloni gosta"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Poništi sesiju gosta"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Poništiti sesiju gosta?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Poništi"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ukloni"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Poništavanje sesije gosta…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Poništiti sesiju gosta?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Ovim ćete pokrenuti novu sesiju gosta i izbrisati sve aplikacije i podatke iz trenutne sesije"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Napustiti način rada za gosta?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Ovim ćete izbrisati aplikacije i podatke iz trenutne sesije gosta"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Napusti"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Sačuvati aktivnost gosta?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Možete sačuvati aktivnost iz sesije ili izbrisati sve aplikacije i podatke"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Sačuvaj"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Napusti način rada za gosta"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Poništi sesiju gosta"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Izlazak iz sesije gosta"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Sva aktivnost će biti izbrisana kada napustite sesiju"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Možete sačuvati ili izbrisati svoju aktivnost pri napuštanju"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Poništite da sada izbrišete aktivnost iz sesije ili možete sačuvati ili izbrisati aktivnost pri napuštanju"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Snimite fotografiju"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberite sliku"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Odabir fotografije"</string>
diff --git a/packages/SettingsLib/res/values-ca/strings.xml b/packages/SettingsLib/res/values-ca/strings.xml
index 91dcbc91ab88..8ec8f0fc8032 100644
--- a/packages/SettingsLib/res/values-ca/strings.xml
+++ b/packages/SettingsLib/res/values-ca/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Restableix"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Suprimeix"</string>
<string name="guest_resetting" msgid="7822120170191509566">"S\'està restablint el convidat…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Vols restablir la sessió de convidat?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Aquesta acció iniciarà una nova sessió de convidat i suprimirà totes les aplicacions i dades de la sessió actual"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Sortir del mode de convidat?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Aquesta acció suprimirà les aplicacions i dades de la sessió de convidat actual"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Surt"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Desar l\'activitat de convidat?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Pots desar l\'activitat de la sessió actual o suprimir totes les apps i dades"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Suprimeix"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Desa"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Surt del mode de convidat"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Restableix la sessió de convidat"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Surt del mode de convidat"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Se suprimirà tota l\'activitat en sortir"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Pots desar o suprimir l\'activitat en sortir"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Restableix per suprimir l\'activitat de la sessió ara. També pots desar o suprimir l\'activitat en sortir."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fes una foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Tria una imatge"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Selecciona una foto"</string>
diff --git a/packages/SettingsLib/res/values-cs/strings.xml b/packages/SettingsLib/res/values-cs/strings.xml
index 7729815c5ff4..17e7ce3a27e6 100644
--- a/packages/SettingsLib/res/values-cs/strings.xml
+++ b/packages/SettingsLib/res/values-cs/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetovat"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Odstranit"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Resetování hosta…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Resetovat relaci hosta?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Tímto zahájíte novou relaci hosta a smažete všechny aplikace a data z aktuální relace"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Ukončit režim hosta?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Tímto smažete aplikace a data z aktuální relace hosta"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ukončit"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Uložit aktivitu hosta?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Aktivitu z aktuální relace můžete uložit, nebo všechny aplikace a data smazat"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Smazat"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Uložit"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Ukončit režim hosta"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Resetovat relaci hosta"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Ukončit režim hosta"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Veškerá aktivita bude při ukončení smazána"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Aktivitu můžete při ukončení uložit nebo smazat"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Aktivitu relace můžete ihned smazat resetováním, případně ji můžete uložit nebo smazat při ukončení"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Pořídit fotku"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrat obrázek"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Vybrat fotku"</string>
diff --git a/packages/SettingsLib/res/values-da/strings.xml b/packages/SettingsLib/res/values-da/strings.xml
index 6f4846ee8755..0ef99e9151fb 100644
--- a/packages/SettingsLib/res/values-da/strings.xml
+++ b/packages/SettingsLib/res/values-da/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Nulstil"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Fjern"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Nulstiller gæst…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Vil du nulstille gæstesessionen?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Denne handling starter en ny gæstesession og sletter alle apps og data fra den aktuelle session"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Vil du afslutte gæstetilstanden?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Denne handling sletter apps og data fra den aktuelle gæstesession."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Luk"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Vil du gemme gæsteaktiviteten?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Du kan gemme aktivitet fra den aktuelle session eller slette alle apps og data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Slet"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Gem"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Afslut gæstetilstanden"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Nulstil gæstesession"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Afslut gæstesession"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Al aktivitet slettes ved afslutning"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Du kan gemme eller slette din aktivitet ved afslutning"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Nulstil for at slette sessionsaktiviteten nu, eller du kan gemme eller slette aktivitet ved afslutning"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tag et billede"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Vælg et billede"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Vælg billede"</string>
diff --git a/packages/SettingsLib/res/values-de/strings.xml b/packages/SettingsLib/res/values-de/strings.xml
index 7d66c837d53b..c94218fec760 100644
--- a/packages/SettingsLib/res/values-de/strings.xml
+++ b/packages/SettingsLib/res/values-de/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Zurücksetzen"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Entfernen"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Gast wird zurückgesetzt…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Gastsitzung zurücksetzen?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hierdurch wird eine neue Gastsitzung gestartet und alle Apps und Daten der aktuellen Sitzung werden gelöscht"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Gastmodus beenden?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Hierdurch werden Apps und Daten der aktuellen Gastsitzung gelöscht"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Beenden"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Gastaktivität speichern?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Speichere Aktivitäten der aktuellen Sitzung oder lösche alle Apps und Daten"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Löschen"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Speichern"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Gastmodus beenden"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Gastsitzung zurücksetzen"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Gastmodus beenden"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Beim Beenden werden alle Aktivitäten gelöscht"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Speichere oder lösche deine Aktivitäten beim Beenden"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Zurücksetzen, um jetzt die Sitzungsaktivitäten zu löschen, oder Aktivitäten beim Beenden speichern oder löschen"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Foto machen"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Bild auswählen"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Foto auswählen"</string>
diff --git a/packages/SettingsLib/res/values-el/strings.xml b/packages/SettingsLib/res/values-el/strings.xml
index 0f04846856bc..f94d43a89934 100644
--- a/packages/SettingsLib/res/values-el/strings.xml
+++ b/packages/SettingsLib/res/values-el/strings.xml
@@ -151,7 +151,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Ακύρωση"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Η σύζευξη παρέχει πρόσβαση στις επαφές σας και το ιστορικό κλήσεων όταν συνδεθείτε."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Δεν ήταν δυνατή η σύζευξη με τη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Δεν ήταν δυνατή η σύζευξη με τη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g> λόγω εσφαλμένου αριθμού PIN ή κλειδιού πρόσβασης."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Δεν ήταν δυνατή η σύζευξη με το <xliff:g id="DEVICE_NAME">%1$s</xliff:g> λόγω εσφαλμένου PIN ή κλειδιού πρόσβασης."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Δεν είναι δυνατή η σύνδεση με τη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Η ζεύξη απορρίφθηκε από τη συσκευή <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Υπολογιστής"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Επαναφορά"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Κατάργηση"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Επαναφορά επισκέπτη…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Επαναφορά περιόδου σύνδεσης επισκέπτη;"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Με αυτόν τον τρόπο θα ξεκινήσει μια νέα περίοδος σύνδεσης επισκέπτη και θα διαγραφούν όλες οι εφαρμογές και τα δεδομένα από την τρέχουσα περίοδο σύνδεσης"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Έξοδος από λειτουργία επισκέπτη;"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Θα διαγραφούν εφαρμογές και δεδομένα από την τρέχουσα περίοδο σύνδεσης επισκέπτη"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Έξοδος"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Αποθήκευση δραστ. επισκέπτη;"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Αποθήκευση δραστ. τρέχουσας περιόδου σύνδεσης ή διαγραφή εφαρμογών και δεδομένων"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Διαγραφή"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Αποθήκευση"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Έξοδος από λειτουργία επισκέπτη"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Επαναφορά περ. σύνδεσης επισκέπτη"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Έξοδος επισκέπτη"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Όλη η δραστηριότητα θα διαγραφεί κατά την έξοδο"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Αποθηκεύστε ή διαγράψτε τη δραστηριότητά σας κατά την έξοδο"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Κάντε επαναφορά για να διαγράψετε τη δραστηριότητα της περιόδου σύνδεσης τώρα ή μπορείτε να αποθηκεύσετε ή να διαγράψετε τη δραστηριότητα κατά την έξοδο"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Λήψη φωτογραφίας"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Επιλογή εικόνας"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Επιλογή φωτογραφίας"</string>
diff --git a/packages/SettingsLib/res/values-en-rAU/strings.xml b/packages/SettingsLib/res/values-en-rAU/strings.xml
index 74e2dbbd05c9..23121e244477 100644
--- a/packages/SettingsLib/res/values-en-rAU/strings.xml
+++ b/packages/SettingsLib/res/values-en-rAU/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Reset"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remove"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Resetting guest…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Reset guest session?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"This will start a new guest session and delete all apps and data from the current session"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Exit guest mode?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"This will delete apps and data from the current guest session"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Exit"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Save guest activity?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"You can save activity from the current session or delete all apps and data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Delete"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Save"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Exit guest mode"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Reset guest session"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Exit guest"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"All activity will be deleted on exit"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"You can save or delete your activity on exit"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reset to delete session activity now, or you can save or delete activity on exit"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Select photo"</string>
diff --git a/packages/SettingsLib/res/values-en-rCA/strings.xml b/packages/SettingsLib/res/values-en-rCA/strings.xml
index eacaad02883f..5c5b6b9ddc3c 100644
--- a/packages/SettingsLib/res/values-en-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-en-rCA/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Reset"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remove"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Resetting guest…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Reset guest session?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"This will start a new guest session and delete all apps and data from the current session"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Exit guest mode?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"This will delete apps and data from the current guest session"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Exit"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Save guest activity?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"You can save activity from the current session or delete all apps and data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Delete"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Save"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Exit guest mode"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Reset guest session"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Exit guest"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"All activity will be deleted on exit"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"You can save or delete your activity on exit"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reset to delete session activity now, or you can save or delete activity on exit"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Select photo"</string>
diff --git a/packages/SettingsLib/res/values-en-rGB/strings.xml b/packages/SettingsLib/res/values-en-rGB/strings.xml
index 74e2dbbd05c9..23121e244477 100644
--- a/packages/SettingsLib/res/values-en-rGB/strings.xml
+++ b/packages/SettingsLib/res/values-en-rGB/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Reset"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remove"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Resetting guest…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Reset guest session?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"This will start a new guest session and delete all apps and data from the current session"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Exit guest mode?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"This will delete apps and data from the current guest session"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Exit"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Save guest activity?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"You can save activity from the current session or delete all apps and data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Delete"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Save"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Exit guest mode"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Reset guest session"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Exit guest"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"All activity will be deleted on exit"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"You can save or delete your activity on exit"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reset to delete session activity now, or you can save or delete activity on exit"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Select photo"</string>
diff --git a/packages/SettingsLib/res/values-en-rIN/strings.xml b/packages/SettingsLib/res/values-en-rIN/strings.xml
index 74e2dbbd05c9..23121e244477 100644
--- a/packages/SettingsLib/res/values-en-rIN/strings.xml
+++ b/packages/SettingsLib/res/values-en-rIN/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Reset"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remove"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Resetting guest…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Reset guest session?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"This will start a new guest session and delete all apps and data from the current session"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Exit guest mode?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"This will delete apps and data from the current guest session"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Exit"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Save guest activity?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"You can save activity from the current session or delete all apps and data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Delete"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Save"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Exit guest mode"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Reset guest session"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Exit guest"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"All activity will be deleted on exit"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"You can save or delete your activity on exit"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reset to delete session activity now, or you can save or delete activity on exit"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Take a photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choose an image"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Select photo"</string>
diff --git a/packages/SettingsLib/res/values-en-rXC/strings.xml b/packages/SettingsLib/res/values-en-rXC/strings.xml
index 70227dce71e4..df280cdbbf8a 100644
--- a/packages/SettingsLib/res/values-en-rXC/strings.xml
+++ b/packages/SettingsLib/res/values-en-rXC/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‎‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎Reset‎‏‎‎‏‎"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‏‎‏‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‎‏‏‏‎Remove‎‏‎‎‏‎"</string>
<string name="guest_resetting" msgid="7822120170191509566">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‎‏‏‏‏‏‎‎Resetting guest…‎‏‎‎‏‎"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‎‎‏‎‎‎‏‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‏‏‎‎‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎‏‎‎‏‎‏‎‎‏‎Reset guest session?‎‏‎‎‏‎"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‏‏‎‎‏‎‏‏‏‎‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‎‎‎‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎‏‎‎‏‎‏‏‎‎This will start a new guest session and delete all apps and data from the current session‎‏‎‎‏‎"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‎‎‎‎‎‎‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‏‎‎‎‎‎‏‎‎‏‏‎‏‎‏‏‎‎‎Exit guest mode?‎‏‎‎‏‎"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‎‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎This will delete apps and data from the current guest session‎‏‎‎‏‎"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‎‎‎‎‎‏‏‎‎‎‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‎‎‎‏‎‎‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎‎‎Exit‎‏‎‎‏‎"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‏‎‏‎‎‏‎‎‏‏‏‏‏‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‏‎‎Save guest activity?‎‏‎‎‏‎"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‏‎‎‎‏‏‎‎‏‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‏‎‎‎‏‎‎‏‎‎You can save activity from the current session or delete all apps and data‎‏‎‎‏‎"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‎‏‎‏‎‏‏‏‎‏‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‎‏‎‏‏‎‎‏‏‎‎Delete‎‏‎‎‏‎"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‏‏‎‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‏‎‏‏‎‏‏‎Save‎‏‎‎‏‎"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‎‏‎‎‎‎‎‎‎‎‎‏‏‏‎‎‎‎Exit guest mode‎‏‎‎‏‎"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‎‏‏‏‎‎‏‎‎‎‎‎‎‎‏‏‏‎‎‎‎‏‏‏‎‏‏‏‎Reset guest session‎‏‎‎‏‎"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‏‏‏‎‎‏‎‎‎‏‎‏‎‎‏‎‏‏‏‏‏‎Exit guest‎‏‎‎‏‎"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎‏‎‎‎‏‎‏‏‏‎‎‏‏‎‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎‎‏‎‏‏‏‏‏‎All activity will be deleted on exit‎‏‎‎‏‎"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‏‏‏‎‎‏‏‏‎‎‏‏‎‏‏‎‏‎‎‏‎‏‏‎‏‎‏‎‏‏‏‏‎‎‎‎‎‎‎‏‎‎You can save or delete your activity on exit‎‏‎‎‏‎"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‎‏‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‎‎‎Reset to delete session activity now, or you can save or delete activity on exit‎‏‎‎‏‎"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‏‎‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‎‎‏‎‎Take a photo‎‏‎‎‏‎"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‎‏‏‏‎‏‏‎‏‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‎‏‏‎‏‎‎‎‎‎‏‎‏‏‏‏‎‎‎Choose an image‎‏‎‎‏‎"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‎‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎Select photo‎‏‎‎‏‎"</string>
diff --git a/packages/SettingsLib/res/values-es-rUS/strings.xml b/packages/SettingsLib/res/values-es-rUS/strings.xml
index d96e7f07a161..afdfa9242627 100644
--- a/packages/SettingsLib/res/values-es-rUS/strings.xml
+++ b/packages/SettingsLib/res/values-es-rUS/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Restablecer"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Quitar"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Restableciendo invitado…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"¿Quieres restablecer la sesión de invitado?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Esta acción comenzará una nueva sesión de invitado y borrará todas las apps y los datos de la sesión actual"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"¿Salir del modo de invitado?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Esta acción borrará todas las apps y los datos de la sesión de invitado actual"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Salir"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"¿Guardar actividad de invitado?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Puedes guardar la actividad de la sesión actual o borrar las apps y los datos"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Borrar"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Guardar"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Salir del modo de invitado"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Restablecer la sesión de invitado"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Salir del modo de invitado"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Cuando salgas, se borrará toda la actividad"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Puedes guardar o borrar la actividad cuando salgas"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Restablece la sesión para borrar la actividad ahora, o bien guarda y borra la actividad cuando salgas"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tomar una foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Elegir una imagen"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Seleccionar foto"</string>
diff --git a/packages/SettingsLib/res/values-es/strings.xml b/packages/SettingsLib/res/values-es/strings.xml
index b4d28d798333..c28a4590851a 100644
--- a/packages/SettingsLib/res/values-es/strings.xml
+++ b/packages/SettingsLib/res/values-es/strings.xml
@@ -507,7 +507,7 @@
<string name="screen_zoom_summary_extremely_large" msgid="1438045624562358554">"Lo más grande posible"</string>
<string name="screen_zoom_summary_custom" msgid="3468154096832912210">"Personalizado (<xliff:g id="DENSITYDPI">%d</xliff:g>)"</string>
<string name="content_description_menu_button" msgid="6254844309171779931">"Menú"</string>
- <string name="retail_demo_reset_message" msgid="5392824901108195463">"Escribe una contraseña para restablecer estado de fábrica en modo demostración"</string>
+ <string name="retail_demo_reset_message" msgid="5392824901108195463">"Escribe una contraseña para restablecer estado de fábrica en modo Demo"</string>
<string name="retail_demo_reset_next" msgid="3688129033843885362">"Siguiente"</string>
<string name="retail_demo_reset_title" msgid="1866911701095959800">"Contraseña obligatoria"</string>
<string name="active_input_method_subtypes" msgid="4232680535471633046">"Métodos de introducción de texto activos"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Restablecer"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Quitar"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Restableciendo invitado…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"¿Restablecer sesión de invitado?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Se iniciará una nueva sesión de invitado y se borrarán todas las aplicaciones y datos de esta sesión"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"¿Salir del modo Invitados?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Se eliminarán todas las aplicaciones y datos de la sesión de invitado"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Salir"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"¿Guardar actividad de invitados?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Puedes guardar la actividad de esta sesión o eliminar las aplicaciones y datos"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Eliminar"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Guardar"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Salir del modo Invitados"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Restablecer sesión de invitado"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Salir del modo Invitados"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toda la actividad se eliminará cuando salgas"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Puedes guardar o eliminar tu actividad cuando salgas"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Restablece la sesión para eliminar la actividad ahora, o guarda o elimina la actividad cuando salgas"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Hacer foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Seleccionar una imagen"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Seleccionar foto"</string>
diff --git a/packages/SettingsLib/res/values-et/strings.xml b/packages/SettingsLib/res/values-et/strings.xml
index 37a6db28ee0b..a42fc4d5f6c8 100644
--- a/packages/SettingsLib/res/values-et/strings.xml
+++ b/packages/SettingsLib/res/values-et/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Lähtesta"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Eemalda"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Külastajaseansi lähtestamine …"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Kas lähtestada külastajaseanss?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"See alustab uut külastajaseanssi ning kustutab kõik praeguse seansi rakendused ja andmed"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Kas väljuda külalisrežiimist?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"See kustutab praeguse külastajaseansi rakendused ja andmed"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Välju"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Kas salvestada külalise tegevus?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Võite selle seansi tegevused salvestada või kustutada kõik rakendused ja andmed."</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Kustuta"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Salvesta"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Välju külalisrežiimist"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Lähtesta külastajaseanss"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Välju külastajaseansist"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Kõik tegevused kustutatakse väljumisel"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Võite tegevused salvestada või kustutada väljumisel"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Lähtestage, et seansi tegevused kohe kustutada; samuti võite tegevused salvestada või kustutada väljumisel"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Pildistage"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Valige pilt"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Valige foto"</string>
diff --git a/packages/SettingsLib/res/values-eu/strings.xml b/packages/SettingsLib/res/values-eu/strings.xml
index 5386c3513fa4..61b9e0327697 100644
--- a/packages/SettingsLib/res/values-eu/strings.xml
+++ b/packages/SettingsLib/res/values-eu/strings.xml
@@ -590,7 +590,7 @@
<string name="add_guest_failed" msgid="8074548434469843443">"Ezin izan da sortu beste gonbidatu bat"</string>
<string name="user_nickname" msgid="262624187455825083">"Goitizena"</string>
<string name="user_add_user" msgid="7876449291500212468">"Gehitu erabiltzaile bat"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatua"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Gehitu gonbidatu bat"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Kendu gonbidatua"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Berrezarri gonbidatuentzako saioa"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Gonbidatuentzako saioa berrezarri nahi duzu?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Berrezarri"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Kendu"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Gonbidatuentzako saioa berrezartzen…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Gonbidatuentzako saioa berrezarri nahi duzu?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Gonbidatuentzako saio berri bat abiaraziko da, eta saio honetako aplikazio eta datu guztiak ezabatuko"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Gonbidatu modutik irten nahi duzu?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Gonbidatuentzako saio honetako aplikazioak eta datuak ezabatuko dira"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Irten"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Gonbidatuaren jarduerak gorde nahi dituzu?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Saio honetako jarduerak gorde ditzakezu, edo aplikazio eta datu guztiak ezabatu"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Ezabatu"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Gorde"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Irten gonbidatu modutik"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Berrezarri gonbidatuentzako saioa"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Irten gonbidatu modutik"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Saiotik irtetean, jarduera guztiak ezabatuko dira"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Irtetean, jarduerak gorde edo ezabatu egin ditzakezu"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Berrezarri saioa jarduerak ezabatzeko (bestela, jarduerak gordetzea edo ezabatzea aukera dezakezu irtetean)"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Atera argazki bat"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Aukeratu irudi bat"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Hautatu argazki bat"</string>
diff --git a/packages/SettingsLib/res/values-fa/strings.xml b/packages/SettingsLib/res/values-fa/strings.xml
index 740e1e69906f..34bed451dbeb 100644
--- a/packages/SettingsLib/res/values-fa/strings.xml
+++ b/packages/SettingsLib/res/values-fa/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"بازنشانی"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"برداشتن"</string>
<string name="guest_resetting" msgid="7822120170191509566">"درحال بازنشانی مهمان…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"جلسه مهمان بازنشانی شود؟"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"با این کار، جلسه مهمان جدیدی شروع خواهد شد و همه برنامه‌ها و داده‌ها از جلسه کنونی حذف خواهند شد"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"از حالت مهمان خارج می‌شوید؟"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"با این کار، برنامه‌ها و داده‌ها از جلسه مهمان کنونی حذف خواهند شد."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"خروج"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"فعالیت مهمان ذخیره شود؟"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"می‌توانید فعالیت جلسه کنونی را ذخیره کنید یا همه برنامه و داده‌ها را حذف کنید"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"حذف"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ذخیره"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"خروج از حالت مهمان"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"بازنشاندن جلسه مهمان"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"خروج مهمان"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"همه فعالیت‌ها هنگام خروج حذف خواهد شد"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"می‌توانید فعالیتتان را هنگام خروج ذخیره یا حذف کنید"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"برای حذف فعالیت جلسه در این لحظه، بازنشانی کنید یا می‌توانید فعالیت را هنگام خروج ذخیره یا حذف کنید"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"عکس گرفتن"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"انتخاب تصویر"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"انتخاب عکس"</string>
diff --git a/packages/SettingsLib/res/values-fi/strings.xml b/packages/SettingsLib/res/values-fi/strings.xml
index 32c52412359b..e14cf873faa9 100644
--- a/packages/SettingsLib/res/values-fi/strings.xml
+++ b/packages/SettingsLib/res/values-fi/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Nollaa"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Poista"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Nollataan vierasta…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Nollataanko vierailija-käyttökerta?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Tämä aloittaa uuden vierailija-käyttökerran ja kaikki nykyisen istunnon sovellukset ja data poistetaan"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Poistutaanko vierastilasta?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Tämä poistaa nykyisen vierailija-käyttökerran sovellukset ja datan"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Sulje"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Tallennetaanko vierastoiminta?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Voit tallentaa tämän istunnon toimintaa tai poistaa kaikki sovellukset ja datan"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Poista"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Tallenna"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Poistu vierastilasta"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Nollaa vierailija-käyttökerta"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Kirjaa vieras ulos"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Kaikki toiminta poistetaan uloskirjaamisen aikana"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Voit tallentaa tai poistaa toiminnan poistuessasi"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Nollaa poistaaksesi istunnon toiminnan nyt, tai voit tallentaa tai poistaa toimintaa poistuessasi"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ota kuva"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Valitse kuva"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Valitse kuva"</string>
diff --git a/packages/SettingsLib/res/values-fr-rCA/strings.xml b/packages/SettingsLib/res/values-fr-rCA/strings.xml
index e1e731f601e7..8ca7c42a5385 100644
--- a/packages/SettingsLib/res/values-fr-rCA/strings.xml
+++ b/packages/SettingsLib/res/values-fr-rCA/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Réinitialiser"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Retirer"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Réinitialisation de la session Invité en cours…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Réinitialiser la session d\'invité?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Une nouvelle session d\'invité sera lancée, et toutes les applications et données de la session en cours seront supprimées"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Quitter le mode Invité?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Les applications et les données de la session d\'invité en cours seront supprimées"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Quitter"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Enregistrer l\'activité d\'invité?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Vous pouvez enregistrer l\'activité de session ou supprimer les applis et données"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Supprimer"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Enregistrer"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Quitter le mode Invité"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Réinitialiser la session d\'invité"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Quitter la session d\'invité"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toute l\'activité sera supprimée à la fin de la session"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Vous pouvez enregistrer ou supprimer votre activité à la fin"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Réinitialisez pour supprimer l\'activité de la session maintenant, ou vous pouvez enregistrer ou supprimer l\'activité à la fin de la session"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Sélectionner une image"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Sélectionnez une photo"</string>
diff --git a/packages/SettingsLib/res/values-fr/strings.xml b/packages/SettingsLib/res/values-fr/strings.xml
index e635464d0926..265a21f55535 100644
--- a/packages/SettingsLib/res/values-fr/strings.xml
+++ b/packages/SettingsLib/res/values-fr/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Réinitialiser"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Supprimer"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Réinitialisation de la session Invité…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Réinitialiser la session Invité ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Cette action lancera une nouvelle session Invité et supprimera toutes les applis et données de la session actuelle"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Quitter le mode Invité ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Cette action supprimera les applis et données de la session Invité actuelle."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Quitter"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Enregistrer l\'activité ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Enregistrez l\'activité de la session actuelle ou supprimez les applis et données"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Supprimer"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Enregistrer"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Quitter le mode Invité"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Réinitialiser la session Invité"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Quitter le mode Invité"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toute l\'activité sera supprimée une fois la session quittée"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Vous pouvez enregistrer ou supprimer l\'activité en quittant"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Réinitialisez la session pour supprimer immédiatement l\'activité, qui pourra aussi être enregistrée ou supprimée lorsque vous quitterez la session"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Prendre une photo"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Choisir une image"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Sélectionner une photo"</string>
diff --git a/packages/SettingsLib/res/values-gl/strings.xml b/packages/SettingsLib/res/values-gl/strings.xml
index f5ea9d473021..cd002d5f8d7a 100644
--- a/packages/SettingsLib/res/values-gl/strings.xml
+++ b/packages/SettingsLib/res/values-gl/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Restablecer"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Quitar"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Restablecendo sesión de convidado…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Queres restablecer a sesión de convidado?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Iniciarase unha nova sesión de convidado e eliminaranse todas as aplicacións e datos desta sesión"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Saír do modo de convidado?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Eliminaranse as aplicacións e os datos da sesión de convidado"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Saír"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Gardar actividade do convidado?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Podes gardar a actividade da sesión ou eliminar todas as aplicacións e datos"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Eliminar"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Gardar"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Saír do modo de convidado"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Restablecer sesión de convidado"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Saír do modo de convidado"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Eliminarase toda a actividade ao saír"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Podes gardar ou eliminar a túa actividade ao saír"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Restablece a sesión para eliminar a actividade agora, ou ben gárdaa ou elimínaa ao saír"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escoller imaxe"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Seleccionar foto"</string>
diff --git a/packages/SettingsLib/res/values-gu/strings.xml b/packages/SettingsLib/res/values-gu/strings.xml
index a9c4b69b261a..be103548e37e 100644
--- a/packages/SettingsLib/res/values-gu/strings.xml
+++ b/packages/SettingsLib/res/values-gu/strings.xml
@@ -146,7 +146,7 @@
<string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"ઇનપુટ માટે ઉપયોગ કરો"</string>
<string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="7689393730163320483">"શ્રવણ યંત્રો માટે ઉપયોગ કરો"</string>
<string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"LE_AUDIO માટે ઉપયોગ કરો"</string>
- <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"જોડી"</string>
+ <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"જોડી કરો"</string>
<string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"જોડી કરો"</string>
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"રદ કરો"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"એ કનેક્ટ કરેલ હોય ત્યારે જોડાણ બનાવવાથી તમારા સંપર્કો અને કૉલ ઇતિહાસનો અ‍ૅક્સેસ મળે છે."</string>
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"પ્રતિબંધિત પ્રોફાઇલ"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"નવા વપરાશકર્તાને ઉમેરીએ?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"તમે વધારાના વપરાશકર્તાઓ બનાવીને અન્ય લોકો સાથે આ ડિવાઇસને શેર કરી શકો છો. દરેક વપરાશકર્તા પાસે તેમની પોતાની સ્પેસ છે, જેને તેઓ ઍપ, વૉલપેપર, વગેરે સાથે કસ્ટમાઇઝ કરી શકે છે. વપરાશકર્તાઓ પ્રત્યેક વ્યક્તિને અસર કરતી હોય તેવી ડિવાઇસ સેટિંગ જેમ કે વાઇ-ફાઇને પણ સમાયોજિત કરી શકે છે.\n\nજ્યારે તમે કોઈ નવા વપરાશકર્તાને ઉમેરો છો, ત્યારે તે વ્યક્તિને તેમની સ્પેસ સેટ કરવાની જરૂર પડે છે.\n\nકોઈપણ વપરાશકર્તા અન્ય બધા વપરાશકર્તાઓ માટે ઍપને અપડેટ કરી શકે છે. નવા વપરાશકર્તાને ઍક્સેસિબિલિટી સેટિંગ અને સેવાઓ ટ્રાન્સફર ન પણ થાય."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"જ્યારે તમે કોઈ નવા વપરાશકર્તાને ઉમેરો છો, ત્યારે તે વ્યક્તિને તેમનું સ્થાન સેટ અપ કરવાની જરૂર પડે છે.\n\nકોઈપણ વપરાશકર્તા બધા અન્ય વપરાશકર્તાઓ માટે એપ્લિકેશન્સને અપડેટ કરી શકે છે."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"જ્યારે તમે કોઈ નવા વપરાશકર્તાને ઉમેરો છો, ત્યારે તે વ્યક્તિને તેમનું સ્થાન સેટ અપ કરવાની જરૂર પડે છે.\n\nકોઈપણ વપરાશકર્તા બધા અન્ય વપરાશકર્તાઓ માટે ઍપને અપડેટ કરી શકે છે."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"અત્યારે જ વપરાશકર્તાને સેટ અપ કરીએ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ખાતરી કરો કે વ્યક્તિ ડિવાઇસ લેવા અને તેમના સ્થાનનું સેટ અપ કરવા માટે ઉપલબ્ધ છે"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"હવે પ્રોફાઇલ સેટ કરીએ?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"રીસેટ કરો"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"કાઢી નાખો"</string>
<string name="guest_resetting" msgid="7822120170191509566">"અતિથિને રીસેટ કરી રહ્યાં છીએ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"શું અતિથિ સત્ર રીસેટ કરીએ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"આમ કરવાથી કોઈ નવું અતિથિ સત્ર ચાલુ થશે તેમજ હાલના સત્રમાંની તમામ ઍપ અને ડેટા ડિલીટ થશે"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"શું અતિથિ મોડમાંથી બહાર નીકળીએ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"આમ કરવાથી હાલના અતિથિ સત્રની તમામ ઍપ અને ડેટા ડિલીટ કરવામાં આવશે"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"બહાર નીકળો"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"શું અતિથિ પ્રવૃત્તિ સાચવીએ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"તમે હાલના સત્રની પ્રવૃત્તિ સાચવી શકો છો અથવા તમામ ઍપ અને ડેટા ડિલીટ કરી શકો છો"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ડિલીટ કરો"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"સાચવો"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"અતિથિ મોડમાંથી બહાર નીકળો"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"અતિથિ સત્ર રીસેટ કરો"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"અતિથિ મોડમાંથી બહાર નીકળો"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"બહાર નીકળતી વખતે તમામ પ્રવૃત્તિ ડિલીટ કરવામાં આવશે"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"બહાર નીકળતી વખતે તમે પ્રવૃત્તિ સાચવી કે ડિલીટ કરી શકશો"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"સત્રની પ્રવૃત્તિ હમણાં ડિલીટ કરવા માટે રીસેટ કરો અથવા બહાર નીકળતી વખતે તમે પ્રવૃત્તિ સાચવી કે ડિલીટ કરી શકશો"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ફોટો લો"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"છબી પસંદ કરો"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ફોટો પસંદ કરો"</string>
diff --git a/packages/SettingsLib/res/values-hi/arrays.xml b/packages/SettingsLib/res/values-hi/arrays.xml
index a449773611a1..61a8f92b792d 100644
--- a/packages/SettingsLib/res/values-hi/arrays.xml
+++ b/packages/SettingsLib/res/values-hi/arrays.xml
@@ -265,7 +265,7 @@
<item msgid="910925519184248772">"PTP (पिक्चर ट्रांसफर प्रोटोकॉल)"</item>
<item msgid="3825132913289380004">"RNDIS (USB ईथरनेट)"</item>
<item msgid="8828567335701536560">"ऑडियो स्रोत"</item>
- <item msgid="8688681727755534982">"MIDI"</item>
+ <item msgid="8688681727755534982">"एमआईडीआई"</item>
</string-array>
<string-array name="avatar_image_descriptions">
</string-array>
diff --git a/packages/SettingsLib/res/values-hi/strings.xml b/packages/SettingsLib/res/values-hi/strings.xml
index 325fdbba7c25..68dd25857af7 100644
--- a/packages/SettingsLib/res/values-hi/strings.xml
+++ b/packages/SettingsLib/res/values-hi/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"डिस्‍कनेक्‍ट हो रहा है..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"कनेक्ट हो रहा है..."</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> से जुड़ गया"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"युग्‍मित कर रहा है…"</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"जोड़ा जा रहा है…"</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"जुड़ गया (फ़ोन के ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"जुड़ गया (मीडिया ऑडियो को छोड़कर)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"जुड़ गया (मैसेज का ऐक्सेस नहीं)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"रीसेट करें"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"हटाएं"</string>
<string name="guest_resetting" msgid="7822120170191509566">"मेहमान के तौर पर ब्राउज़ करने का सेशन रीसेट किया जा रहा है…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"क्या मेहमान मोड के मौजूदा सेशन को रीसेट करना है?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ऐसा करने पर, मेहमान के तौर पर ब्राउज़ करने का एक नया सेशन शुरू हो जाएगा. साथ ही, पिछले सेशन में मौजूद डेटा और इस्तेमाल किए जा रहे ऐप्लिकेशन को मिटा दिया जाएगा"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"मेहमान मोड से बाहर निकलना है?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"इससे, मेहमान मोड के मौजूदा सेशन का डेटा और इसमें इस्तेमाल हो रहे ऐप मिट जाएंगे"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"बाहर निकलें"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"मेहमान मोड की गतिविधि को सेव करना है?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"मौजूदा सेशन की गतिविधि को सेव किया जा सकता है या सभी ऐप और डेटा को मिटाया जा सकता है"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"मिटाएं"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"सेव करें"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"मेहमान मोड से बाहर निकलें?"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"मेहमान मोड के सेशन को रीसेट करें?"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"मेहमान मोड से बाहर निकलें"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"बाहर निकलने पर, सारी गतिविधि मिट जाएगी"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"बाहर निकलने पर, गतिविधि को मिटाया या सेव किया जा सकता है"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"सेशन की गतिविधि को अभी मिटाने के लिए, रीसेट करें. इसके अलावा, बाहर निकलने पर, गतिविधि को मिटाया या सेव किया जा सकता है"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"फ़ोटो खींचें"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"कोई इमेज चुनें"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"फ़ोटो चुनें"</string>
diff --git a/packages/SettingsLib/res/values-hr/strings.xml b/packages/SettingsLib/res/values-hr/strings.xml
index fdf14b3fab88..69d643c0282e 100644
--- a/packages/SettingsLib/res/values-hr/strings.xml
+++ b/packages/SettingsLib/res/values-hr/strings.xml
@@ -365,11 +365,11 @@
<string name="show_touches" msgid="8437666942161289025">"Prikaži dodire"</string>
<string name="show_touches_summary" msgid="3692861665994502193">"Prikaži vizualne povratne informacije za dodire"</string>
<string name="show_screen_updates" msgid="2078782895825535494">"Prikaži ažur. površine"</string>
- <string name="show_screen_updates_summary" msgid="2126932969682087406">"Površina prozora bljeska pri ažuriranju."</string>
- <string name="show_hw_screen_updates" msgid="2021286231267747506">"Pokaži ažuriranja prikaza"</string>
+ <string name="show_screen_updates_summary" msgid="2126932969682087406">"Površina prozora bljeska pri ažuriranju"</string>
+ <string name="show_hw_screen_updates" msgid="2021286231267747506">"Prikaži ažuriranja prikaza"</string>
<string name="show_hw_screen_updates_summary" msgid="3539770072741435691">"Bljeskanje prikaza u prozorima pri crtanju"</string>
<string name="show_hw_layers_updates" msgid="5268370750002509767">"Prikaži ažuriranja hardverskih slojeva"</string>
- <string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"Hardverski slojevi bljeskaju zeleno pri ažuriranju."</string>
+ <string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"Hardverski slojevi bljeskaju zeleno pri ažuriranju"</string>
<string name="debug_hw_overdraw" msgid="8944851091008756796">"Rješavanje GPU preklapanja"</string>
<string name="disable_overlays" msgid="4206590799671557143">"Onemogući dijeljenje mem."</string>
<string name="disable_overlays_summary" msgid="1954852414363338166">"Uvijek koristi GPU za slaganje zaslona"</string>
@@ -378,7 +378,7 @@
<string name="usb_audio_disable_routing" msgid="3367656923544254975">"Onemogući USB audiousmj."</string>
<string name="usb_audio_disable_routing_summary" msgid="8768242894849534699">"Onemogući aut. usmjeravanje na USB audioperiferiju"</string>
<string name="debug_layout" msgid="1659216803043339741">"Prikaži okvir prikaza"</string>
- <string name="debug_layout_summary" msgid="8825829038287321978">"Prikazuju se obrubi, margine itd. isječaka."</string>
+ <string name="debug_layout_summary" msgid="8825829038287321978">"Prikazuju se obrubi, margine itd. isječaka"</string>
<string name="force_rtl_layout_all_locales" msgid="8690762598501599796">"Nametni zdesna ulijevo"</string>
<string name="force_rtl_layout_all_locales_summary" msgid="6663016859517239880">"Nametni smjer zdesna ulijevo za sve zemlje/jezike"</string>
<string name="window_blurs" msgid="6831008984828425106">"Dopusti zamućenja na razini prozora"</string>
@@ -594,10 +594,25 @@
<string name="guest_exit_guest" msgid="5908239569510734136">"Uklanjanje gosta"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Poništi gostujuću sesiju"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Poništiti gostujuću sesiju?"</string>
- <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Ukloniti gosta?"</string>
+ <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite li ukloniti gosta?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Poništi"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ukloni"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Poništavanje gostujuće sesije…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Želite li poništiti gostujuću sesiju?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Time će se pokrenuti nova gostujuća sesija i izbrisati sve aplikacije i podaci iz trenutačne sesije."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Napustiti način rada za goste?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Time će se izbrisati aplikacije i podaci iz trenutačne gostujuće sesije."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Izlaz"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Spremiti aktivnosti gosta?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Možete spremiti aktivnosti iz ove sesije ili izbrisati sve aplikacije i podatke"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Spremi"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Izlaz iz načina rada za goste"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Poništi gostujuću sesiju"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Izlaz iz gostujuće sesije"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Sve će se aktivnosti izbrisati na izlasku"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Možete spremiti ili izbrisati svoje aktivnosti na izlasku"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Poništite da biste odmah izbrisali aktivnost sesije, a možete i spremiti ili izbrisati aktivnost na izlasku."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotografiraj"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Odaberi sliku"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Odabir slike"</string>
diff --git a/packages/SettingsLib/res/values-hu/strings.xml b/packages/SettingsLib/res/values-hu/strings.xml
index 862078055372..fe6bc88cded3 100644
--- a/packages/SettingsLib/res/values-hu/strings.xml
+++ b/packages/SettingsLib/res/values-hu/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Visszaállítás"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Eltávolítás"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Vendég munkamenet visszaállítása…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Visszaállítja a vendégmunkamenetet?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"A művelettel új vendégmunkamenetet indít, valamint az összes alkalmazást és adatot törli az aktuális munkamenetből"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Kilép a vendég módból?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"A művelettel törlődnek az aktuális vendégmunkamenet alkalmazásai és adatai"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Kilépés"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Menti a vendégtevékenységeket?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Tevékenységeket menthet az aktuális munkamenetből, vagy minden appot és adatot törölhet"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Törlés"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Mentés"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Kilépés a vendég módból"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Vendégmunkamenet visszaállítása"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Vendég kiléptetése"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"A kilépéssel minden tevékenység törlődik"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"A kilépéskor mentheti vagy törölheti a tevékenységeket"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Visszaállítással azonnal törölheti a munkamenethez tartozó tevékenységeket, illetve kilépéskor mentheti vagy törölheti a tevékenységeket"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotó készítése"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Kép kiválasztása"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Fotó kiválasztása"</string>
diff --git a/packages/SettingsLib/res/values-hy/strings.xml b/packages/SettingsLib/res/values-hy/strings.xml
index 2cd935205664..0550b296b5ad 100644
--- a/packages/SettingsLib/res/values-hy/strings.xml
+++ b/packages/SettingsLib/res/values-hy/strings.xml
@@ -151,7 +151,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Չեղարկել"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Զուգակցում է մուտքի թույլտվությունը դեպի ձեր կոնտակտները և զանգերի պատմությունը, երբ միացված է:"</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Հնարավոր չեղավ զուգակցվել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ սխալ PIN-ի կամ անցաբառի պատճառով:."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Չհաջողվեց զուգակցել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ սխալ PIN-ի կամ անցաբառի պատճառով:"</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Հնարավոր չէ կապ հաստատել <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի հետ:"</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Զուգավորումը մերժվեց <xliff:g id="DEVICE_NAME">%1$s</xliff:g>-ի կողմից:"</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Համակարգիչ"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Զրոյացնել"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Հեռացնել"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Հյուրի աշխատաշրջանը վերակայվում է…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Վերակայե՞լ հյուրի աշխատաշրջանը"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Կսկսվի հյուրի նոր աշխատաշրջան, իսկ նախորդ աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Դուրս գա՞լ հյուրի ռեժիմից"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Հյուրի ընթացիկ աշխատաշրջանի բոլոր հավելվածներն ու տվյալները կջնջվեն"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Դուրս գալ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Պահե՞լ հյուրի ռեժիմի պատմությունը"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Պահեք ընթացիկ աշխատաշրջանի պատմությունը կամ ջնջեք հավելվածներն ու տվյալները"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Ջնջել"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Պահել"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Դուրս գալ հյուրի ռեժիմից"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Վերակայել հյուրի աշխատաշրջանը"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Դուրս գալ հյուրի ռեժիմից"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Դուրս գալու դեպքում ամբողջ պատմությունը կջնջվի"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Դուրս գալիս կարող եք պահել կամ ջնջել ձեր պատմությունը"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Վերակայեք աշխատաշրջանի պատմությունը հիմա կամ պահեք/ջնջեք այն դուրս գալիս"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Լուսանկարել"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Ընտրել պատկեր"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Ընտրեք լուսանկար"</string>
diff --git a/packages/SettingsLib/res/values-in/strings.xml b/packages/SettingsLib/res/values-in/strings.xml
index da3c3e634f20..96e129a862fb 100644
--- a/packages/SettingsLib/res/values-in/strings.xml
+++ b/packages/SettingsLib/res/values-in/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Memutus sambungan..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Menghubungkan…"</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"Terhubung<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"Menyandingkan..."</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"Menyambungkan..."</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Terhubung (tanpa ponsel)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Terhubung (tanpa media)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"Terhubung (tanpa akses pesan)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -150,8 +150,8 @@
<string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"SAMBUNGKAN"</string>
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Batal"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Penyandingan memberi akses ke kontak dan histori panggilan saat terhubung"</string>
- <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Tidak dapat menyambungkan ke <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Tidak dapat menyambungkan ke <xliff:g id="DEVICE_NAME">%1$s</xliff:g> karena PIN atau kode sandi salah."</string>
+ <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Tidak dapat menyambungkan dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"Tidak dapat menyambungkan dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> karena PIN atau kode sandi salah."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"Tidak dapat berkomunikasi dengan <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Penyandingan ditolak oleh <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Komputer"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Reset"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Hapus"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Mereset tamu …"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Reset sesi tamu?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Tindakan ini akan memulai sesi tamu baru dan menghapus semua aplikasi serta data dari sesi saat ini"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Keluar dari mode tamu?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Tindakan ini akan menghapus aplikasi dan data dari sesi tamu saat ini"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Keluar"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Simpan aktivitas tamu?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Anda bisa menyimpan aktivitas sesi saat ini atau menghapus semua aplikasi &amp; data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Hapus"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Simpan"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Keluar dari mode tamu"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Reset sesi tamu"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Keluar dari mode tamu"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Semua aktivitas akan dihapus saat Anda keluar"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Anda dapat menyimpan atau menghapus aktivitas saat keluar"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reset untuk menghapus aktivitas sesi sekarang, atau Anda dapat menyimpan atau menghapus aktivitas saat keluar"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih gambar"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Pilih foto"</string>
diff --git a/packages/SettingsLib/res/values-is/strings.xml b/packages/SettingsLib/res/values-is/strings.xml
index e0ca6e35c985..3a1ab7fef74a 100644
--- a/packages/SettingsLib/res/values-is/strings.xml
+++ b/packages/SettingsLib/res/values-is/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Endurstilla"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Fjarlægja"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Endurstillir gest…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Endurstilla gestalotu?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Þetta opnar nýja gestalotu og eyðir öllum forritum og gögnum úr núverandi lotu"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Loka gestastillingu?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Þetta eyðir forritum og gögnum úr núverandi gestalotu"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Hætta"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Vista aðgerðir úr gestalotu?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Þú getur vistað aðgerðir úr núverandi lotu eða eytt öllum forritum og gögnum"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Eyða"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Vista"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Loka gestastillingu"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Endurstilla gestalotu"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Loka gestastillingu"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Öllum aðgerðum verður eytt þegar lotu er lokað"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Þú getur vistað eða eytt aðgerðum þegar þú lokar"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Endurstilltu til að eyða aðgerðum lotu núna, eða þú getur vistað eða eytt aðgerðum þegar þú lokar"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Taka mynd"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Velja mynd"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Velja mynd"</string>
diff --git a/packages/SettingsLib/res/values-it/strings.xml b/packages/SettingsLib/res/values-it/strings.xml
index 66c7273ab5d3..d8c49eb3359b 100644
--- a/packages/SettingsLib/res/values-it/strings.xml
+++ b/packages/SettingsLib/res/values-it/strings.xml
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Profilo con limitazioni"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Aggiungere un nuovo utente?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Puoi condividere il dispositivo con altre persone creando altri utenti. Ogni utente ha un proprio spazio personalizzabile con app, sfondo e così via. Gli utenti possono anche regolare le impostazioni del dispositivo, come il Wi‑Fi, che riguardano tutti.\n\nQuando crei un nuovo utente, la persona in questione deve configurare il proprio spazio.\n\nQualsiasi utente può aggiornare le app per tutti gli altri utenti. I servizi e le impostazioni di accessibilità non potranno essere trasferiti al nuovo utente."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"Il nuovo utente, una volta aggiunto, deve impostare il proprio spazio.\n\nQualsiasi utente può aggiornare le app per tutti gli altri."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"Il nuovo utente, una volta aggiunto, deve configurare il proprio spazio.\n\nQualsiasi utente può aggiornare le app per tutti gli altri."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Configurare l\'utente ora?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Assicurati che la persona sia disponibile a prendere il dispositivo e configurare il suo spazio"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Configurare il profilo ora?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Reimposta"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Rimuovi"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Reimpostazione sessione Ospite in corso…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Vuoi reimpostare la sessione Ospite?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Verrà avviata una nuova sessione Ospite e verranno eliminati tutti i dati e le app della sessione corrente"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Vuoi uscire da modalità Ospite?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Verranno eliminati i dati e le app della sessione Ospite corrente"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Esci"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Vuoi salvare l\'attività Ospite?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Puoi salvare l\'attività della sessione corrente o eliminare tutti i dati e app"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Elimina"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Salva"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Esci dalla modalità Ospite"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Reimposta sessione Ospite"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Esci dalla modalità Ospite"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Quando esci verrà eliminata tutta l\'attività"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Quando esci puoi salvare o eliminare la tua attività"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reimposta per eliminare subito l\'attività della sessione oppure puoi salvare o eliminare l\'attività quando esci"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Scatta una foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Scegli un\'immagine"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Seleziona la foto"</string>
diff --git a/packages/SettingsLib/res/values-iw/strings.xml b/packages/SettingsLib/res/values-iw/strings.xml
index c50b18079f52..fc70209f7d94 100644
--- a/packages/SettingsLib/res/values-iw/strings.xml
+++ b/packages/SettingsLib/res/values-iw/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"איפוס"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"הסרה"</string>
<string name="guest_resetting" msgid="7822120170191509566">"מתבצע איפוס של הגלישה כאורח…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"לאפס את הגלישה כאורח?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"הפעולה הזו תתחיל גלישה חדשה כאורח ותמחק את כל האפליקציות והנתונים מהסשן הנוכחי"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"לצאת ממצב אורח?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"הפעולה הזו תמחק את האפליקציות והנתונים מהגלישה הנוכחית כאורח"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"יציאה"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"לשמור את פעילות האורח?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"אפשר לשמור את הפעילות מהסשן הנוכחי או למחוק את כל האפליקציות והנתונים"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"מחיקה"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"שמירה"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"יציאה ממצב אורח"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"איפוס הגלישה כאורח"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"יציאה ממצב אורח"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"כל הפעילות תימחק ביציאה"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"אפשר לשמור או למחוק את הפעילות שלך ביציאה"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"ניתן לאפס כדי למחוק את הפעילות מהסשן כעת, או לשמור או למחוק את הפעילות ביציאה"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"צילום תמונה"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"לבחירת תמונה"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"בחירת תמונה"</string>
diff --git a/packages/SettingsLib/res/values-ja/strings.xml b/packages/SettingsLib/res/values-ja/strings.xml
index ae428df2a285..c42cca676620 100644
--- a/packages/SettingsLib/res/values-ja/strings.xml
+++ b/packages/SettingsLib/res/values-ja/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"リセット"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"削除"</string>
<string name="guest_resetting" msgid="7822120170191509566">"ゲストをリセットしています…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ゲスト セッションをリセットしますか?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"新しいゲスト セッションが開始し、現在のセッションのすべてのアプリとデータが削除されます"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ゲストモードを終了しますか?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"現在のゲスト セッションからすべてのアプリとデータが削除されます"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"終了"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ゲストのアクティビティを保存しますか?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"現在のセッションのアクティビティの保存や、すべてのアプリとデータの削除を行えます"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"削除"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"保存"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ゲストモードを終了"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ゲスト セッションをリセット"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ゲストを終了"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"終了時にすべてのアクティビティが削除されます"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"終了時にアクティビティを保存、削除できます"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"リセットして今すぐセッションのアクティビティを削除します(終了時にアクティビティを保存、削除することもできます)"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"写真を撮る"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"画像を選択"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"写真を選択"</string>
diff --git a/packages/SettingsLib/res/values-ka/strings.xml b/packages/SettingsLib/res/values-ka/strings.xml
index c6a50ac9e565..525a0d235fd2 100644
--- a/packages/SettingsLib/res/values-ka/strings.xml
+++ b/packages/SettingsLib/res/values-ka/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"გადაყენება"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ამოშლა"</string>
<string name="guest_resetting" msgid="7822120170191509566">"მიმდინარეობს სტუმრის გადაყენება…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"გსურთ სტუმრის სესიის გადაყენება?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ამ ქმედებით დაიწყება სტუმრის ახალი სესია და წაიშლება ყველა აპი და მონაცემი მიმდინარე სესიიდან"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"გსურთ სტუმრის რეჟიმიდან გასვლა?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ეს ქმედება წაშლის აპებსა და მონაცემებს სტუმრის რეჟიმის მიმდინარე სესიიდან"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"გასვლა"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"გსურთ სტუმრის აქტივობის შენახვა?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"შეგიძლიათ შეინახოთ აქტივობა მიმდინარე სესიიდან ან წაშალოთ ყველა აპი და მონაცემი"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"წაშლა"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"შენახვა"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"სტუმრის რეჟიმიდან გასვლა"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"სტუმრის სესიის გადაყენება"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"სტუმრის გასვლა"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"გასვლისას ყველა აქტივობა წაიშლება"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"გასვლისას შეგიძლიათ შეინახოთ ან წაშალოთ თქვენი აქტივობა"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"გადააყენეთ სესიის აქტივობის ახლავე წასაშლელად, ასევე, შეინახეთ ან წაშალეთ აქტივობა გასვლისას"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ფოტოს გადაღება"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"აირჩიეთ სურათი"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ფოტოს არჩევა"</string>
diff --git a/packages/SettingsLib/res/values-kk/strings.xml b/packages/SettingsLib/res/values-kk/strings.xml
index f5559d270f0a..692d82ff75c2 100644
--- a/packages/SettingsLib/res/values-kk/strings.xml
+++ b/packages/SettingsLib/res/values-kk/strings.xml
@@ -151,7 +151,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Бас тарту"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Жұптасқан кезде, контактілеріңіз бен қоңыраулар тарихын көру мүмкіндігі беріледі."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> жұпталу орындалмады."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысымен жұптаса алмады, себебі PIN немесе құпия сөз дұрыс емес."</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысымен жұптаса алмады, себебі PIN немесе рұқсат кілті дұрыс емес."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысымен қатынаса алмайды"</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> құрылғысы жұпталудан бас тартты."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Компьютер"</string>
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"Шектелген профайл"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"Жаңа пайдаланушы қосылсын ба?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"Қосымша профильдер жасай отырып, бұл құрылғыны басқалармен ортақ пайдалануға болады. Әр пайдаланушы қолданбаларды, тұсқағаздарды орнатып, профилін өз қалауынша реттей алады. Сондай-ақ барлығы ортақ қолданатын Wi‑Fi сияқты параметрлерді де реттеуге болады.\n\nЖаңа пайдаланушы енгізілгенде, ол өз профилін реттеуі керек болады.\n\nКез келген пайдаланушы барлық басқа пайдаланушылар үшін қолданбаларды жаңарта алады. Арнайы мүмкіндіктерге қатысты параметрлер мен қызметтер жаңа пайдаланушыға өтпейді."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"Жаңа пайдаланушыны қосқанда сол адам өз кеңістігін реттеуі керек.\n\nКез келген пайдаланушы барлық басқа пайдаланушылар үшін қолданбаларды жаңарта алады."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"Жаңа пайдаланушыны қосқанда, сол адам өз кеңістігін реттеуі керек.\n\nКез келген пайдаланушы барлық басқа пайдаланушылар үшін қолданбаларды жаңарта алады."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"Профиль құру керек пе?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"Пайдаланушы құрылғыны алып, өз профилін реттеуі керек."</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"Профайл қазір жасақталсын ба?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Бастапқы күйге қайтару"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Өшіру"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Қонақ сеансы бастапқы күйге қайтарылуда…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Қонақ сеансын бастапқы күйге қайтару керек пе?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Мұндайда жаңа қонақ сеансы басталады және ағымдағы сеанстағы барлық қолданба мен дерек жойылады."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Қонақ режимінен шығу керек пе?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Ағымдағы қонақ сеансындағы барлық қолданба мен дерек жойылады."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Шығу"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Қонақ әрекетін сақтау керек пе?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Ағымдағы сеанстағы әрекетті сақтай не барлық қолданба мен деректі жоя аласыз."</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Жою"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Сақтау"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Қонақ режимінен шығу"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Иә"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Қонақ режимінен шығу"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Шыққанда барлық әрекет жойылады."</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Шыққанда барлық әрекетті сақтай немесе жоя аласыз."</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Сеанс әрекетін қазір жою үшін оны бастапқы күйге қайтарыңыз. Әрекетті сақтау немесе жою шешімін шыққан кезде қабылдай аласыз."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Фотосуретке түсіру"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Сурет таңдау"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Фотосурет таңдау"</string>
@@ -655,12 +670,8 @@
<string name="bt_le_audio_scan_qr_code" msgid="3521809854780392679">"QR кодын сканерлеу"</string>
<string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"Тыңдай бастау үшін төмендегі QR кодын ортаға орналастырыңыз."</string>
<string name="bt_le_audio_qr_code_is_not_valid_format" msgid="6092191081849434734">"QR кодының форматы жарамсыз."</string>
- <!-- no translation found for bt_le_audio_broadcast_dialog_title (5392738488989777074) -->
- <skip />
- <!-- no translation found for bt_le_audio_broadcast_dialog_sub_title (268234802198852753) -->
- <skip />
- <!-- no translation found for bt_le_audio_broadcast_dialog_switch_app (5749813313369517812) -->
- <skip />
- <!-- no translation found for bt_le_audio_broadcast_dialog_different_output (2638402023060391333) -->
- <skip />
+ <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"<xliff:g id="APP_NAME">%1$s</xliff:g> қолданбасын таратуды тоқтатасыз ба?"</string>
+ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын таратсаңыз немесе аудио шығысын өзгертсеңіз, қазіргі тарату сеансы тоқтайды."</string>
+ <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"<xliff:g id="SWITCHAPP">%1$s</xliff:g> қолданбасын тарату"</string>
+ <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Аудио шығысын өзгерту"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-km/strings.xml b/packages/SettingsLib/res/values-km/strings.xml
index a770f61f2acf..4f36f697dbca 100644
--- a/packages/SettingsLib/res/values-km/strings.xml
+++ b/packages/SettingsLib/res/values-km/strings.xml
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"ប្រវត្តិ​បាន​ដាក់កម្រិត"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"បញ្ចូល​​អ្នកប្រើ​ប្រាស់​ថ្មី?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"អ្នកអាច​ចែករំលែក​ឧបករណ៍​នេះ​ជាមួយ​មនុស្ស​ផ្សេងទៀតបានដោយ​បង្កើត​អ្នកប្រើប្រាស់​បន្ថែម។ អ្នក​ប្រើប្រាស់​ម្នាក់ៗ​មាន​ទំហំផ្ទុក​ផ្ទាល់ខ្លួន​របស់​គេ ដែលពួកគេ​អាច​ប្ដូរតាម​បំណង​សម្រាប់​កម្មវិធី ផ្ទាំង​រូបភាព និង​អ្វីៗ​ផ្សេង​ទៀត។ អ្នក​ប្រើប្រាស់​ក៏អាច​កែសម្រួល​ការកំណត់​ឧបករណ៍​ដូចជា Wi‑Fi ដែល​ប៉ះពាល់​ដល់​អ្នកប្រើប្រាស់​ផ្សេង​ទៀត​ផងដែរ។\n\nនៅពេល​ដែលអ្នក​បញ្ចូល​អ្នកប្រើប្រាស់​ថ្មី បុគ្គល​នោះត្រូវតែ​រៀបចំទំហំ​ផ្ទុក​​របស់​គេ។\n\nអ្នកប្រើប្រាស់​ណាក៏​អាច​ដំឡើង​កំណែ​កម្មវិធី​សម្រាប់​អ្នក​ប្រើប្រាស់​ទាំងអស់​ផ្សេង​ទៀត​បាន​ដែរ។ ការកំណត់​ភាព​ងាយស្រួល និង​សេវាកម្ម​មិនអាច​ផ្ទេរទៅកាន់​អ្នកប្រើប្រាស់​ថ្មី​បានទេ។"</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"ពេល​អ្នក​បញ្ចូល​​អ្នកប្រើប្រាស់​​ថ្មី អ្នកប្រើ​ប្រាស់​នោះ​ត្រូវ​កំណត់​ទំហំ​ផ្ទាល់​របស់​គេ។\n\nអ្នកប្រើ​ប្រាស់​ណាក៏​​​អាច​ដំឡើងជំនាន់​​កម្មវិធី​សម្រាប់​អ្នកប្រើប្រាស់​​ផ្សេង​ទាំងអស់បានដែរ។"</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"នៅពេល​អ្នក​បញ្ចូល​​អ្នកប្រើប្រាស់​​ថ្មី អ្នកប្រើ​ប្រាស់​នោះ​ចាំបាច់ត្រូវ​រៀបចំលំហ​ផ្ទាល់​ខ្លួនរបស់​គាត់។\n\nអ្នកប្រើ​ប្រាស់​ណាក៏​​​អាច​ដំឡើងកំណែកម្មវិធី​សម្រាប់​អ្នកប្រើប្រាស់​​ទាំងអស់​ផ្សេងទៀតបានដែរ។"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"រៀបចំ​អ្នក​ប្រើ​ប្រាស់ឥឡូវនេះ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"សូម​ប្រាកដ​ថា​​អ្នក​ប្រើ​ប្រាស់នេះ​អាច​យក​​ឧបករណ៍ ​និង​រៀបចំ​​ទំហំ​ផ្ទុករបស់​គេបាន"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"រៀបចំ​ប្រវត្តិរូប​ឥឡូវ?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"កំណត់​ឡើងវិញ"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ដកចេញ"</string>
<string name="guest_resetting" msgid="7822120170191509566">"កំពុងកំណត់​ភ្ញៀវឡើងវិញ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"កំណត់វគ្គភ្ញៀវឡើងវិញឬ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ការធ្វើបែបនេះនឹងចាប់ផ្ដើមវគ្គភ្ញៀវថ្មី និងលុបកម្មវិធី និងទិន្នន័យទាំងអស់ចេញពីវគ្គបច្ចុប្បន្ន"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ចាកចេញពីមុខងារភ្ញៀវឬ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ការធ្វើបែបនេះនឹងលុបកម្មវិធី និងទិន្នន័យចេញពីវគ្គភ្ញៀវបច្ចុប្បន្ន"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ចាកចេញ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"រក្សាទុកសកម្មភាពភ្ញៀវឬ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"អ្នកអាចរក្សាទុកសកម្មភាពពីវគ្គបច្ចុប្បន្ន ឬលុបកម្មវិធីនិងទិន្នន័យទាំងអស់"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"លុប"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"រក្សាទុក"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ចាកចេញពីមុខងារភ្ញៀវ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"កំណត់វគ្គភ្ញៀវឡើងវិញ"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ចាកចេញពីមុខងារភ្ញៀវ"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"សកម្មភាពទាំងអស់នឹងត្រូវលុបនៅពេលចាកចេញ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"អ្នកអាចរក្សាទុក ឬលុបសកម្មភាពរបស់អ្នកនៅពេលចាកចេញ"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"កំណត់ឡើងវិញ ដើម្បីលុបសកម្មភាពក្នុងវគ្គឥឡូវនេះ ឬអ្នកអាចរក្សាទុកឬលុបសកម្មភាពនៅពេលចាកចេញ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ថតរូប"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ជ្រើសរើស​រូបភាព"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ជ្រើសរើស​​រូបថត"</string>
diff --git a/packages/SettingsLib/res/values-kn/strings.xml b/packages/SettingsLib/res/values-kn/strings.xml
index f4ee0a709e57..5e5221fee866 100644
--- a/packages/SettingsLib/res/values-kn/strings.xml
+++ b/packages/SettingsLib/res/values-kn/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ರೀಸೆಟ್ ಮಾಡಿ"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ತೆಗೆದುಹಾಕಿ"</string>
<string name="guest_resetting" msgid="7822120170191509566">"ಅತಿಥಿ ಬಳಕೆದಾರರ ಸೆಟ್ಟಿಂಗ್ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಲಾಗುತ್ತಿದೆ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ಅತಿಥಿ ಸೆಷನ್ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಬೇಕೇ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ಈ ಪ್ರಕ್ರಿಯೆಯು ಹೊಸ ಅತಿಥಿ ಸೆಶನ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸುತ್ತದೆ ಮತ್ತು ಪ್ರಸ್ತುತ ಸೆಶನ್‌ನಿಂದ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಹಾಗೂ ಡೇಟಾವನ್ನು ಅಳಿಸುತ್ತದೆ"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ಅತಿಥಿ ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಬೇಕೆ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ಈ ಪ್ರಕ್ರಿಯೆಯು ಪ್ರಸ್ತುತ ಅತಿಥಿ ಸೆಷನ್‌ನಿಂದ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸುತ್ತದೆ"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ನಿರ್ಗಮಿಸಿ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ಅತಿಥಿ ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬೇಕೆ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ನೀವು ಪ್ರಸ್ತುತ ಸೆಶನ್‌ನ ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬಹುದು ಅಥವಾ ಎಲ್ಲಾ ಆ್ಯಪ್‌ಗಳು ಮತ್ತು ಡೇಟಾವನ್ನು ಅಳಿಸಬಹುದು"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ಅಳಿಸಿ"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ಉಳಿಸಿ"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ಅತಿಥಿ ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ಅತಿಥಿ ಸೆಷನ್ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಿ"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ಅತಿಥಿ ಮೋಡ್‌ನಿಂದ ನಿರ್ಗಮಿಸಿ"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ನಿರ್ಗಮಿಸುವಾಗ ಎಲ್ಲಾ ಚಟುವಟಿಕೆಯನ್ನು ಅಳಿಸಲಾಗುತ್ತದೆ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ನಿರ್ಗಮಿಸುವಾಗ ನಿಮ್ಮ ಚಟುವಟಿಕೆಯನ್ನು ನೀವು ಉಳಿಸಬಹುದು ಅಥವಾ ಅಳಿಸಬಹುದು"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"ಸೆಶನ್ ಚಟುವಟಿಕೆಯನ್ನು ಈಗ ಅಳಿಸಲು ರೀಸೆಟ್ ಮಾಡಿ ಅಥವಾ ನಿರ್ಗಮಿಸುವಾಗ ನೀವು ಚಟುವಟಿಕೆಯನ್ನು ಉಳಿಸಬಹುದು ಅಥವಾ ಅಳಿಸಬಹುದು"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ಫೋಟೋ ತೆಗೆದುಕೊಳ್ಳಿ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ಚಿತ್ರವನ್ನು ಆರಿಸಿ"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ಫೋಟೋ ಆಯ್ಕೆಮಾಡಿ"</string>
diff --git a/packages/SettingsLib/res/values-ko/strings.xml b/packages/SettingsLib/res/values-ko/strings.xml
index 7a97242e9af5..f4889325dea3 100644
--- a/packages/SettingsLib/res/values-ko/strings.xml
+++ b/packages/SettingsLib/res/values-ko/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"재설정"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"삭제"</string>
<string name="guest_resetting" msgid="7822120170191509566">"게스트 재설정 중…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"게스트 세션을 재설정하시겠습니까?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"이렇게 하면 새로운 게스트 세션이 시작되고 기존 세션의 모든 앱과 데이터가 삭제됩니다."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"게스트 모드를 종료하시겠습니까?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"기존 게스트 세션의 앱과 데이터가 삭제됩니다."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"종료"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"게스트 활동을 저장하시겠습니까?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"기존 세션의 활동을 저장하거나 모든 앱과 데이터를 삭제할 수 있습니다."</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"삭제"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"저장"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"게스트 모드 종료"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"게스트 세션 재설정"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"게스트 종료"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"종료하면 모든 활동이 삭제됩니다."</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"종료 시 활동을 저장하거나 삭제할 수 있습니다."</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"지금 재설정하여 활동 세션을 삭제하거나 종료 시 활동을 저장 또는 삭제할 수 있습니다."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"사진 찍기"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"이미지 선택"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"사진 선택"</string>
diff --git a/packages/SettingsLib/res/values-ky/strings.xml b/packages/SettingsLib/res/values-ky/strings.xml
index 73830c51d3f2..aa94537121e1 100644
--- a/packages/SettingsLib/res/values-ky/strings.xml
+++ b/packages/SettingsLib/res/values-ky/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Баштапкы абалга келтирүү"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Өчүрүү"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Конок сеансы баштапкы абалга келтирилүүдө…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Конок сеансы баштапкы абалга келтирилсинби?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Бул аракет жаңы конок сеансын баштап, учурдагы сеанстагы бардык колдонмолорду жана дайындарды жок кылат"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Конок режиминен чыгасызбы?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Бул учурдагы конок сеансындагы колдонмолорду жана дайындарды жок кылат"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Чыгуу"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Коноктун аракеттери сакталсынбы?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Учурдагы сеанстагы аракеттерди сактап же бардык колдонмолорду жана дайындарды жок кылсаңыз болот"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Өчүрүү"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Сактоо"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Конок режиминен чыгуу"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Конок сеансын кайра коюу"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Конок режиминен чыгуу"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Чыксаңыз, бардык аракеттер өчүрүлөт"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Чыгуудан мурун аракеттериңизди сактап же жок кылсаңыз болот"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Сеанстагы аракеттерди азыр жок кылуу үчүн баштапкы абалга келтириңиз, же болбосо чыгуу учурунда аракеттерди сактап же жок кылсаңыз болот"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сүрөткө тартуу"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Сүрөт тандаңыз"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Сүрөт тандаңыз"</string>
diff --git a/packages/SettingsLib/res/values-lo/strings.xml b/packages/SettingsLib/res/values-lo/strings.xml
index 5c0f1921b665..5f42f5c0a3f1 100644
--- a/packages/SettingsLib/res/values-lo/strings.xml
+++ b/packages/SettingsLib/res/values-lo/strings.xml
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"ໂປຣໄຟລ໌ທີ່ຖືກຈຳກັດ"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"ເພີ່ມຜູ້ໃຊ້ໃໝ່ບໍ?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"ທ່ານສາມາດໃຊ້ອຸປະກອນນີ້ຮ່ວມກັບຄົນອື່ນໄດ້ໂດຍການສ້າງຜູ້ໃຊ້ເພີ່ມເຕີມ. ຜູ້ໃຊ້ແຕ່ລະຄົນຈະມີພື້ນທີ່ຂອງຕົວເອງ, ເຊິ່ງເຂົາເຈົ້າສາມາດປັບແຕ່ງແອັບ, ຮູບພື້ນຫຼັງ ແລະ ອື່ນໆໄດ້. ຜູ້ໃຊ້ຕ່າງໆ ສາມາດປັບແຕ່ງການຕັ້ງຄ່າອຸປະກອນໄດ້ ເຊັ່ນ: Wi‑Fi ທີ່ມີຜົນກະທົບທຸກຄົນ.\n\nເມື່ອທ່ານເພີ່ມຜູ້ໃຊ້ໃໝ່, ບຸກຄົນນັ້ນຈະຕ້ອງຕັ້ງຄ່າພື້ນທີ່ຂອງເຂົາເຈົ້າກ່ອນ.\n\nຜູ້ໃຊ້ໃດກໍຕາມສາມາດອັບເດດແອັບສຳລັບຜູ້ໃຊ້ຄົນອື່ນທັງໝົດໄດ້. ການຕັ້ງຄ່າການຊ່ວຍເຂົ້າເຖິງອາດບໍ່ຖືກໂອນຍ້າຍໄປໃຫ້ຜູ້ໃຊ້ໃໝ່."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"ເມື່ອ​ທ່ານ​ເພີ່ມ​ຜູ້​ໃຊ້​ໃໝ່, ຜູ້​ໃຊ້​ນັ້ນ​ຈະ​ຕ້ອງ​ຕັ້ງ​ຄ່າ​ພື້ນ​ທີ່​ບ່ອນ​ຈັດ​ເກັບ​ຂໍ້​ມູນ​ຂອງ​ລາວ.\n\nຜູ້​ໃຊ້​ທຸກ​ຄົນ​ສາ​ມາດ​ອັບ​ເດດ​ແອັບຯສຳ​ລັບ​ຜູ້​ໃຊ້​ຄົນ​ອື່ນ​ທັງ​ໝົດ​ໄດ້."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"ເມື່ອ​ທ່ານ​ເພີ່ມ​ຜູ້​ໃຊ້​ໃໝ່, ຜູ້​ໃຊ້​ນັ້ນ​ຈະ​ຕ້ອງ​ຕັ້ງ​ຄ່າ​ພື້ນ​ທີ່​ບ່ອນ​ຈັດ​ເກັບ​ຂໍ້​ມູນ​ຂອງ​ລາວ.\n\nຜູ້​ໃຊ້​ທຸກ​ຄົນ​ສາ​ມາດ​ອັບ​ເດດ​ແອັບສຳ​ລັບ​ຜູ້​ໃຊ້​ຄົນ​ອື່ນ​ທັງ​ໝົດ​ໄດ້."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ຕັ້ງຄ່າຜູ້ໃຊ້ຕອນນີ້ບໍ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ກວດ​ສອບ​ໃຫ້​ແນ່​ໃຈ​ວ່າ​ບຸກ​ຄົນ​ດັ່ງ​ກ່າວ​ສາ​ມາດ​ຮັບ​ອຸ​ປະ​ກອນ​ ແລະ ​ຕັ້ງ​ຄ່າ​ພື້ນ​ທີ່​ຂອງ​ພວກ​ເຂົາ​ໄດ້"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"ຕັ້ງຄ່າໂປຣໄຟລ໌ດຽວນີ້?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ຣີເຊັດ"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ລຶບອອກ"</string>
<string name="guest_resetting" msgid="7822120170191509566">"ກຳລັງຣີເຊັດແຂກ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ຣີເຊັດໄລຍະເວລາຂອງແຂກບໍ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ນີ້ຈະເລີ່ມໄລຍະເວລາຂອງແຂກໃໝ່ ແລະ ລຶບແອັບ ແລະ ຂໍ້ມູນທັງໝົດອອກຈາກເຊດຊັນປັດຈຸບັນ"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ອອກຈາກໂໝດແຂກບໍ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ນີ້ຈະລຶບແອັບ ແລະ ຂໍ້ມູນອອກຈາກໄລຍະເວລາຂອງແຂກປັດຈຸບັນ"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ອອກ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ບັນທຶກການເຄື່ອນໄຫວແຂກບໍ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ທ່ານສາມາດບັນທຶກການເຄື່ອນໄຫວຈາກເຊດຊັນປັດຈຸບັນ ຫຼື ລຶບແອັບ ແລະ ຂໍ້ມູນທັງໝົດໄດ້"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ລຶບ"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ບັນທຶກ"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ອອກຈາກໂໝດແຂກ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ຣີເຊັດໄລຍະເວລາຂອງແຂກ"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ອອກຈາກແຂກ"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ການເຄື່ອນໄຫວທັງໝົດຈະຖືກລຶບໃນຕອນອອກ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ທ່ານສາມາດບັນທຶກ ຫຼື ລຶບການເຄື່ອນໄຫວຂອງທ່ານໃນຕອນອອກໄດ້"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"ຣີເຊັດເພື່ອລຶບການເຄື່ອນໄຫວເຊດຊັນຕອນນີ້ ຫຼື ທ່ານສາມາດບັນທຶກ ຫຼື ລຶບການເຄື່ອນໄຫວໃນຕອນອອກໄດ້"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ຖ່າຍຮູບ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ເລືອກຮູບ"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ເລືອກຮູບ"</string>
diff --git a/packages/SettingsLib/res/values-lt/strings.xml b/packages/SettingsLib/res/values-lt/strings.xml
index 250d24af0d38..a161750e2098 100644
--- a/packages/SettingsLib/res/values-lt/strings.xml
+++ b/packages/SettingsLib/res/values-lt/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Nustatyti iš naujo"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Pašalinti"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Svečias nustatomas iš naujo…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Nustatyti svečio sesiją iš naujo?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Bus pradėta nauja svečio sesija ir iš esamos sesijos bus ištrintos visos programos ir duomenys"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Išeiti iš svečio režimo?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Bus ištrintos esamos svečio sesijos programos ir duomenys"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Išeiti"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Išsaugoti svečio veiklą?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Galite išsaugoti esamos sesijos veiklą arba ištrinti visas programas ir duomenis"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Ištrinti"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Išsaugoti"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Išeiti iš svečio režimo"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Nustatyti svečio sesiją iš naujo"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Išeiti iš svečio režimo"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Išėjus iš režimo visa veikla bus ištrinta"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Išeidami galite išsaugoti arba ištrinti savo veiklą"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Nustatykite iš naujo, jei norite ištrinti sesijos veiklą dabar, arba galite išeidami išsaugoti ar ištrinti veiklą"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotografuoti"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pasirinkti vaizdą"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Pasirinkti nuotrauką"</string>
diff --git a/packages/SettingsLib/res/values-lv/strings.xml b/packages/SettingsLib/res/values-lv/strings.xml
index f72ba8e996fe..929b857950ff 100644
--- a/packages/SettingsLib/res/values-lv/strings.xml
+++ b/packages/SettingsLib/res/values-lv/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Atiestatīt"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Noņemt"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Notiek viesa sesijas atiestatīšana…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Vai atiestatīt viesa sesiju?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Tādējādi tiks sākta jauna viesa sesijas un visas pašreizējās sesijas lietotnes un dati tiks dzēsti"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Vai iziet no viesa režīma?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Tādējādi tiks dzēstas pašreizējās viesa sesijas lietotnes un dati."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Iziet"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Vai saglabāt viesa darbības?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Varat saglabāt pašreizējās sesijas darbības vai dzēst visas lietotnes un datus"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Dzēst"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Saglabāt"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Iziet no viesa režīma"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Atiestatīt viesa sesiju"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Iziet no viesa režīma"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Izejot tiks dzēstas visas darbības"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Izejot varat saglabāt vai dzēst savas darbības"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Atiestatiet, lai tagad dzēstu sesijas darbības. Izejot varēsiet saglabāt vai dzēst darbības."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Uzņemt fotoattēlu"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Izvēlēties attēlu"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Atlasīt fotoattēlu"</string>
diff --git a/packages/SettingsLib/res/values-mk/strings.xml b/packages/SettingsLib/res/values-mk/strings.xml
index 376452c78d4a..9a286913372c 100644
--- a/packages/SettingsLib/res/values-mk/strings.xml
+++ b/packages/SettingsLib/res/values-mk/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Ресетирај"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Отстрани"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Се ресетира гостинот…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Да се ресетира гостинската сесија?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Ова ќе започне нова гостинска сесија и ќе ги избрише сите апликации и податоци од тековната сесија"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Да се излезе од режим на гостин?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Ова ќе ги избрише сите апликации и податоци од тековната гостинска сесија"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Излези"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Да се зачува активност на гостин?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Може да зачувате активност од тековната сесија или да ги избришете сите апликации и податоци"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Избриши"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Зачувај"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Излези од режим на гостин"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Ресетирај ја гостинската сесија"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Излези од режим на гостин"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Целата активност ќе се избрише при излегувањето"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Можете да ја зачувате или избришете вашата активност при излегувањето"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Ресетирајте за да ја избришете активноста на сесијата сега или може да ја зачувате или избришете активноста при излегувањето"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Фотографирајте"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Одберете слика"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Изберете фотографија"</string>
diff --git a/packages/SettingsLib/res/values-ml/strings.xml b/packages/SettingsLib/res/values-ml/strings.xml
index e69d25f65c70..a38daced13d2 100644
--- a/packages/SettingsLib/res/values-ml/strings.xml
+++ b/packages/SettingsLib/res/values-ml/strings.xml
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"നിയന്ത്രിത പ്രൊഫൈൽ"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"പുതിയ ഉപയോക്താവിനെ ചേർക്കണോ?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"കൂടുതൽ ഉപയോക്താക്കളെ സൃഷ്‌ടിച്ചുകൊണ്ട് ഈ ഉപകരണം മറ്റുള്ളവരുമായി നിങ്ങൾക്ക് പങ്കിടാം. ആപ്പുകളും വാൾപേപ്പറുകളും മറ്റും ഉപയോഗിച്ച് ഇഷ്‌ടാനുസൃതമാക്കാൻ ഓരോ ഉപയോക്താവിനും സാധിക്കും. വൈഫൈ പോലെ എല്ലാവരെയും ബാധിക്കുന്ന ഉപകരണ ക്രമീകരണവും ഉപയോക്താക്കൾക്ക് ക്രമീകരിക്കാം.\n\nനിങ്ങളൊരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിക്ക് സ്വന്തമായ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\n എല്ലാ ഉപയോക്താക്കൾക്കുമായി ആപ്പുകൾ അപ്‌ഡേറ്റ് ചെയ്യാൻ ഏതൊരു ഉപയോക്താവിനുമാവും. ഉപയോഗസഹായി ക്രമീകരണവും സേവനങ്ങളും പുതിയ ഉപയോക്താവിന് കൈമാറുകയില്ല."</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"നിങ്ങൾ ഒരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിയ്‌ക്ക് അവരുടെ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\nമറ്റ് എല്ലാ ഉപയോക്താക്കൾക്കുമായി ഏതൊരു ഉപയോക്താവിനും അപ്ലിക്കേഷനുകൾ അപ്‌ഡേറ്റുചെയ്യാനാവും."</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"നിങ്ങൾ ഒരു പുതിയ ഉപയോക്താവിനെ ചേർക്കുമ്പോൾ, ആ വ്യക്തിയ്‌ക്ക് അവരുടെ ഇടം സജ്ജീകരിക്കേണ്ടതുണ്ട്.\n\nമറ്റ് എല്ലാ ഉപയോക്താക്കൾക്കുമായി ഏതൊരു ഉപയോക്താവിനും ആപ്പുകൾ അപ്‌ഡേറ്റ് ചെയ്യാനാവും."</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ഉപയോക്താവിനെ ഇപ്പോൾ സജ്ജീകരിക്കണോ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ഉപകരണം എടുത്ത് ഇടം സജ്ജീകരിക്കുന്നതിന് വ്യക്തി ലഭ്യമാണെന്ന് ഉറപ്പാക്കുക"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"ഇപ്പോൾ പ്രൊഫൈൽ സജ്ജീകരിക്കണോ?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"റീസെറ്റ് ചെയ്യുക"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"നീക്കം ചെയ്യുക"</string>
<string name="guest_resetting" msgid="7822120170191509566">"അതിഥിയെ റീസെറ്റ് ചെയ്യുന്നു…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"അതിഥി സെഷൻ റീസെറ്റ് ചെയ്യണോ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ഇത് പുതിയൊരു അതിഥി സെഷൻ ആരംഭിക്കുകയും നിലവിലെ സെഷനിൽ നിന്ന് എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കുകയും ചെയ്യും"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"അതിഥി മോഡിൽ നിന്ന് പുറത്തുകടക്കണോ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"നിലവിലെ അതിഥി സെഷനിൽ നിന്ന് ആപ്പുകളും ഡാറ്റയും ഇത് ഇല്ലാതാക്കും"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"പുറത്തുകടക്കുക"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"അതിഥി ആക്‌റ്റിവിറ്റി സംരക്ഷിക്കണോ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"നിലവിലെ സെഷനിൽ നിന്നുള്ള ആക്‌റ്റിവിറ്റി സംരക്ഷിക്കാം അല്ലെങ്കിൽ എല്ലാ ആപ്പുകളും ഡാറ്റയും ഇല്ലാതാക്കാം"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ഇല്ലാതാക്കുക"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"സംരക്ഷിക്കുക"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"അതിഥി മോഡിൽ നിന്ന് പുറത്തുകടക്കുക"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"അതിഥി സെഷൻ റീസെറ്റ് ചെയ്യുക"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"അതിഥി മോഡിൽ നിന്ന് പുറത്തുകടക്കുക"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"പുറത്തുകടക്കുമ്പോൾ എല്ലാ ആക്‌റ്റിവിറ്റിയും ഇല്ലാതാക്കും"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"പുറത്തുകടക്കുമ്പോൾ ആക്‌റ്റിവിറ്റി സംരക്ഷിക്കുകയോ ഇല്ലാതാക്കുകയോ ചെയ്യാം"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"സെഷൻ ആക്‌റ്റിവിറ്റി ഇപ്പോൾ ഇല്ലാതാക്കാൻ റീസെറ്റ് ചെയ്യുക അല്ലെങ്കിൽ പുറത്തുകടക്കുമ്പോൾ ആക്‌റ്റിവിറ്റി സംരക്ഷിക്കുകയോ ഇല്ലാതാക്കുകയോ ചെയ്യാം"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ഒരു ഫോട്ടോ എടുക്കുക"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ഒരു ചിത്രം തിരഞ്ഞെടുക്കുക"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ഫോട്ടോ തിരഞ്ഞെടുക്കുക"</string>
diff --git a/packages/SettingsLib/res/values-mn/strings.xml b/packages/SettingsLib/res/values-mn/strings.xml
index cd41b81381d1..5eef7d386cc1 100644
--- a/packages/SettingsLib/res/values-mn/strings.xml
+++ b/packages/SettingsLib/res/values-mn/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Шинэчлэх"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Хасах"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Зочныг шинэчилж байна…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Зочны харилцан үйлдлийг шинэчлэх үү?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Энэ нь шинэ зочны харилцан үйлдэл эхлүүлж, одоогийн харилцан үйлдлээс бүх апп болон өгөгдлийг устгана"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Зочны горимоос гарах уу?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Энэ нь одоогийн зочны харилцан үйлдлээс аппууд болон өгөгдлийг устгана"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Гарах"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Зочны үйл ажиллагааг хадгалах уу?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Та одоогийн харилцан үйлдлээс үйл ажиллагаа хадгалах эсвэл бүх апп, өгөгдлийг устгаж болно"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Устгах"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Хадгалах"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Зочны горимоос гарах"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Зочны харилцан үйлдлийг шинэчлэх"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Зочны горимоос гарах"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Бүх үйл ажиллагааг гарах үед устгана"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Та гарахдаа үйл ажиллагаагаа хадгалах эсвэл устгах боломжтой"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Харилцан үйлдлийн үйл ажиллагааг одоо устгахын тулд шинэчлэх эсвэл та гарахдаа үйл ажиллагааг хадгалах, устгах боломжтой"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Зураг авах"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Зураг сонгох"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Зураг сонгох"</string>
diff --git a/packages/SettingsLib/res/values-mr/strings.xml b/packages/SettingsLib/res/values-mr/strings.xml
index 5c3578cea23d..1611961f18c9 100644
--- a/packages/SettingsLib/res/values-mr/strings.xml
+++ b/packages/SettingsLib/res/values-mr/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"रीसेट करा"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"काढून टाका"</string>
<string name="guest_resetting" msgid="7822120170191509566">"अतिथीला रीसेट करत आहे…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"अतिथी सत्र रीसेट करायचे का?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"हे नवीन अतिथी सत्र सुरू करेल आणि सध्याच्या सत्रातील सर्व अ‍ॅप्स व डेटा हटवेल"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"अतिथी मोडमधून बाहेर पडायचे का?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"हे सध्याच्या अतिथी सत्रातील अ‍ॅप्स आणि डेटा हटवेल"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"बाहेर पडा"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"अतिथी अ‍ॅक्टिव्हिटी सेव्ह करायची का?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"सध्याच्या सत्रातील अ‍ॅक्टिव्हिटी सेव्ह करू किंवा सर्व अ‍ॅप्स व डेटा हटवू शकता"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"हटवा"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"सेव्ह करा"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"अतिथी मोडमधून बाहेर पडा"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"अतिथी सत्र रीसेट करा"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"अतिथी मोडमधून बाहेर पडा"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"बाहेर पडल्यावर सर्व अ‍ॅक्टिव्हिटी हटवली जाईल"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"बाहेर पडल्यावर तुमची अ‍ॅक्टिव्हिटी सेव्ह करू किंवा हटवू शकता"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"सत्र अ‍ॅक्टिव्हिटी आता हटवण्यासाठी रीसेट करा किंवा तुम्ही बाहेर पडल्यावर अ‍ॅक्टिव्हिटी सेव्ह करू अथवा हटवू शकता"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"फोटो काढा"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"इमेज निवडा"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"फोटो निवडा"</string>
diff --git a/packages/SettingsLib/res/values-ms/strings.xml b/packages/SettingsLib/res/values-ms/strings.xml
index 8a2fc2b0f450..79bb7a712e2b 100644
--- a/packages/SettingsLib/res/values-ms/strings.xml
+++ b/packages/SettingsLib/res/values-ms/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Tetapkan semula"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Alih keluar"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Menetapkan semula tetamu…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Tetapkan semula sesi tetamu?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Tindakan ini akan memulakan sesi tetamu baharu dan memadamkan semua apl dan data daripada sesi semasa"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Keluar daripada mod tetamu?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Tindakan ini akan memadamkan apl dan data daripada sesi tetamu semasa"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Keluar"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Simpan aktiviti tetamu?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Anda boleh menyimpan aktiviti daripada sesi semasa atau memadamkan semua apl dan data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Padam"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Simpan"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Keluar daripada mod tetamu"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Tetapkan semula sesi tetamu"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Keluar mod tetamu"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Semua aktiviti akan dipadamkan semasa keluar"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Aktiviti anda boleh disimpan atau dipadam semasa keluar"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Tetapkan semula sesi untuk memadamkan aktiviti sesi sekarang atau anda boleh menyimpan atau memadamkan aktiviti semasa keluar"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ambil foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pilih imej"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Pilih foto"</string>
diff --git a/packages/SettingsLib/res/values-my/strings.xml b/packages/SettingsLib/res/values-my/strings.xml
index c0aafc490564..a4325315291a 100644
--- a/packages/SettingsLib/res/values-my/strings.xml
+++ b/packages/SettingsLib/res/values-my/strings.xml
@@ -599,6 +599,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ပြင်ဆင်သတ်မှတ်ရန်"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ဖယ်ရှားရန်"</string>
<string name="guest_resetting" msgid="7822120170191509566">"ဧည့်သည်ကို ပြင်ဆင်သတ်မှတ်နေသည်…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ဧည့်သည် စက်ရှင် ပြင်ဆင်သတ်မှတ်မလား။"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"၎င်းသည် ဧည့်သည် စက်ရှင်အသစ်ကို စတင်မည်ဖြစ်ပြီး လက်ရှိစက်ရှင်မှ အက်ပ်နှင့် ဒေတာအားလုံးကို ဖျက်ပါမည်"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ဧည့်သည်မုဒ်မှ ထွက်မလား။"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"၎င်းသည် လက်ရှိဧည့်သည် စက်ရှင်မှ အက်ပ်နှင့် ဒေတာအားလုံးကို ဖျက်လိုက်ပါမည်"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ထွက်ရန်"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ဧည့်သည်လုပ်ဆောင်ချက် သိမ်းမလား။"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"လက်ရှိစက်ရှင်မှ လုပ်ဆောင်ချက် သိမ်းနိုင်သည် (သို့) အက်ပ်နှင့် ဒေတာအားလုံး ဖျက်နိုင်သည်"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ဖျက်ရန်"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"သိမ်းရန်"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ဧည့်သည်မုဒ်မှ ထွက်ရန်"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ဧည့်သည် စက်ရှင် ပြင်ဆင်သတ်မှတ်ရန်"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ဧည့်သည့်မှ ထွက်ရန်"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ထွက်သည့်အခါ လုပ်ဆောင်ချက်အားလုံးကို ဖျက်လိုက်မည်"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ထွက်သည့်အခါ လုပ်ဆောင်ချက်ကို သိမ်းနိုင် (သို့) ဖျက်နိုင်သည်"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"စက်ရှင်လုပ်ဆောင်ချက်ကို ယခုဖျက်ရန် ပြင်ဆင်သတ်မှတ်နိုင်သည် (သို့) ထွက်သည့်အခါ လုပ်ဆောင်ချက်ကို သိမ်းနိုင် (သို့) ဖျက်နိုင်သည်"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ဓာတ်ပုံရိုက်ရန်"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ပုံရွေးရန်"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ဓာတ်ပုံရွေးရန်"</string>
diff --git a/packages/SettingsLib/res/values-nb/strings.xml b/packages/SettingsLib/res/values-nb/strings.xml
index 4f64d104a809..013737ca6234 100644
--- a/packages/SettingsLib/res/values-nb/strings.xml
+++ b/packages/SettingsLib/res/values-nb/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Tilbakestill"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Fjern"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Tilbakestiller gjesten …"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Vil du tilbakestille gjesteøkten?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Dette starter en ny gjesteøkt og sletter alle apper og data fra den gjeldende økten"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Vil du avslutte gjestemodus?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Dette sletter apper og data fra den gjeldende gjesteøkten"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Avslutt"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Vil du lagre gjesteaktivitet?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Du kan lagre aktivitet fra den gjeldende økten eller slette alle apper og data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Slett"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Lagre"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Avslutt gjestemodus"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Tilbakestill gjesteøkten"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Avslutt gjesteøkten"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"All aktivitet slettes når du avslutter"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Du kan lagre eller slette aktiviteten når du avslutter"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Tilbakestill for å slette øktaktivitet nå, eller du kan lagre eller slette aktivitet når du avslutter"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ta et bilde"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Velg et bilde"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Velg et bilde"</string>
diff --git a/packages/SettingsLib/res/values-ne/strings.xml b/packages/SettingsLib/res/values-ne/strings.xml
index 30401ace32ed..90fb7980e548 100644
--- a/packages/SettingsLib/res/values-ne/strings.xml
+++ b/packages/SettingsLib/res/values-ne/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"जडान हटाइँदै ..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"जडान हुँदै..."</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> सँग जडान गरियो"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"जोडा बाँध्दै..."</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"कनेक्ट गरिँदै छ..."</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"जडान गरियो (फोनबाहेेक) <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"जडान गरियो (मिडियाबाहेक) <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"जडान गरियो (सन्देशमाथि पहुँच छैन) <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -117,7 +117,7 @@
<string name="bluetooth_profile_opp" msgid="6692618568149493430">"फाइल स्थानान्तरण"</string>
<string name="bluetooth_profile_hid" msgid="2969922922664315866">"इनपुट उपकरण"</string>
<string name="bluetooth_profile_pan" msgid="1006235139308318188">"इन्टरनेट पहुँच"</string>
- <string name="bluetooth_profile_pbap" msgid="7064307749579335765">"सम्पर्क ठेगानाको सेयरिङ"</string>
+ <string name="bluetooth_profile_pbap" msgid="7064307749579335765">"कन्ट्याक्ट सेयरिङ"</string>
<string name="bluetooth_profile_pbap_summary" msgid="2955819694801952056">"सम्पर्क साझेदारीका लागि प्रयोग"</string>
<string name="bluetooth_profile_pan_nap" msgid="7871974753822470050">"इन्टरनेट जडान साझेदारी गर्दै"</string>
<string name="bluetooth_profile_map" msgid="8907204701162107271">"टेक्स्ट म्यासेजहरू"</string>
@@ -591,13 +591,28 @@
<string name="user_nickname" msgid="262624187455825083">"उपनाम"</string>
<string name="user_add_user" msgid="7876449291500212468">"प्रयोगकर्ता थप्नुहोस्"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"अतिथि थप्नुहोस्"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"अतिथि हटाउनुहोस्"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"गेस्ट मोडबाट बाहिर निस्कियोस्"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"अतिथि सत्र रिसेट गर्नुहोस्"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"अतिथिका रूपमा ब्राउज गर्ने सेसन रिसेट गर्ने हो?"</string>
<string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"यी अतिथि हटाउने हो?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"रिसेट गर्नुहोस्"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"हटाउनुहोस्"</string>
<string name="guest_resetting" msgid="7822120170191509566">"अतिथिका रूपमा ब्राउज गर्ने सेसन रिसेट गरिँदै छ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"अतिथि सत्र रिसेट गर्ने हो?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"यसो गर्नाले नयाँ अतिथि सत्र सुरु हुने छ र हालको अतिथि सत्रका सबै एप तथा डेटा मेटिने छ"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"अतिथि मोडबाट बाहिरिने हो?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"यसो गर्नाले हालको अतिथि सत्रका एप तथा डेटा मेटिने छ"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"बाहिरिनुहोस्"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"अतिथि सत्रमा गरिएका क्रियाकलाप सेभ गर्ने हो?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"तपाईं हालको सत्रमा गरिएका क्रियाकलाप सेभ गर्न वा सबै एप तथा डेटा मेटाउन सक्नुहुन्छ"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"मेटाउनुहोस्"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"सेभ गर्नुहोस्"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"अतिथि मोडबाट बाहिरिनुहोस्"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"अतिथि सत्र रिसेट गर्नुहोस्"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"अतिथि मोडबाट बाहिरिनुहोस्"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"अतिथि मोडबाट बाहिरिँदा सबै क्रियाकलाप मेटाइने छ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"तपाईं अतिथि मोडबाट बाहिरिँदा आफ्ना क्रियाकलाप सेभ गर्ने वा मेटाउने विकल्प रोज्न सक्नुहुन्छ"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"यो सत्रमा गरिएका क्रियाकलाप अहिले नै मेटाउन रिसेट गर्नुहोस्, अथवा तपाईं अतिथि मोडबाट बाहिरिँदा आफ्ना क्रियाकलाप सेभ गर्ने वा मेटाउने विकल्प रोज्न सक्नुहुन्छ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"फोटो खिच्नुहोस्"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"कुनै फोटो छनौट गर्नुहोस्"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"फोटो चयन गर्नुहोस्"</string>
diff --git a/packages/SettingsLib/res/values-nl/strings.xml b/packages/SettingsLib/res/values-nl/strings.xml
index c5065cb364bb..20fe50282087 100644
--- a/packages/SettingsLib/res/values-nl/strings.xml
+++ b/packages/SettingsLib/res/values-nl/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetten"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Verwijderen"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Gast resetten…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Gastsessie resetten?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hierdoor wordt een nieuwe gastsessie gestart en worden alle apps en gegevens van de huidige sessie verwijderd"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Gastmodus sluiten?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Hierdoor worden apps en gegevens van de huidige gastsessie verwijderd"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Sluiten"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Gastactiviteit opslaan?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Sla activiteit van de huidige sessie op of verwijder alle apps en gegevens"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Verwijderen"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Opslaan"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Gastmodus sluiten"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Gastsessie resetten"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Gastmodus verlaten"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Alle activiteit wordt na het afsluiten verwijderd"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Je kunt je activiteit bij afsluiten opslaan of verwijderen"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Voer een reset uit om de sessie-activiteit nu te verwijderen of verwijder of sla je activiteit op bij afsluiten"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Foto maken"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Afbeelding kiezen"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Foto selecteren"</string>
diff --git a/packages/SettingsLib/res/values-or/strings.xml b/packages/SettingsLib/res/values-or/strings.xml
index 337dd79735f6..5e275a5aa43d 100644
--- a/packages/SettingsLib/res/values-or/strings.xml
+++ b/packages/SettingsLib/res/values-or/strings.xml
@@ -570,7 +570,7 @@
<string name="user_add_profile_item_title" msgid="3111051717414643029">"ସୀମିତ ସୁବିଧା ଥିବା ପ୍ରୋଫାଇଲ୍‌"</string>
<string name="user_add_user_title" msgid="5457079143694924885">"ନୂତନ ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ଯୋଗ କରିବେ?"</string>
<string name="user_add_user_message_long" msgid="1527434966294733380">"ଅତିରିକ୍ତ ଉପଯୋଗକର୍ତ୍ତା ଯୋଗ କରି ଆପଣ ଏହି ଡିଭାଇସକୁ ଅନ୍ୟ ଲୋକମାନଙ୍କ ସହିତ ସେୟାର କରିପାରିବେ। ପ୍ରତ୍ୟେକ ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ନିଜର ସ୍ପେସ୍ ଅଛି ଯାହାକୁ ସେମାନେ ଆପ, ୱାଲପେପର୍ ଓ ଏପରି ଅନେକ କିଛି ସହିତ କଷ୍ଟମାଇଜ କରିପାରିବେ। ଉପଯୋଗକର୍ତ୍ତା ୱାଇ-ଫାଇ ଭଳି ଡିଭାଇସ ସେଟିଂସକୁ ମଧ୍ୟ ଆଡଜଷ୍ଟ କରିପାରିବେ ଯାହା ସମସ୍ତଙ୍କୁ ପ୍ରଭାବିତ କରିଥାଏ। \n\nଯେତେବେଳେ ଆପଣ ଗୋଟିଏ ନୂଆ ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ଯୋଗ କରିବେ, ସେତେବେଳେ ସେହି ବ୍ୟକ୍ତିଙ୍କୁ ନିଜର ସ୍ପେସ୍‌କୁ ସେଟ‌ଅପ କରିବାକୁ ପଡ଼ିବ। \n\nଅନ୍ୟ ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ପାଇଁ ଯେ କୌଣସି ଉପଯୋଗକର୍ତ୍ତା ଆପକୁ ଅପଡେଟ କରିପାରିବେ। ଆକ୍ସେସିବିଲିଟୀ ସେଟିଂସ ଏବଂ ସେବାଗୁଡ଼ିକ ନୂଆ ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ସ୍ଥାନାନ୍ତର ହୋ‌ଇନପାରେ।"</string>
- <string name="user_add_user_message_short" msgid="3295959985795716166">"ଜଣେ ନୂଆ ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ଯୋଡ଼ିବାବେଳେ, ସେହି ବ୍ୟକ୍ତିଙ୍କୁ ସ୍ଥାନ ସେଟ୍‍ କରିବାକୁ ପଡ଼ିବ।\n\nଅନ୍ୟ ସମସ୍ତ ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ପାଇଁ ଯେକୌଣସି ଉପଯୋଗକର୍ତ୍ତା ଆପ୍‌ଗୁଡ଼ିକୁ ଅପ୍‌ଡେଟ୍‌ କରିପାରିବେ।"</string>
+ <string name="user_add_user_message_short" msgid="3295959985795716166">"ଆପଣ ଜଣେ ନୂଆ ଉପଯୋଗକର୍ତ୍ତାଙ୍କୁ ଯୋଗ କରିବା ବେଳେ, ସେହି ବ୍ୟକ୍ତିଙ୍କୁ ତାଙ୍କ ସ୍ପେସ ସେଟ ଅପ କରିବାକୁ ପଡ଼ିବ।\n\nଅନ୍ୟ ସମସ୍ତ ଉପଯୋଗକର୍ତ୍ତାଙ୍କ ପାଇଁ, ଆପଗୁଡ଼ିକୁ ଯେ କୌଣସି ଉପଯୋଗକର୍ତ୍ତା ଅପଡେଟ କରିପାରିବେ।"</string>
<string name="user_setup_dialog_title" msgid="8037342066381939995">"ଏବେ ଉପଯୋଗକର୍ତ୍ତା ସେଟଅପ କରିବେ?"</string>
<string name="user_setup_dialog_message" msgid="269931619868102841">"ସୁନିଶ୍ଚିତ କରନ୍ତୁ ଯେ, ବ୍ୟକ୍ତି ଜଣକ ଡିଭାଇସ୍‌ ଓ ନିଜର ସ୍ଥାନ ସେଟଅପ୍‌ କରିବା ପାଇଁ ଉପଲବ୍ଧ ଅଛନ୍ତି।"</string>
<string name="user_setup_profile_dialog_message" msgid="4788197052296962620">"ପ୍ରୋଫାଇଲ୍‌କୁ ଏବେ ସେଟ୍‌ କରିବେ?"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ରିସେଟ୍ କରନ୍ତୁ"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"କାଢ଼ି ଦିଅନ୍ତୁ"</string>
<string name="guest_resetting" msgid="7822120170191509566">"ଅତିଥି ସେସନକୁ ରିସେଟ୍ କରାଯାଉଛି…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ଅତିଥି ସେସନକୁ ରିସେଟ କରିବେ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ଏହା ଏକ ନୂଆ ଅତିଥି ସେସନ ଆରମ୍ଭ କରିବ ଏବଂ ବର୍ତ୍ତମାନର ସେସନରୁ ସମସ୍ତ ଆପ୍ସ ଏବଂ ଡାଟାକୁ ଡିଲିଟ କରିବ"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ଅତିଥି ମୋଡରୁ ବାହାରି ଯିବେ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ଏହା ବର୍ତ୍ତମାନର ଅତିଥି ସେସନରୁ ଆପ୍ସ ଏବଂ ଡାଟାକୁ ଡିଲିଟ କରିବ"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ବାହାରି ଯାଆନ୍ତୁ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ଅତିଥି କାର୍ଯ୍ୟକଳାପ ସେଭ କରିବେ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ଆପଣ ଏବେର ସେସନରୁ କାର୍ଯ୍ୟକଳାପକୁ ସେଭ କରିପାରିବେ ବା ସବୁ ଆପ୍ସ ଓ ଡାଟାକୁ ଡିଲିଟ କରିପାରିବେ"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ଡିଲିଟ କରନ୍ତୁ"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ସେଭ କରନ୍ତୁ"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ଅତିଥି ମୋଡରୁ ବାହାରି ଯାଆନ୍ତୁ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ଅତିଥି ସେସନକୁ ରିସେଟ କରନ୍ତୁ"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ଅତିଥି ମୋଡରୁ ବାହାରି ଯାଆନ୍ତୁ"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ବାହାରିବା ସମୟରେ ସମସ୍ତ କାର୍ଯ୍ୟକଳାପକୁ ଡିଲିଟ କରାଯିବ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ବାହାରିବା ସମୟରେ ଆପଣଙ୍କର କାର୍ଯ୍ୟକଳାପକୁ ସେଭ ବା ଡିଲିଟ କରିପାରିବେ"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"ବର୍ତ୍ତମାନ ସେସନ କାର୍ଯ୍ୟକଳାପକୁ ଡିଲିଟ କରିବାକୁ ରିସେଟ କରନ୍ତୁ କିମ୍ବା ବାହାରିବା ସମୟରେ ଆପଣ କାର୍ଯ୍ୟକଳାପକୁ ସେଭ କିମ୍ବା ଡିଲିଟ କରିପାରିବେ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ଗୋଟିଏ ଫଟୋ ଉଠାନ୍ତୁ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ଏକ ଛବି ବାଛନ୍ତୁ"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ଫଟୋ ବାଛନ୍ତୁ"</string>
diff --git a/packages/SettingsLib/res/values-pa/strings.xml b/packages/SettingsLib/res/values-pa/strings.xml
index 0da516c80698..91f6f025d5d4 100644
--- a/packages/SettingsLib/res/values-pa/strings.xml
+++ b/packages/SettingsLib/res/values-pa/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"ਡਿਸਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"ਕਨੈਕਟ ਕਰ ਰਿਹਾ ਹੈ…"</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"ਕਨੈਕਟ ਕੀਤਾ ਹੋਇਆ<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"ਪੇਅਰ ਕਰ ਰਿਹਾ ਹੈ…"</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"ਜੋੜਾਬੱਧ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"ਕਨੈਕਟ ਕੀਤਾ ਹੋਇਆ (ਕੋਈ ਫ਼ੋਨ ਨਹੀਂ)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"ਕਨੈਕਟ ਕੀਤਾ ਹੋਇਆ (ਕੋਈ ਮੀਡੀਆ ਨਹੀਂ)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"ਕਨੈਕਟ ਕੀਤਾ ਹੋਇਆ (ਸੁਨੇਹੇ \'ਤੇ ਪਹੁੰਚ ਨਹੀਂ)<xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ਰੀਸੈੱਟ ਕਰੋ"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ਹਟਾਓ"</string>
<string name="guest_resetting" msgid="7822120170191509566">"ਮਹਿਮਾਨ ਨੂੰ ਰੀਸੈੱਟ ਕੀਤਾ ਜਾ ਰਿਹਾ ਹੈ…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ਕੀ ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਨੂੰ ਰੀਸੈੱਟ ਕਰਨਾ ਹੈ?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ਇਸ ਨਾਲ ਨਵਾਂ ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਸ਼ੁਰੂ ਹੋ ਜਾਵੇਗਾ ਅਤੇ ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਦੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਮਿਟ ਜਾਵੇਗਾ"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ਕੀ ਮਹਿਮਾਨ ਮੋਡ ਤੋਂ ਬਾਹਰ ਜਾਣਾ ਹੈ?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ਇਸ ਨਾਲ ਮੌਜੂਦਾ ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਦੀਆਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਮਿਟ ਜਾਵੇਗਾ"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ਬਾਹਰ ਜਾਓ"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ਕੀ ਮਹਿਮਾਨ ਸਰਗਰਮੀ ਰੱਖਿਅਤ ਕਰਨੀ ਹੈ?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ਤੁਸੀਂ ਮੌਜੂਦਾ ਸੈਸ਼ਨ ਦੀ ਸਰਗਰਮੀ ਨੂੰ ਰੱਖਿਅਤ ਕਰ ਸਕਦੇ ਹੋ ਜਾਂ ਸਾਰੀਆਂ ਐਪਾਂ ਅਤੇ ਡਾਟਾ ਮਿਟਾ ਸਕਦੇ ਹੋ"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ਮਿਟਾਓ"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"ਰੱਖਿਅਤ ਕਰੋ"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ਮਹਿਮਾਨ ਮੋਡ ਤੋਂ ਬਾਹਰ ਜਾਓ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ਮਹਿਮਾਨ ਸੈਸ਼ਨ ਨੂੰ ਰੀਸੈੱਟ ਕਰੋ"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ਮਹਿਮਾਨ ਮੋਡ ਤੋਂ ਬਾਹਰ ਜਾਓ"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ਬਾਹਰ ਜਾਣ \'ਤੇ ਸਾਰੀ ਸਰਗਰਮੀ ਮਿਟਾਈ ਜਾਵੇਗੀ"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ਤੁਸੀਂ ਬਾਹਰ ਜਾਣ \'ਤੇ ਆਪਣੀ ਸਭ ਸਰਗਰਮੀ ਰੱਖਿਅਤ ਕਰ ਜਾਂ ਮਿਟਾ ਸਕਦੇ ਹੋ"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"ਸੈਸ਼ਨ ਦੀ ਸਰਗਰਮੀ ਮਿਟਾਉਣ ਹੁਣੇ ਲਈ ਰੀਸੈੱਟ ਕਰੋ ਜਾਂ ਤੁਸੀਂ ਬਾਹਰ ਜਾਣ \'ਤੇ ਸਰਗਰਮੀ ਨੂੰ ਰੱਖਿਅਤ ਕਰ ਜਾਂ ਮਿਟਾ ਸਕਦੇ ਹੋ"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ਇੱਕ ਫ਼ੋਟੋ ਖਿੱਚੋ"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ਕੋਈ ਚਿੱਤਰ ਚੁਣੋ"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ਫ਼ੋਟੋ ਚੁਣੋ"</string>
diff --git a/packages/SettingsLib/res/values-pl/strings.xml b/packages/SettingsLib/res/values-pl/strings.xml
index aa5359af1fc2..658a63ba989e 100644
--- a/packages/SettingsLib/res/values-pl/strings.xml
+++ b/packages/SettingsLib/res/values-pl/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Rozłączanie..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Łączenie..."</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"Połączono – <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"Parowanie..."</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"Paruję…"</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Połączono (bez telefonu) – <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Połączono (bez multimediów) – <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"Połączono (bez dostępu do wiadomości) – <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetuj"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Usuń"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Resetuję sesję gościa…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Zresetować sesję gościa?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Zostanie uruchomiona nowa sesja gościa. Wszystkie aplikacje i dane z obecnej sesji zostaną usunięte."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Zamknąć tryb gościa?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Wszystkie aplikacje i dane z obecnej sesji gościa zostaną usunięte."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Wyjdź"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Zapisać aktywność gościa?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Możesz zapisać aktywność z obecnej sesji lub usunąć wszystkie aplikacje i dane"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Usuń"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Zapisz"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Zamknij tryb gościa"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Zresetuj sesję gościa"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Zakończ tryb gościa"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Cała aktywność zostanie usunięta po zamknięciu"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Możesz zapisać lub usunąć swoją aktywność podczas zamykania"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Zresetuj, aby usunąć aktywność w sesji w tym momencie. Możesz też ją zapisać lub usunąć podczas zamykania"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Zrób zdjęcie"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Wybierz obraz"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Wybierz zdjęcie"</string>
diff --git a/packages/SettingsLib/res/values-pt-rBR/strings.xml b/packages/SettingsLib/res/values-pt-rBR/strings.xml
index a0355b9c419e..06faf68461ca 100644
--- a/packages/SettingsLib/res/values-pt-rBR/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rBR/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Redefinir"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remover"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Redefinindo visitante…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Redefinir Sessão de visitante?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Essa ação vai iniciar uma nova Sessão de visitante e excluir todos os apps e dados da sessão atual"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Sair do modo visitante?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Essa ação vai excluir apps e dados da Sessão de visitante atual"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Sair"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Salvar a atividade do visitante?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Você pode salvar a atividade da sessão atual ou excluir todos os apps e dados"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Excluir"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Salvar"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Sair do modo visitante"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Redefinir Sessão de visitante"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Sair do modo visitante"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Todas as atividades serão excluídas ao sair"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Você pode salvar ou excluir sua atividade ao sair"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Faça uma redefinição para excluir a atividade da sessão agora. Você também pode salvar ou excluir a atividade ao sair"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Selecionar foto"</string>
diff --git a/packages/SettingsLib/res/values-pt-rPT/strings.xml b/packages/SettingsLib/res/values-pt-rPT/strings.xml
index 5b4172536a1f..4fc5728b4177 100644
--- a/packages/SettingsLib/res/values-pt-rPT/strings.xml
+++ b/packages/SettingsLib/res/values-pt-rPT/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Repor"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remover"</string>
<string name="guest_resetting" msgid="7822120170191509566">"A repor o convidado…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Repor sessão de convidado?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Esta ação inicia uma nova sessão de convidado e elimina todas as apps e dados da sessão atual"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Sair do modo de convidado?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Esta ação elimina as apps e os dados da sessão de convidado atual"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Sair"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Guardar atividade de convidado?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Pode guardar a atividade da sessão atual ou eliminar todas as apps e dados"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Eliminar"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Guardar"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Sair do modo de convidado"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Repor sessão de convidado"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Sair do modo de convidado"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toda a atividade é eliminada ao sair"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Pode guardar ou eliminar a sua atividade ao sair"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Reponha para eliminar agora a atividade da sessão. Em alternativa, pode guardar ou eliminar a atividade ao sair"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Selecionar foto"</string>
diff --git a/packages/SettingsLib/res/values-pt/strings.xml b/packages/SettingsLib/res/values-pt/strings.xml
index a0355b9c419e..06faf68461ca 100644
--- a/packages/SettingsLib/res/values-pt/strings.xml
+++ b/packages/SettingsLib/res/values-pt/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Redefinir"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Remover"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Redefinindo visitante…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Redefinir Sessão de visitante?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Essa ação vai iniciar uma nova Sessão de visitante e excluir todos os apps e dados da sessão atual"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Sair do modo visitante?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Essa ação vai excluir apps e dados da Sessão de visitante atual"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Sair"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Salvar a atividade do visitante?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Você pode salvar a atividade da sessão atual ou excluir todos os apps e dados"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Excluir"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Salvar"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Sair do modo visitante"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Redefinir Sessão de visitante"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Sair do modo visitante"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Todas as atividades serão excluídas ao sair"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Você pode salvar ou excluir sua atividade ao sair"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Faça uma redefinição para excluir a atividade da sessão agora. Você também pode salvar ou excluir a atividade ao sair"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Tirar uma foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Escolher uma imagem"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Selecionar foto"</string>
diff --git a/packages/SettingsLib/res/values-ro/strings.xml b/packages/SettingsLib/res/values-ro/strings.xml
index 9c7a2ce0dce6..019fb7f60fe1 100644
--- a/packages/SettingsLib/res/values-ro/strings.xml
+++ b/packages/SettingsLib/res/values-ro/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"În curs de deconectare..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Se conectează..."</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"V-ați conectat la <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"Se conectează…"</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"Se asociază…"</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Conectat (fără telefon) la <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Conectat (fără conținut media) la <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"Conectat (fără acces la mesaje) la <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetați"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Eliminați"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Se resetează invitatul…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Resetați sesiunea pentru invitați?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Astfel, va începe o nouă sesiune pentru invitați și se vor șterge toate aplicațiile și datele din sesiunea actuală"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Ieșiți din modul pentru invitați?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Se vor șterge toate aplicațiile și datele din sesiunea pentru invitați actuală"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ieșiți"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Salvați activitatea invitatului?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Salvați activitatea din sesiunea actuală sau ștergeți aplicațiile și datele"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Ștergeți"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Salvați"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Ieșiți din modul pentru invitați"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Resetați sesiunea pentru invitați"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Ieșiți din modul pentru invitați"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Toate activitățile vor fi șterse la ieșire"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Puteți să salvați sau să ștergeți activitatea la ieșire"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Resetați pentru a șterge acum activitatea din sesiune sau salvați ori ștergeți activitatea la ieșire"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Faceți o fotografie"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Alegeți o imagine"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Selectați fotografia"</string>
diff --git a/packages/SettingsLib/res/values-ru/strings.xml b/packages/SettingsLib/res/values-ru/strings.xml
index 3ce46e7bd0c1..2910ebc02652 100644
--- a/packages/SettingsLib/res/values-ru/strings.xml
+++ b/packages/SettingsLib/res/values-ru/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Сбросить"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Удалить"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Сброс гостевого сеанса…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Сбросить гостевой сеанс?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"При этом начнется новый гостевой сеанс, а все данные и приложения предыдущего сеанса будут удалены."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Выйти из гостевого режима?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Все приложения и данные текущего гостевого сеанса будут удалены."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Выйти"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Сохранить историю сеанса?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Сохраните историю текущего сеанса или удалите данные и приложения."</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Удалить"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Сохранить"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Выйти из гостевого режима"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Сбросить гостевой сеанс"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Выйти из гостевого режима"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"История будет удалена сразу после выхода."</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"При выходе вы можете сохранить или удалить историю."</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Сбросьте историю сеанса прямо сейчас и удалите или сохраните ее при выходе."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сделать снимок"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Выбрать фото"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Выбрать фотографию"</string>
diff --git a/packages/SettingsLib/res/values-si/strings.xml b/packages/SettingsLib/res/values-si/strings.xml
index 0cb9c840ee12..bd0caefe4786 100644
--- a/packages/SettingsLib/res/values-si/strings.xml
+++ b/packages/SettingsLib/res/values-si/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"යළි සකසන්න"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ඉවත් කරන්න"</string>
<string name="guest_resetting" msgid="7822120170191509566">"අමුත්තා යළි සකසමින්…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"ආගන්තුක සැසිය යළි සකසන්නද?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"මෙය නව ආගන්තුක සැසියක් ආරම්භ කර වත්මන් සැසියෙන් සියලු යෙදුම් සහ දත්ත මකනු ඇත"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ආගන්තුක ප්‍රකාරයෙන් පිටවන්නද?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"මෙය වත්මන් ආගන්තුක සැසියෙන් යෙදුම් සහ දත්ත මකනු ඇත"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"පිටවන්න"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"ආගන්තුක ක්‍රියාකාරකම් සුරකින්නද?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"ඔබට වත්මන් සැසියෙන් ක්‍රියාකාරකම් සුරැකීමට හෝ සියලු යෙදුම් සහ දත්ත මැකීමට හැකිය"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"මකන්න"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"සුරකින්න"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ආගන්තුක ප්‍රකාරයෙන් පිටවන්න"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"ආගන්තුක සැසිය යළි සකසන්න"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"අමුත්තා පිටවීම"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"පිටවීමේදී සියලු ක්‍රියාකාරකම් මකනු ඇත"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"ඔබට පිටවීමේදී ඔබගේ ක්‍රියාකාරකම් සුරැකීමට හෝ මැකීමට හැකිය"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"දැන් සැසි ක්‍රියාකාරකම් මැකීමට යළි සකසන්න, නැතහොත් ඔබට පිටවීමේදී ක්‍රියාකාරකම් සුරැකීමට හෝ මැකීමට හැකිය"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ඡායාරූපයක් ගන්න"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"රූපයක් තෝරන්න"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ඡායාරූපය තෝරන්න"</string>
diff --git a/packages/SettingsLib/res/values-sk/strings.xml b/packages/SettingsLib/res/values-sk/strings.xml
index 72a9f40ff5a6..5ad807118909 100644
--- a/packages/SettingsLib/res/values-sk/strings.xml
+++ b/packages/SettingsLib/res/values-sk/strings.xml
@@ -95,7 +95,7 @@
<string name="bluetooth_disconnecting" msgid="7638892134401574338">"Prebieha odpájanie..."</string>
<string name="bluetooth_connecting" msgid="5871702668260192755">"Prebieha pripájanie…"</string>
<string name="bluetooth_connected" msgid="8065345572198502293">"Pripojené k zariadeniu <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g>"</string>
- <string name="bluetooth_pairing" msgid="4269046942588193600">"Párovanie..."</string>
+ <string name="bluetooth_pairing" msgid="4269046942588193600">"Páruje sa..."</string>
<string name="bluetooth_connected_no_headset" msgid="2224101138659967604">"Pripojené k zariadeniu <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (bez telefónu)"</string>
<string name="bluetooth_connected_no_a2dp" msgid="8566874395813947092">"Pripojené k zariadeniu <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (bez médií)"</string>
<string name="bluetooth_connected_no_map" msgid="3381860077002724689">"Pripojené k <xliff:g id="ACTIVE_DEVICE">%1$s</xliff:g> (bez prístupu k správam)"</string>
@@ -403,9 +403,9 @@
<string name="show_notification_channel_warnings" msgid="3448282400127597331">"Zobraziť hlásenia kanála upozornení"</string>
<string name="show_notification_channel_warnings_summary" msgid="68031143745094339">"Zobrazovať na obrazovke varovné hlásenie, keď aplikácia zverejní upozornenie bez platného kanála"</string>
<string name="force_allow_on_external" msgid="9187902444231637880">"Vynútiť povolenie aplikácií na externom úložisku"</string>
- <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Umožňuje zapísať akúkoľvek aplikáciu do externého úložiska bez ohľadu na hodnoty v manifeste"</string>
+ <string name="force_allow_on_external_summary" msgid="8525425782530728238">"Umožniť zapísať akúkoľvek aplikáciu do externého úložiska bez ohľadu na hodnoty v manifeste"</string>
<string name="force_resizable_activities" msgid="7143612144399959606">"Vynútiť možnosť zmeny veľkosti aktivít"</string>
- <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Veľkosti všetkých aktivít bude možné zmeniť na niekoľko okien (bez ohľadu na hodnoty manifestu)."</string>
+ <string name="force_resizable_activities_summary" msgid="2490382056981583062">"Umožniť veľkosť všetkých aktivít na niekoľko okien (bez ohľadu na hodnoty manifestu)"</string>
<string name="enable_freeform_support" msgid="7599125687603914253">"Povoliť okná s voľným tvarom"</string>
<string name="enable_freeform_support_summary" msgid="1822862728719276331">"Povoliť podporu pre experimentálne okná s voľným tvarom"</string>
<string name="local_backup_password_title" msgid="4631017948933578709">"Heslo pre zálohy v počítači"</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Resetovať"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Odstrániť"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Relácia hosťa sa resetuje…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Chcete resetovať reláciu hosťa?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Týmto sa spustí nová relácia hosťa a odstránia sa všetky aplikácie a údaje z aktuálnej relácie"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Chcete skončiť režim pre hostí?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Ukončí sa režim pre hostí a odstránia sa aplikácie a údaje z relácie hosťa"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Ukončiť"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Chcete uložiť aktivitu hosťa?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Aktivitu v aktuálnej relácii uložte alebo odstráňte všetky aplikácie a údaje"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Odstrániť"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Uložiť"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Ukončiť režim pre hostí"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Resetovať reláciu hosťa"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Ukončiť režim pre hostí"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Pri ukončení sa všetka aktivita odstráni"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Aktivitu môžete pri ukončení uložiť alebo odstrániť"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Resetovaním ihneď odstráňte aktivitu relácie alebo ju uložte či odstráňte pri ukončení relácie"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Odfotiť"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Vybrať obrázok"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Vybrať fotku"</string>
diff --git a/packages/SettingsLib/res/values-sl/strings.xml b/packages/SettingsLib/res/values-sl/strings.xml
index e49f0c4e5e02..34bfa8e5d3a5 100644
--- a/packages/SettingsLib/res/values-sl/strings.xml
+++ b/packages/SettingsLib/res/values-sl/strings.xml
@@ -590,14 +590,29 @@
<string name="add_guest_failed" msgid="8074548434469843443">"Ustvarjanje novega gosta ni uspelo."</string>
<string name="user_nickname" msgid="262624187455825083">"Vzdevek"</string>
<string name="user_add_user" msgid="7876449291500212468">"Dodaj uporabnika"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Dodajanje gosta"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"Odstranitev gosta"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Dodaj gosta"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Odstrani gosta"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Ponastavi gosta"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Želite ponastaviti gosta?"</string>
<string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Želite odstraniti gosta?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Ponastavi"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Odstrani"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Ponastavljanje gosta …"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Želite ponastaviti sejo gosta?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"S tem boste začeli novo sejo gosta ter izbrisali vse aplikacije in podatke v trenutni seji."</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Zapri način za goste"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"S tem boste izbrisali aplikacije in podatke v trenutni seji gosta."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Zapri"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Želite shraniti dejavnost gosta?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Lahko shranite dejavnost v trenutni seji ali izbrišete vse aplikacije in podatke."</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Izbriši"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Shrani"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Zapri način za goste"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Ponastavi sejo gosta"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Zapri sejo gosta"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Ko zaprete sejo, bo vsa dejavnost izbrisana."</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Ko zaprete sejo, lahko shranite ali izbrišete dejavnost."</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Ponastavite, če želite dejavnost v seji izbrisati zdaj, lahko pa jo shranite ali izbrišete, ko zaprete sejo."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotografiranje"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Izberi sliko"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Izbira fotografije"</string>
diff --git a/packages/SettingsLib/res/values-sq/strings.xml b/packages/SettingsLib/res/values-sq/strings.xml
index 9b62de269bb9..aa8aee827087 100644
--- a/packages/SettingsLib/res/values-sq/strings.xml
+++ b/packages/SettingsLib/res/values-sq/strings.xml
@@ -590,14 +590,29 @@
<string name="add_guest_failed" msgid="8074548434469843443">"Profili i vizitorit të ri nuk u krijua"</string>
<string name="user_nickname" msgid="262624187455825083">"Pseudonimi"</string>
<string name="user_add_user" msgid="7876449291500212468">"Shto përdorues"</string>
- <string name="guest_new_guest" msgid="3482026122932643557">"Shto të ftuar"</string>
- <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq të ftuarin"</string>
+ <string name="guest_new_guest" msgid="3482026122932643557">"Shto vizitor"</string>
+ <string name="guest_exit_guest" msgid="5908239569510734136">"Hiq vizitorin"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Rivendos vizitorin"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Të rivendoset vizitori?"</string>
<string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Të hiqet vizitori?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Rivendos"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Hiq"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Vizitori po rivendoset…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Të rivendoset sesioni për vizitorë?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Kjo do të fillojë një sesion të ri për vizitorë dhe do të fshijë të gjitha aplikacionet dhe të dhënat nga sesioni aktual"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Të hiqet modaliteti \"vizitor\"?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Kjo do të fshijë aplikacionet dhe të dhënat nga sesioni aktual për vizitorë"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Dil"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Të ruhet aktiviteti i vizitorit?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Ruaj aktivitetin nga sesioni aktual ose fshi të gjitha aplikacionet e të dhënat"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Fshi"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Ruaj"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Dil nga modaliteti \"vizitor\""</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Rivendos sesionin për vizitorë"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Dil nga modaliteti \"vizitor\""</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Të gjitha aktivitetet do të fshihen kur të dalësh"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Mund ta ruash ose ta fshish aktivitetin tënd kur të dalësh"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Rivendose për të fshirë aktivitetin e sesionit tani ose mund ta ruash ose ta fshish aktivitetin kur të dalësh"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Bëj një fotografi"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Zgjidh një imazh"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Zgjidh një fotografi"</string>
diff --git a/packages/SettingsLib/res/values-sr/strings.xml b/packages/SettingsLib/res/values-sr/strings.xml
index eba79105f437..381cd971727e 100644
--- a/packages/SettingsLib/res/values-sr/strings.xml
+++ b/packages/SettingsLib/res/values-sr/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Ресетуј"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Уклони"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Сесија госта се ресетује…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Желите да ресетујете сесију госта?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Тиме ћете покренути нову сесију госта и избрисати све апликације и податке из актуелне сесије"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Изаћи ћете из режима госта?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Тиме ћете избрисати све апликације и податке из актуелне сесије госта"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Изађи"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Сачуваћете активности госта?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Сачувајте активности из актуелне сесије или избришите све апликације и податке"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Избриши"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Сачувај"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Изађи из режима госта"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Ресетуј сесију госта"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Изађи из режима госта"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Све активности ће бити избрисане при излазу"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Можете да сачувате или избришете активности при излазу"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Ресетујете да бисте избрисали активности сесије одмах или можете да сачувате или избришете активности при излазу"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Сликај"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Одабери слику"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Изаберите слику"</string>
diff --git a/packages/SettingsLib/res/values-sv/strings.xml b/packages/SettingsLib/res/values-sv/strings.xml
index 1c1c38fc193b..683619a6a305 100644
--- a/packages/SettingsLib/res/values-sv/strings.xml
+++ b/packages/SettingsLib/res/values-sv/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Återställ"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ta bort"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Gästsessionen återställs …"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Vill du återställa gästsessionen?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"En ny gästsession startas och alla appar och all data från den pågående sessionen raderas"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Vill du avsluta gästläget?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Appar och data från den pågående gästsessionen raderas"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Avsluta"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Vill du spara gästaktivitet?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Du kan spara aktivitet från den pågående sessionen eller radera appar och data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Radera"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Spara"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Avsluta gästläget"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Återställ gästsession"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Avsluta gästsession"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"All aktivitet raderas när du avslutar"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Du kan spara eller radera aktivitet när du avslutar"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Återställ om du vill radera sessionsaktiviteten nu. Du kan också spara eller radera aktivitet när du avslutar"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Ta ett foto"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Välj en bild"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Välj foto"</string>
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index fd7c893f0998..69ab95d7dc51 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Badilisha"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Ondoa"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Inabadilisha kipindi cha mgeni…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Ungependa kuweka upya kipindi cha mgeni?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Hii itaanzisha upya kipindi cha mgeni na kufuta programu na data yote kwenye kipindi cha sasa"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Utafunga matumizi ya wageni?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Hatua hii itafuta programu na data kutoka kwenye kipindi cha mgeni cha sasa"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Funga"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Utahifadhi shughuli za mgeni?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Unaweza kuhifadhi shughuli kutoka kipindi cha sasa au kufuta programu na data yote"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Futa"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Hifadhi"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Funga matumizi ya wageni"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Weka upya kipindi cha mgeni"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Funga utumiaji wa mgeni"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Shughuli zote zitafutwa wakati wa kufunga"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Unaweza kuhifadhi au kufuta shughuli zako wakati wa kufunga"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Weka upya ili ufute shughuli za kipindi sasa au unaweza kuhifadhi au kufuta shughuli wakati wa kufunga"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Piga picha"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Chagua picha"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Chagua picha"</string>
diff --git a/packages/SettingsLib/res/values-ta/strings.xml b/packages/SettingsLib/res/values-ta/strings.xml
index 236b74bfcb57..6544a2335b18 100644
--- a/packages/SettingsLib/res/values-ta/strings.xml
+++ b/packages/SettingsLib/res/values-ta/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"மீட்டமை"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"அகற்று"</string>
<string name="guest_resetting" msgid="7822120170191509566">"கெஸ்ட்டை மீட்டமைக்கிறது…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"கெஸ்ட் அமர்வை ரீசெட் செய்யவா?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"புதிய கெஸ்ட் அமர்வு தொடங்கப்படும், மேலும் தற்போதைய கெஸ்ட் அமர்வின் ஆப்ஸ் மற்றும் தரவு அனைத்தும் நீக்கப்படும்"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"கெஸ்ட் முறையிலிருந்து வெளியேறவா?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"தற்போதைய கெஸ்ட் அமர்வின் ஆப்ஸ் மற்றும் தரவு அனைத்தும் நீக்கப்படும்"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"வெளியேறு"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"கெஸ்ட் செயல்பாடுகளைச் சேமிக்கவா?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"தற்போதைய அமர்வின் செயல்பாடுகளைச் சேமிக்கலாம் அல்லது ஆப்ஸையும் தரவையும் நீக்கலாம்"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"நீக்கு"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"சேமி"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"கெஸ்ட் பயன்முறையிலிருந்து வெளியேறு"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"கெஸ்ட் அமர்வை ரீசெட் செய்"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"கெஸ்ட் பயன்முறையிலிருந்து வெளியேறு"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"வெளியேறியவுடன் அனைத்துச் செயல்பாடுகளும் நீக்கப்படும்"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"வெளியேறும்போது செயல்பாடுகளைச் சேமிக்கலாம் அல்லது நீக்கலாம்"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"அமர்வின் செயல்பாடுகளை இப்போதே நீக்க ரீசெட் செய்யவும் அல்லது வெளியேறும்போது செயல்பாடுகளைச் சேமிக்கலாம் அல்லது நீக்கலாம்"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"படமெடுங்கள்"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"படத்தைத் தேர்வுசெய்யுங்கள்"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"படத்தைத் தேர்ந்தெடுங்கள்"</string>
diff --git a/packages/SettingsLib/res/values-te/strings.xml b/packages/SettingsLib/res/values-te/strings.xml
index c25966c2d429..f6612b8243dd 100644
--- a/packages/SettingsLib/res/values-te/strings.xml
+++ b/packages/SettingsLib/res/values-te/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"రీసెట్ చేయండి"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"తీసివేయండి"</string>
<string name="guest_resetting" msgid="7822120170191509566">"గెస్ట్ సెషన్‌ను రీసెట్ చేస్తోంది…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"గెస్ట్ సెషన్‌ను రీసెట్ చేయాలా?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"ఇది కొత్త గెస్ట్ సెషన్‌ను ప్రారంభిస్తుంది, ప్రస్తుత సెషన్ నుండి అన్ని యాప్‌లు, డేటాను తొలగించండి"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"గెస్ట్ మోడ్ నిష్క్రమించాలా?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"ఇది ప్రస్తుత గెస్ట్ సెషన్ నుండి యాప్‌లు, డేటాను తొలగిస్తుంది"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"నిష్క్రమించండి"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"గెస్ట్ యాక్టివిటీని సేవ్ చేయాలా?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"మీరు సెషన్ నుండి యాక్టివిటీని సేవ్ చేయవచ్చు, అన్ని యాప్‌లు, డేటాను తొలగించవచ్చు"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"తొలగించండి"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"సేవ్ చేయండి"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"గెస్ట్ మోడ్ నుండి నిష్క్రమణ"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"గెస్ట్ సెషన్‌ను రీసెట్ చేయండి"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"గెస్ట్ మోడ్ నుండి నిష్క్రమించండి"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"నిష్క్రమణ సమయంలో మొత్తం యాక్టివిటీ తొలగించబడుతుంది"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"మీ నిష్క్రమణలో, యాక్టివిటీని సేవ్ చేయవచ్చు లేదా తొలగించవచ్చు"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"సెషన్ యాక్టివిటీని తొలగించడానికి ఇప్పుడే రీసెట్ చేయండి లేదా మీరు నిష్క్రమించేటప్పుడు యాక్టివిటీని సేవ్ చేయవచ్చు లేదా తొలగించవచ్చు"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ఒక ఫోటో తీయండి"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ఇమేజ్‌ను ఎంచుకోండి"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"ఫోటోను ఎంచుకోండి"</string>
diff --git a/packages/SettingsLib/res/values-th/strings.xml b/packages/SettingsLib/res/values-th/strings.xml
index 41ef1e41b90f..4410b1823bd7 100644
--- a/packages/SettingsLib/res/values-th/strings.xml
+++ b/packages/SettingsLib/res/values-th/strings.xml
@@ -594,10 +594,25 @@
<string name="guest_exit_guest" msgid="5908239569510734136">"นำผู้ใช้ชั่วคราวออก"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"รีเซ็ตผู้เข้าร่วม"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"รีเซ็ตผู้เข้าร่วมไหม"</string>
- <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"นำผู้เข้าร่วมออกไหม"</string>
+ <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"นำผู้ใช้ชั่วคราวออกไหม"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"รีเซ็ต"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"นำออก"</string>
<string name="guest_resetting" msgid="7822120170191509566">"กำลังรีเซ็ตผู้เข้าร่วม…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"รีเซ็ตเซสชันผู้มาเยือนไหม"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"การดำเนินการนี้จะเริ่มเซสชันผู้มาเยือนใหม่ และจะลบแอปและข้อมูลทั้งหมดจากเซสชันปัจจุบัน"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"ออกจากโหมดผู้มาเยือนไหม"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"การดำเนินการนี้จะลบแอปและข้อมูลออกจากเซสชันผู้มาเยือนในปัจจุบัน"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"ออก"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"บันทึกกิจกรรมของผู้มาเยือนไหม"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"คุณสามารถบันทึกกิจกรรมจากเซสชันปัจจุบันหรือจะลบแอปและข้อมูลทั้งหมดก็ได้"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"ลบ"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"บันทึก"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"ออกจากโหมดผู้มาเยือน"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"รีเซ็ตเซสชันผู้มาเยือน"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"ออกจากโหมดผู้มาเยือน"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"ระบบจะลบกิจกรรมทั้งหมดเมื่อออก"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"คุณสามารถบันทึกหรือลบกิจกรรมเมื่อออก"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"รีเซ็ตเพื่อลบกิจกรรมของเซสชันตอนนี้เลย หรือจะบันทึกหรือลบกิจกรรมเมื่อออกก็ได้"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ถ่ายรูป"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"เลือกรูปภาพ"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"เลือกรูปภาพ"</string>
diff --git a/packages/SettingsLib/res/values-tl/strings.xml b/packages/SettingsLib/res/values-tl/strings.xml
index 7d4a4b49bc0d..a1c4fffc0217 100644
--- a/packages/SettingsLib/res/values-tl/strings.xml
+++ b/packages/SettingsLib/res/values-tl/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"I-reset"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Alisin"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Nire-reset ang bisita…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"I-reset ang session ng bisita?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Magsisimula ito ng bagong session ng bisita at made-delete ang lahat ng app at data mula sa kasalukuyang session"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Umalis sa guest mode?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Ide-delete nito ang mga app at data mula sa kasalukuyang session ng bisita"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Umalis"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"I-save ang aktibidad ng bisita?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Puwedeng i-save ang aktibidad ng session ngayon o i-delete lahat ng app at data"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"I-delete"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"I-save"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Umalis sa guest mode"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"I-reset ang session ng bisita"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Umalis sa pagiging bisita"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Made-delete ang lahat ng aktibidad kapag umalis"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Puwede mong i-save o i-delete ang aktibidad pagkaalis"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Mag-reset para mag-delete ng aktibidad ng session ngayon, o puwede kang mag-save o mag-delete ng aktibidad pagkaalis"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Kumuha ng larawan"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Pumili ng larawan"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Pumili ng larawan"</string>
diff --git a/packages/SettingsLib/res/values-tr/strings.xml b/packages/SettingsLib/res/values-tr/strings.xml
index 5e5b42756043..24c6d690c325 100644
--- a/packages/SettingsLib/res/values-tr/strings.xml
+++ b/packages/SettingsLib/res/values-tr/strings.xml
@@ -151,7 +151,7 @@
<string name="bluetooth_pairing_decline" msgid="6483118841204885890">"İptal"</string>
<string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Eşleme işlemi, bağlantı kurulduğunda kişilerinize ve çağrı geçmişine erişim izni verir."</string>
<string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
- <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya parola yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi"</string>
+ <string name="bluetooth_pairing_pin_error_message" msgid="264422127613704940">"PIN veya parola yanlış olduğundan <xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile eşlenemedi."</string>
<string name="bluetooth_pairing_device_down_error_message" msgid="2554424863101358857">"<xliff:g id="DEVICE_NAME">%1$s</xliff:g> ile iletişim kurulamıyor."</string>
<string name="bluetooth_pairing_rejected_error_message" msgid="5943444352777314442">"Eşleme <xliff:g id="DEVICE_NAME">%1$s</xliff:g> tarafından reddedildi."</string>
<string name="bluetooth_talkback_computer" msgid="3736623135703893773">"Bilgisayar"</string>
@@ -367,7 +367,7 @@
<string name="show_screen_updates" msgid="2078782895825535494">"Yüzey güncellemelerini göster"</string>
<string name="show_screen_updates_summary" msgid="2126932969682087406">"Güncellenirken tüm pencere yüzeylerini yakıp söndür"</string>
<string name="show_hw_screen_updates" msgid="2021286231267747506">"Görünüm güncellemelerini göster"</string>
- <string name="show_hw_screen_updates_summary" msgid="3539770072741435691">"Çizim yapılırken görünümleri pencerede çiz"</string>
+ <string name="show_hw_screen_updates_summary" msgid="3539770072741435691">"Çizildiğinde pencerelerin içini yakıp söndür"</string>
<string name="show_hw_layers_updates" msgid="5268370750002509767">"Donanım katmanı güncellemelerini göster"</string>
<string name="show_hw_layers_updates_summary" msgid="5850955890493054618">"Güncellenirken donanım katmanlarını yeşil renkte yanıp söndür"</string>
<string name="debug_hw_overdraw" msgid="8944851091008756796">"GPU fazla çizim hatasını ayıkla"</string>
@@ -594,10 +594,25 @@
<string name="guest_exit_guest" msgid="5908239569510734136">"Misafir oturumunu kaldır"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Misafir oturumunu sıfırla"</string>
<string name="guest_reset_guest_dialog_title" msgid="8047270010895437534">"Misafir oturumu sıfırlansın mı?"</string>
- <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Konuk çıkarılsın mı?"</string>
+ <string name="guest_remove_guest_dialog_title" msgid="4548511006624088072">"Misafir oturumu kaldırılsın mı?"</string>
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Sıfırla"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Kaldır"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Misafir oturumu sıfırlanıyor…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Misafir oturumu sıfırlansın mı?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Bu işlem, yeni bir misafir oturumu başlatarak mevcut oturumdaki tüm uygulamaları ve verileri siler"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Misafir modundan çıkılsın mı?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Bu işlem mevcut misafir oturumundaki tüm uygulamaları ve verileri siler"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Çık"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Misafir etkinliği kaydedilsin mi?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Oturumdaki etkinliği kaydedebilir ya da tüm uygulama ve verileri silebilirsiniz"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Sil"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Kaydet"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Misafir modundan çık"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Misafir oturumunu sıfırla"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Misafir modundan çık"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Çıkış yapıldığında tüm etkinlikler silinir"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Etkinliklerinizi çıkarken kaydedebilir veya silebilirsiniz"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Oturum etkinliklerini hemen silmek için sıfırlayın. Etkinlikleri çıkarken kaydetmeyi veya silmeyi de tercih edebilirsiniz"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Fotoğraf çek"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Resim seç"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Fotoğraf seç"</string>
@@ -644,7 +659,7 @@
<string name="dream_complication_title_weather" msgid="598609151677172783">"Hava durumu"</string>
<string name="dream_complication_title_aqi" msgid="4587552608957834110">"Hava Kalitesi"</string>
<string name="dream_complication_title_cast_info" msgid="4038776652841885084">"Yayın Bilgisi"</string>
- <string name="avatar_picker_title" msgid="8492884172713170652">"Profil fotoğrafı seç"</string>
+ <string name="avatar_picker_title" msgid="8492884172713170652">"Profil fotoğrafı seçin"</string>
<string name="default_user_icon_description" msgid="6554047177298972638">"Varsayılan kullanıcı simgesi"</string>
<string name="physical_keyboard_title" msgid="4811935435315835220">"Fiziksel klavye"</string>
<string name="keyboard_layout_dialog_title" msgid="3927180147005616290">"Klavye düzenini seçin"</string>
diff --git a/packages/SettingsLib/res/values-uk/strings.xml b/packages/SettingsLib/res/values-uk/strings.xml
index b068670f8036..cab4add99c10 100644
--- a/packages/SettingsLib/res/values-uk/strings.xml
+++ b/packages/SettingsLib/res/values-uk/strings.xml
@@ -589,7 +589,7 @@
<string name="add_user_failed" msgid="4809887794313944872">"Не вдалося створити користувача"</string>
<string name="add_guest_failed" msgid="8074548434469843443">"Не вдалося створити гостя"</string>
<string name="user_nickname" msgid="262624187455825083">"Псевдонім"</string>
- <string name="user_add_user" msgid="7876449291500212468">"Додати користувача"</string>
+ <string name="user_add_user" msgid="7876449291500212468">"Додати ​користувача"</string>
<string name="guest_new_guest" msgid="3482026122932643557">"Додати гостя"</string>
<string name="guest_exit_guest" msgid="5908239569510734136">"Видалити гостя"</string>
<string name="guest_reset_guest" msgid="6110013010356013758">"Скинути сеанс у режимі \"Гість\""</string>
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Скинути"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Вилучити"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Скидання сеансу в режимі \"Гість\"…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Скинути сеанс у режимі гостя?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Почнеться новий сеанс у режимі гостя, а всі додатки й дані з поточного сеансу буде видалено"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Вийти з режиму гостя?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Усі додатки й дані з поточного сеансу в режимі гостя буде видалено."</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Вийти"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Зберегти дії в режимі гостя?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Ви можете зберегти дії з поточного сеансу або видалити всі додатки й дані"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Видалити"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Зберегти"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Вийти з режиму гостя"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Скинути сеанс у режимі гостя"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Вийти з режиму гостя"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Під час виходу буде видалено всі дії"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Під час виходу можна зберегти або видалити ваші дії"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Скиньте, щоб зараз видалити дії під час сеансу. Ви також можете зберегти чи видалити дії під час виходу."</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Зробити фотографію"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Вибрати зображення"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Вибрати фотографію"</string>
@@ -655,12 +670,8 @@
<string name="bt_le_audio_scan_qr_code" msgid="3521809854780392679">"Сканування QR-коду"</string>
<string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"Щоб почати слухати аудіо, наведіть камеру на QR-код нижче"</string>
<string name="bt_le_audio_qr_code_is_not_valid_format" msgid="6092191081849434734">"Недійсний формат QR-коду"</string>
- <!-- no translation found for bt_le_audio_broadcast_dialog_title (5392738488989777074) -->
- <skip />
- <!-- no translation found for bt_le_audio_broadcast_dialog_sub_title (268234802198852753) -->
- <skip />
- <!-- no translation found for bt_le_audio_broadcast_dialog_switch_app (5749813313369517812) -->
- <skip />
- <!-- no translation found for bt_le_audio_broadcast_dialog_different_output (2638402023060391333) -->
- <skip />
+ <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"Зупинити трансляцію з додатка <xliff:g id="APP_NAME">%1$s</xliff:g>?"</string>
+ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"Якщо ви зміните додаток (<xliff:g id="SWITCHAPP">%1$s</xliff:g>) або аудіовихід, поточну трансляцію буде припинено"</string>
+ <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"Змінити додаток для трансляції на <xliff:g id="SWITCHAPP">%1$s</xliff:g>"</string>
+ <string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"Змінити аудіовихід"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-ur/strings.xml b/packages/SettingsLib/res/values-ur/strings.xml
index 1386e05985b5..e2586bebe25a 100644
--- a/packages/SettingsLib/res/values-ur/strings.xml
+++ b/packages/SettingsLib/res/values-ur/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"ری سیٹ کریں"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"ہٹائیں"</string>
<string name="guest_resetting" msgid="7822120170191509566">"مہمان کو ری سیٹ کرنا…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"مہمان سیشن کو ری سیٹ کریں؟"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"یہ ایک نیا مہمان سیشن شروع کرے گا اور موجودہ سیشن سے تمام ایپس اور ڈیٹا کو حذف کر دے گا"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"مہمان وضع سے باہر نکلیں؟"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"یہ موجودہ مہمان سیشن سے ایپس اور ڈیٹا کو حذف کر دے گا"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"باہر نکلیں"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"مہمان کی سرگرمی محفوظ کریں؟"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"آپ موجودہ سیشن سے سرگرمی کو محفوظ یا تمام ایپس اور ڈیٹا کو حذف کر سکتے ہیں"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"حذف کریں"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"محفوظ کریں"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"مہمان وضع سے باہر نکلیں"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"مہمان سیشن کو ری سیٹ کریں"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"مہمان وضع سے باہر نکلیں"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"باہر نکلنے پر تمام سرگرمیاں حذف کر دی جائیں گی"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"باہر نکلنے پر آپ اپنی سرگرمی کو محفوظ یا حذف کر سکتے ہیں"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"سیشن کی سرگرمی کو ابھی حذف کرنے کے لیے ری سیٹ کریں، یا باہر نکلنے پر آپ اپنی سرگرمی کو محفوظ یا حذف کر سکتے ہیں"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"ایک تصویر لیں"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"ایک تصویر منتخب کریں"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"تصویر منتخب کریں"</string>
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 52cc9cbd7c03..19500f8ad6ca 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Tiklash"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Olib tashlash"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Mehmon seansi tiklanmoqda…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Mehmon seansi tiklansinmi?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Bunda yangi mehmon seansi ishga tushadi va joriy seans ilova va maʼlumotlari tozalanadi"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Mehmon rejimidan chiqasizmi?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Bunda joriy mehmon seansidagi ilova va ularning maʼlumotlari tozalanadi"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Chiqish"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Mehmon faoliyati saqlansinmi?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Joriy seansdagi faoliyatni saqlash yoki barcha ilova va maʼlumotlarni tozalash mumkin"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Oʻchirish"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Saqlash"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Mehmon rejimidan chiqish"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Mehmon seansini tiklash"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Mehmon rejimidan chiqish"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Chiqish vaqtida faoliyat butunlay tozalanadi"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Chiqish vaqtida faoliyatni saqlash yoki tozalash mumkin"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Faoliyat hozir tozalanib tiklanishi yoki chiqish vaqtida saqlanishi yoki tozalanishi mumkin"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Suratga olish"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Rasm tanlash"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Surat tanlash"</string>
diff --git a/packages/SettingsLib/res/values-vi/strings.xml b/packages/SettingsLib/res/values-vi/strings.xml
index 1965942b82e8..904d2a4afeab 100644
--- a/packages/SettingsLib/res/values-vi/strings.xml
+++ b/packages/SettingsLib/res/values-vi/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Đặt lại"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Xoá"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Đang đặt lại phiên khách…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Đặt lại phiên khách?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Thao tác này sẽ bắt đầu một phiên khách mới và xoá mọi ứng dụng cũng như dữ liệu trong phiên hiện tại"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Thoát khỏi chế độ khách?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Thao tác này sẽ xoá các ứng dụng và dữ liệu trong phiên khách hiện tại"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Thoát"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Lưu hoạt động ở chế độ khách?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Bạn có thể lưu hoạt động trong phiên hiện tại hoặc xoá mọi ứng dụng và dữ liệu"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Xoá"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Lưu"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Thoát khỏi chế độ khách"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Đặt lại phiên khách"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Thoát khỏi chế độ khách"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Mọi hoạt động sẽ bị xoá khi thoát"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Bạn có thể lưu hoặc xoá hoạt động của mình khi thoát"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Đặt lại để xoá hoạt động trong phiên ngay bây giờ, hoặc bạn có thể lưu hoặc xoá hoạt động khi thoát"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Chụp ảnh"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Chọn một hình ảnh"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Chọn ảnh"</string>
diff --git a/packages/SettingsLib/res/values-zh-rCN/strings.xml b/packages/SettingsLib/res/values-zh-rCN/strings.xml
index 9bb725a119d9..a6f738f0ac1d 100644
--- a/packages/SettingsLib/res/values-zh-rCN/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rCN/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"重置"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"移除"</string>
<string name="guest_resetting" msgid="7822120170191509566">"正在重置访客会话…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"要重置访客会话吗?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"此操作会开始新的访客会话,并删除当前会话中的所有应用和数据"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"要退出访客模式吗?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"此操作会删除当前访客会话中的所有应用和数据"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"退出"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"要保存访客活动记录吗?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"您可以保存当前会话中的活动记录,也可以删除所有应用和数据"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"删除"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"保存"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"退出访客模式"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"重置访客会话"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"退出访客模式"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"系统会在退出时删除所有活动记录"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"您可以在退出时保存或删除您的活动记录"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"请立即重置以删除会话活动记录;或者,您也可以在退出时保存或删除活动记录"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"拍摄照片"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"选择图片"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"选择照片"</string>
diff --git a/packages/SettingsLib/res/values-zh-rHK/strings.xml b/packages/SettingsLib/res/values-zh-rHK/strings.xml
index 726827cc1054..f334cc84e484 100644
--- a/packages/SettingsLib/res/values-zh-rHK/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rHK/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"重設"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"移除"</string>
<string name="guest_resetting" msgid="7822120170191509566">"正在重設訪客…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"要重設訪客工作階段嗎?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"此操作會開始新的訪客工作階段,並刪除目前工作階段中的所有應用程式和活動"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"要結束訪客模式嗎?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"此操作會刪除目前訪客工作階段中的所有應用程式和資料"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"結束"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"要儲存訪客活動嗎?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"您可儲存目前工作階段中的活動或刪除所有應用程式和資料"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"刪除"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"儲存"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"結束訪客模式"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"重設訪客工作階段"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"結束訪客模式"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"結束時將會刪除所有活動"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"您可以在結束時儲存或刪除活動"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"重設即可立即刪除工作階段活動,或在結束時儲存或刪除工作階段"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"揀相"</string>
@@ -655,8 +670,8 @@
<string name="bt_le_audio_scan_qr_code" msgid="3521809854780392679">"掃瞄 QR 碼"</string>
<string name="bt_le_audio_scan_qr_code_scanner" msgid="4679500020630341107">"如要開始收聽,請將掃瞄器對準下方的 QR 碼"</string>
<string name="bt_le_audio_qr_code_is_not_valid_format" msgid="6092191081849434734">"QR 碼格式無效"</string>
- <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止播送「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
- <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如果播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止播送目前的內容"</string>
- <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"播送「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string>
+ <string name="bt_le_audio_broadcast_dialog_title" msgid="5392738488989777074">"要停止廣播「<xliff:g id="APP_NAME">%1$s</xliff:g>」的內容嗎?"</string>
+ <string name="bt_le_audio_broadcast_dialog_sub_title" msgid="268234802198852753">"如要廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容或變更輸出來源,系統就會停止廣播目前的內容"</string>
+ <string name="bt_le_audio_broadcast_dialog_switch_app" msgid="5749813313369517812">"廣播「<xliff:g id="SWITCHAPP">%1$s</xliff:g>」的內容"</string>
<string name="bt_le_audio_broadcast_dialog_different_output" msgid="2638402023060391333">"變更輸出來源"</string>
</resources>
diff --git a/packages/SettingsLib/res/values-zh-rTW/strings.xml b/packages/SettingsLib/res/values-zh-rTW/strings.xml
index f77e88905743..9a4cc5b66a06 100644
--- a/packages/SettingsLib/res/values-zh-rTW/strings.xml
+++ b/packages/SettingsLib/res/values-zh-rTW/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"重設"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"移除"</string>
<string name="guest_resetting" msgid="7822120170191509566">"正在重設訪客…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"要重設訪客工作階段嗎?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"這麼做將開始新的訪客工作階段,並刪除目前工作階段中的所有應用程式和資料"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"要結束訪客模式嗎?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"這麼做將刪除目前訪客工作階段中的所有應用程式和資料"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"結束"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"要儲存訪客活動嗎?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"你可以儲存目前工作階段中的活動,也可以刪除所有應用程式和資料"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"刪除"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"儲存"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"結束訪客模式"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"重設訪客工作階段"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"結束訪客模式"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"結束時將刪除所有活動"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"你可以在結束時儲存或刪除活動"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"重設即可立即刪除工作階段活動,你也可以在結束時儲存或刪除活動"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"拍照"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"選擇圖片"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"選取相片"</string>
diff --git a/packages/SettingsLib/res/values-zu/strings.xml b/packages/SettingsLib/res/values-zu/strings.xml
index 1cb1fa443e15..45c4d6748d02 100644
--- a/packages/SettingsLib/res/values-zu/strings.xml
+++ b/packages/SettingsLib/res/values-zu/strings.xml
@@ -598,6 +598,21 @@
<string name="guest_reset_guest_confirm_button" msgid="2989915693215617237">"Setha kabusha"</string>
<string name="guest_remove_guest_confirm_button" msgid="7858123434954143879">"Susa"</string>
<string name="guest_resetting" msgid="7822120170191509566">"Ukusetha kabusha isimenywa…"</string>
+ <string name="guest_reset_and_restart_dialog_title" msgid="3396657008451616041">"Sesha kabusha isikhathi sesihambeli?"</string>
+ <string name="guest_reset_and_restart_dialog_message" msgid="2764425635305200790">"Lokhu kuzoqala isikhathi sesihambeli esisha futhi kusule wonke ama-app nedatha kusuka esikhathini samanje"</string>
+ <string name="guest_exit_dialog_title" msgid="1846494656849381804">"Phuma kumodi yesihambeli?"</string>
+ <string name="guest_exit_dialog_message" msgid="1743218864242719783">"Lokhu kuzosula ama-app nedatha kusuka esikhathini sesihambeli samanje"</string>
+ <string name="guest_exit_dialog_button" msgid="1736401897067442044">"Phuma"</string>
+ <string name="guest_exit_dialog_title_non_ephemeral" msgid="7675327443743162986">"Londoloza umsebenzi wesihambeli?"</string>
+ <string name="guest_exit_dialog_message_non_ephemeral" msgid="223385323235719442">"Ungalondoloza umsebenzi kusuka esikhathini samanje noma usule wonke ama-app nedatha"</string>
+ <string name="guest_exit_clear_data_button" msgid="3425812652180679014">"Sula"</string>
+ <string name="guest_exit_save_data_button" msgid="3690974510644963547">"Londoloza"</string>
+ <string name="guest_exit_button" msgid="5774985819191803960">"Phuma kumodi yesivakashi"</string>
+ <string name="guest_reset_button" msgid="2515069346223503479">"Setha kabusha isikhathi sesihambeli"</string>
+ <string name="guest_exit_quick_settings_button" msgid="1912362095913765471">"Phuma kusivakashi"</string>
+ <string name="guest_notification_ephemeral" msgid="7263252466950923871">"Wonke umsebenzi uzosulwa lapho uphuma"</string>
+ <string name="guest_notification_non_ephemeral" msgid="6843799963012259330">"Ungalondoloza noma usule umsebenzi wakho lapho uphuma"</string>
+ <string name="guest_notification_non_ephemeral_non_first_login" msgid="8009307983766934876">"Setha kabusha ukuze usule umsebenzi wesikhathi manje, noma ungalondoloza noma usule umsebenzi lapho uphuma"</string>
<string name="user_image_take_photo" msgid="467512954561638530">"Thatha isithombe"</string>
<string name="user_image_choose_photo" msgid="1363820919146782908">"Khetha isithombe"</string>
<string name="user_image_photo_selector" msgid="433658323306627093">"Khetha isithombe"</string>
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 331dfae9f61b..f1745ec1bbd3 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -246,9 +246,9 @@
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (accessing Internet through remote device). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan">Internet access</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PBAP profile. [CHAR LIMIT=40] -->
- <string name="bluetooth_profile_pbap">Contact sharing</string>
+ <string name="bluetooth_profile_pbap">Contacts and call history sharing</string>
<!-- Bluetooth settings. The user-visible summary string that is used whenever referring to the PBAP profile (sharing contacts). [CHAR LIMIT=60] -->
- <string name="bluetooth_profile_pbap_summary">Use for contact sharing</string>
+ <string name="bluetooth_profile_pbap_summary">Use for contacts and call history sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the PAN profile (sharing this device's Internet connection). [CHAR LIMIT=40] -->
<string name="bluetooth_profile_pan_nap">Internet connection sharing</string>
<!-- Bluetooth settings. The user-visible string that is used whenever referring to the map profile. -->
@@ -1134,6 +1134,8 @@
<string name="battery_info_status_charging_slow">Charging slowly</string>
<!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when charging wirelessly. -->
<string name="battery_info_status_charging_wireless">Charging wirelessly</string>
+ <!-- [CHAR_LIMIT=20] Battery use screen. Battery status shown in chart label when the device is dock charging. -->
+ <string name="battery_info_status_charging_dock">Charging Dock</string>
<!-- Battery Info screen. Value for a status item. Used for diagnostic info screens, precise translation isn't needed -->
<string name="battery_info_status_discharging">Not charging</string>
<!-- Battery Info screen. Value for a status item. A state which device is connected with any charger(e.g. USB, Adapter or Wireless) but not charging yet. Used for diagnostic info screens, precise translation isn't needed -->
@@ -1438,6 +1440,44 @@
<string name="guest_remove_guest_confirm_button">Remove</string>
<!-- Status message indicating the device is in the process of resetting the guest user. [CHAR_LIMIT=NONE] -->
<string name="guest_resetting">Resetting guest\u2026</string>
+ <!-- Dialog title on action reset and restart guest [CHAR LIMIT=60] -->
+ <string name="guest_reset_and_restart_dialog_title">Reset guest session?</string>
+ <!-- Dialog message on action reset and restart guest [CHAR LIMIT=160] -->
+ <string name="guest_reset_and_restart_dialog_message">This will start a new guest
+ session and delete all apps and data from the current session</string>
+ <!-- Dialog title on action exit guest (ephemeral guest) [CHAR LIMIT=32] -->
+ <string name="guest_exit_dialog_title">Exit guest mode?</string>
+ <!-- Dialog message on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_dialog_message">This will delete
+ apps and data from the current guest session</string>
+ <!-- Dialog button on action exit guest (ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_dialog_button">Exit</string>
+ <!-- Dialog title on action exit guest (non-ephemeral guest) [CHAR LIMIT=32] -->
+ <string name="guest_exit_dialog_title_non_ephemeral">Save guest activity?</string>
+ <!-- Dialog message on action exit guest (non-ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_dialog_message_non_ephemeral">You can save activity from
+ the current session or delete all apps and data</string>
+ <!-- Button on guest exit, clear data (non-ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_clear_data_button">Delete</string>
+ <!-- Button on guest exit, save data (non-ephemeral guest) [CHAR LIMIT=80] -->
+ <string name="guest_exit_save_data_button">Save</string>
+ <!-- Label for button in confirmation dialog when exiting guest user [CHAR LIMIT=35] -->
+ <string name="guest_exit_button">Exit guest mode</string>
+ <!-- Label for button in confirmation dialog when resetting guest user [CHAR LIMIT=35] -->
+ <string name="guest_reset_button">Reset guest session</string>
+ <!-- Label for guest icon in quick settings user switcher [CHAR LIMIT=35] -->
+ <string name="guest_exit_quick_settings_button">Exit guest</string>
+ <!-- Message of the notification when guest mode is entered
+ and it's a ephemeral guest [CHAR LIMIT=60] -->
+ <string name="guest_notification_ephemeral">All activity will be deleted on exit</string>
+ <!-- Message of the notification when guest mode is entered
+ and it's not a ephemeral guest and it's a first time guest login [CHAR LIMIT=60] -->
+ <string name="guest_notification_non_ephemeral">You can save or delete your activity on exit</string>
+ <!-- Message of the notification when guest mode is entered
+ and it's not a ephemeral guest and it's not a first time guest login [CHAR LIMIT=NONE] -->
+ <string name="guest_notification_non_ephemeral_non_first_login">Reset to delete session
+ activity now, or you can save or delete activity on exit</string>
+
<!-- An option in a photo selection dialog to take a new photo [CHAR LIMIT=50] -->
<string name="user_image_take_photo">Take a photo</string>
<!-- An option in a photo selection dialog to choose a pre-existing image [CHAR LIMIT=50] -->
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index feb4212035bc..b9c4030d9d0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -239,6 +239,8 @@ public class Utils {
statusString = res.getString(R.string.battery_info_status_charging);
break;
}
+ } else if (batteryStatus.isPluggedInDock()) {
+ statusString = res.getString(R.string.battery_info_status_charging_dock);
} else {
statusString = res.getString(R.string.battery_info_status_charging_wireless);
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index bf9debf5ccce..bf6975714acd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -87,9 +87,11 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
if (DEBUG) {
Log.d(TAG, "Bluetooth service connected");
}
- mService = (BluetoothLeBroadcast) proxy;
- mIsProfileReady = true;
- registerServiceCallBack(mExecutor, mBroadcastCallback);
+ if(!mIsProfileReady) {
+ mService = (BluetoothLeBroadcast) proxy;
+ mIsProfileReady = true;
+ registerServiceCallBack(mExecutor, mBroadcastCallback);
+ }
}
@Override
@@ -97,8 +99,10 @@ public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
if (DEBUG) {
Log.d(TAG, "Bluetooth service disconnected");
}
- mIsProfileReady = false;
- unregisterServiceCallBack(mBroadcastCallback);
+ if(mIsProfileReady) {
+ mIsProfileReady = false;
+ unregisterServiceCallBack(mBroadcastCallback);
+ }
}
};
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index 58944f653869..4714ff93625b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -576,6 +576,15 @@ public class LocalBluetoothProfileManager {
return;
}
+ // The profiles list's sequence will affect the bluetooth icon at
+ // BluetoothUtils.getBtClassDrawableWithDescription(Context,CachedBluetoothDevice).
+
+ // Moving the LE audio profile to be the first priority if the device supports LE audio.
+ if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
+ profiles.add(mLeAudioProfile);
+ removedProfiles.remove(mLeAudioProfile);
+ }
+
if (mHeadsetProfile != null) {
if ((ArrayUtils.contains(localUuids, BluetoothUuid.HSP_AG)
&& ArrayUtils.contains(uuids, BluetoothUuid.HSP))
@@ -660,11 +669,6 @@ public class LocalBluetoothProfileManager {
removedProfiles.remove(mHearingAidProfile);
}
- if (ArrayUtils.contains(uuids, BluetoothUuid.LE_AUDIO) && mLeAudioProfile != null) {
- profiles.add(mLeAudioProfile);
- removedProfiles.remove(mLeAudioProfile);
- }
-
if (mSapProfile != null && ArrayUtils.contains(uuids, BluetoothUuid.SAP)) {
profiles.add(mSapProfile);
removedProfiles.remove(mSapProfile);
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index 4939e04dad6a..132a631e25cc 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -135,7 +135,7 @@ public class BatteryStatus {
* @return true if the device is charged
*/
public boolean isCharged() {
- return status == BATTERY_STATUS_FULL || level >= 100;
+ return isCharged(status, level);
}
/**
@@ -177,4 +177,31 @@ public class BatteryStatus {
return "BatteryStatus{status=" + status + ",level=" + level + ",plugged=" + plugged
+ ",health=" + health + ",maxChargingWattage=" + maxChargingWattage + "}";
}
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ *
+ * @param batteryChangedIntent ACTION_BATTERY_CHANGED intent
+ * @return true if the device is charged
+ */
+ public static boolean isCharged(Intent batteryChangedIntent) {
+ int status = batteryChangedIntent.getIntExtra(EXTRA_STATUS, BATTERY_STATUS_UNKNOWN);
+ int level = batteryChangedIntent.getIntExtra(EXTRA_LEVEL, 0);
+ return isCharged(status, level);
+ }
+
+ /**
+ * Whether or not the device is charged. Note that some devices never return 100% for
+ * battery level, so this allows either battery level or status to determine if the
+ * battery is charged.
+ *
+ * @param status values for "status" field in the ACTION_BATTERY_CHANGED Intent
+ * @param level values from 0 to 100
+ * @return true if the device is charged
+ */
+ public static boolean isCharged(int status, int level) {
+ return status == BATTERY_STATUS_FULL || level >= 100;
+ }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
index d6d73046bed3..f4355c39819f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/LocalMediaManager.java
@@ -22,6 +22,7 @@ import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
import android.media.RoutingSessionInfo;
import android.os.Build;
import android.text.TextUtils;
@@ -83,6 +84,8 @@ public class LocalMediaManager implements BluetoothCallback {
private InfoMediaManager mInfoMediaManager;
private String mPackageName;
private MediaDevice mOnTransferBluetoothDevice;
+ @VisibleForTesting
+ AudioManager mAudioManager;
@VisibleForTesting
List<MediaDevice> mMediaDevices = new CopyOnWriteArrayList<>();
@@ -126,6 +129,7 @@ public class LocalMediaManager implements BluetoothCallback {
mPackageName = packageName;
mLocalBluetoothManager =
LocalBluetoothManager.getInstance(context, /* onInitCallback= */ null);
+ mAudioManager = context.getSystemService(AudioManager.class);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mLocalBluetoothManager == null) {
Log.e(TAG, "Bluetooth is not supported on this device");
@@ -148,6 +152,7 @@ public class LocalMediaManager implements BluetoothCallback {
mInfoMediaManager = infoMediaManager;
mPackageName = packageName;
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mAudioManager = context.getSystemService(AudioManager.class);
}
/**
@@ -527,13 +532,16 @@ public class LocalMediaManager implements BluetoothCallback {
synchronized (mMediaDevicesLock) {
mMediaDevices.clear();
mMediaDevices.addAll(devices);
- // Add disconnected bluetooth devices only when phone output device is available.
+ // Add muting expected bluetooth devices only when phone output device is available.
for (MediaDevice device : devices) {
final int type = device.getDeviceType();
if (type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE
|| type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
|| type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE) {
- mMediaDevices.addAll(buildDisconnectedBluetoothDevice());
+ MediaDevice mutingExpectedDevice = getMutingExpectedDevice();
+ if (mutingExpectedDevice != null) {
+ mMediaDevices.add(mutingExpectedDevice);
+ }
break;
}
}
@@ -552,6 +560,34 @@ public class LocalMediaManager implements BluetoothCallback {
}
}
+ private MediaDevice getMutingExpectedDevice() {
+ if (mBluetoothAdapter == null
+ || mAudioManager.getMutingExpectedDevice() == null) {
+ Log.w(TAG, "BluetoothAdapter is null or muting expected device not exist");
+ return null;
+ }
+ final List<BluetoothDevice> bluetoothDevices =
+ mBluetoothAdapter.getMostRecentlyConnectedDevices();
+ final CachedBluetoothDeviceManager cachedDeviceManager =
+ mLocalBluetoothManager.getCachedDeviceManager();
+ for (BluetoothDevice device : bluetoothDevices) {
+ final CachedBluetoothDevice cachedDevice =
+ cachedDeviceManager.findDevice(device);
+ if (isBondedMediaDevice(cachedDevice) && isMutingExpectedDevice(cachedDevice)) {
+ return new BluetoothMediaDevice(mContext,
+ cachedDevice,
+ null, null, mPackageName);
+ }
+ }
+ return null;
+ }
+
+ private boolean isMutingExpectedDevice(CachedBluetoothDevice cachedDevice) {
+ return mAudioManager.getMutingExpectedDevice() != null
+ && cachedDevice.getAddress().equals(
+ mAudioManager.getMutingExpectedDevice().getAddress());
+ }
+
private List<MediaDevice> buildDisconnectedBluetoothDevice() {
if (mBluetoothAdapter == null) {
Log.w(TAG, "buildDisconnectedBluetoothDevice() BluetoothAdapter is null");
@@ -595,6 +631,13 @@ public class LocalMediaManager implements BluetoothCallback {
return new ArrayList<>(mDisconnectedMediaDevices);
}
+ private boolean isBondedMediaDevice(CachedBluetoothDevice cachedDevice) {
+ return cachedDevice != null
+ && cachedDevice.getBondState() == BluetoothDevice.BOND_BONDED
+ && !cachedDevice.isConnected()
+ && isMediaDevice(cachedDevice);
+ }
+
private boolean isMediaDevice(CachedBluetoothDevice device) {
for (LocalBluetoothProfile profile : device.getConnectableProfiles()) {
if (profile instanceof A2dpProfile || profile instanceof HearingAidProfile ||
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
index 4332bd24e1d1..45c0d7823e99 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStatusTracker.java
@@ -57,6 +57,7 @@ public class WifiStatusTracker {
private final Handler mHandler;
private final Handler mMainThreadHandler;
private final Set<Integer> mNetworks = new HashSet<>();
+ private int mPrimaryNetworkId;
// Save the previous HISTORY_SIZE states for logging.
private final String[] mHistory = new String[HISTORY_SIZE];
// Where to copy the next state into.
@@ -106,6 +107,7 @@ public class WifiStatusTracker {
if (!mNetworks.contains(network.getNetId())) {
mNetworks.add(network.getNetId());
}
+ mPrimaryNetworkId = network.getNetId();
updateWifiInfo(wifiInfo);
updateStatusLabel();
mMainThreadHandler.post(() -> postResults());
@@ -121,10 +123,13 @@ public class WifiStatusTracker {
recordLastWifiNetwork(log);
if (mNetworks.contains(network.getNetId())) {
mNetworks.remove(network.getNetId());
- updateWifiInfo(null);
- updateStatusLabel();
- mMainThreadHandler.post(() -> postResults());
}
+ if (network.getNetId() != mPrimaryNetworkId) {
+ return;
+ }
+ updateWifiInfo(null);
+ updateStatusLabel();
+ mMainThreadHandler.post(() -> postResults());
}
};
private final NetworkCallback mDefaultNetworkCallback =
diff --git a/packages/SettingsLib/tests/integ/Android.bp b/packages/SettingsLib/tests/integ/Android.bp
index 64563be78fbb..d463170fae8c 100644
--- a/packages/SettingsLib/tests/integ/Android.bp
+++ b/packages/SettingsLib/tests/integ/Android.bp
@@ -25,7 +25,7 @@ android_test {
name: "SettingsLibTests",
defaults: [
"SettingsLibDefaults",
- "framework-wifi-test-defaults"
+ "framework-wifi-test-defaults",
],
certificate: "platform",
@@ -47,6 +47,7 @@ android_test {
"androidx.test.espresso.core",
"mockito-target-minus-junit4",
"truth-prebuilt",
+ "SettingsLibDeviceStateRotationLock",
"SettingsLibSettingsSpinner",
"SettingsLibUsageProgressBarPreference",
],
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 09b2a2e73c5b..336cdd3f259f 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -365,6 +365,17 @@ public class UtilsTest {
}
@Test
+ public void getBatteryStatus_chargingDock_returnDockChargingString() {
+ final Intent intent = new Intent();
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
+ final Resources resources = mContext.getResources();
+
+ assertThat(Utils.getBatteryStatus(mContext, intent, /* compactStatus= */ false)).isEqualTo(
+ resources.getString(R.string.battery_info_status_charging_dock));
+ }
+
+ @Test
public void getBatteryStatus_chargingWireless_returnWirelessChargingString() {
final Intent intent = new Intent();
intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
index 8e850b25159c..24bb1bc9ac67 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/LocalMediaManagerTest.java
@@ -16,7 +16,6 @@
package com.android.settingslib.media;
-import static android.bluetooth.BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES;
import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
import static com.google.common.truth.Truth.assertThat;
@@ -29,10 +28,12 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.content.Context;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioManager;
+import android.media.AudioSystem;
import android.media.MediaRoute2Info;
import android.media.MediaRouter2Manager;
import android.media.RoutingSessionInfo;
@@ -71,6 +72,7 @@ public class LocalMediaManagerTest {
private static final String TEST_CURRENT_DEVICE_ID = "currentDevice_id";
private static final String TEST_PACKAGE_NAME = "com.test.playmusic";
private static final String TEST_SESSION_ID = "session_id";
+ private static final String TEST_ADDRESS = "00:01:02:03:04:05";
@Mock
private InfoMediaManager mInfoMediaManager;
@@ -90,6 +92,8 @@ public class LocalMediaManagerTest {
private MediaRoute2Info mRouteInfo1;
@Mock
private MediaRoute2Info mRouteInfo2;
+ @Mock
+ private AudioManager mAudioManager;
private Context mContext;
private LocalMediaManager mLocalMediaManager;
@@ -118,6 +122,7 @@ public class LocalMediaManagerTest {
TEST_PACKAGE_NAME);
mLocalMediaManager = new LocalMediaManager(mContext, mLocalBluetoothManager,
mInfoMediaManager, "com.test.packagename");
+ mLocalMediaManager.mAudioManager = mAudioManager;
}
@Test
@@ -551,16 +556,12 @@ public class LocalMediaManagerTest {
}
@Test
- public void onDeviceListAdded_haveDisconnectedDevice_addDisconnectedDevice() {
+ public void onDeviceListAdded_haveMutingExpectedDevice_addMutingExpectedDevice() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
- final MediaDevice device2 = mock(MediaDevice.class);
- final MediaDevice device3 = mock(MediaDevice.class);
mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
devices.add(device1);
- devices.add(device2);
- mLocalMediaManager.mMediaDevices.add(device3);
- mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
+ devices.add(mLocalMediaManager.mPhoneDevice);
final List<LocalBluetoothProfile> profiles = new ArrayList<>();
final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
@@ -573,22 +574,25 @@ public class LocalMediaManagerTest {
bluetoothDevices.add(bluetoothDevice);
mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
+ AudioDeviceAttributes audioDeviceAttributes = new AudioDeviceAttributes(
+ AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, TEST_ADDRESS);
+
+ when(mAudioManager.getMutingExpectedDevice()).thenReturn(audioDeviceAttributes);
when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
when(cachedDevice.isConnected()).thenReturn(false);
when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
+ when(cachedDevice.getAddress()).thenReturn(TEST_ADDRESS);
when(mA2dpProfile.getActiveDevice()).thenReturn(bluetoothDevice);
when(mHapProfile.getActiveDevices()).thenReturn(new ArrayList<>());
when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
+ assertThat(mLocalMediaManager.mMediaDevices).hasSize(0);
mLocalMediaManager.registerCallback(mCallback);
mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
@@ -634,68 +638,6 @@ public class LocalMediaManagerTest {
}
@Test
- public void onDeviceListAdded_haveDisconnectedDevice_list5DisconnectedDevice() {
- final List<MediaDevice> devices = new ArrayList<>();
- final MediaDevice device1 = mock(MediaDevice.class);
- final MediaDevice device2 = mock(MediaDevice.class);
- final MediaDevice device3 = mock(MediaDevice.class);
- mLocalMediaManager.mPhoneDevice = mock(PhoneMediaDevice.class);
- devices.add(device1);
- devices.add(device2);
- mLocalMediaManager.mMediaDevices.add(device3);
- mLocalMediaManager.mMediaDevices.add(mLocalMediaManager.mPhoneDevice);
-
- final List<LocalBluetoothProfile> profiles = new ArrayList<>();
- final A2dpProfile a2dpProfile = mock(A2dpProfile.class);
- profiles.add(a2dpProfile);
-
- final List<BluetoothDevice> bluetoothDevices = new ArrayList<>();
- final BluetoothDevice bluetoothDevice = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice2 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice3 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice4 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice5 = mock(BluetoothDevice.class);
- final BluetoothDevice bluetoothDevice6 = mock(BluetoothDevice.class);
- final BluetoothClass bluetoothClass = mock(BluetoothClass.class);
- final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class);
- final CachedBluetoothDeviceManager cachedManager = mock(CachedBluetoothDeviceManager.class);
- bluetoothDevices.add(bluetoothDevice);
- bluetoothDevices.add(bluetoothDevice2);
- bluetoothDevices.add(bluetoothDevice3);
- bluetoothDevices.add(bluetoothDevice4);
- bluetoothDevices.add(bluetoothDevice5);
- bluetoothDevices.add(bluetoothDevice6);
- mShadowBluetoothAdapter.setMostRecentlyConnectedDevices(bluetoothDevices);
-
- when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(cachedManager);
- when(cachedManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice2)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice3)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice4)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice5)).thenReturn(cachedDevice);
- when(cachedManager.findDevice(bluetoothDevice6)).thenReturn(cachedDevice);
- when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
- when(cachedDevice.isConnected()).thenReturn(false);
- when(cachedDevice.getDevice()).thenReturn(bluetoothDevice);
- when(cachedDevice.getConnectableProfiles()).thenReturn(profiles);
- when(bluetoothDevice.getBluetoothClass()).thenReturn(bluetoothClass);
- when(bluetoothClass.getDeviceClass()).thenReturn(AUDIO_VIDEO_HEADPHONES);
-
- when(device1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(device2.getId()).thenReturn(TEST_DEVICE_ID_2);
- when(device3.getId()).thenReturn(TEST_DEVICE_ID_3);
- when(device1.getDeviceType()).thenReturn(MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
- when(mLocalMediaManager.mPhoneDevice.getId()).thenReturn("test_phone_id");
-
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(2);
- mLocalMediaManager.registerCallback(mCallback);
- mLocalMediaManager.mMediaDeviceCallback.onDeviceListAdded(devices);
-
- assertThat(mLocalMediaManager.mMediaDevices).hasSize(7);
- verify(mCallback).onDeviceListUpdate(any());
- }
-
- @Test
public void onDeviceListAdded_bluetoothAdapterIsNull_noDisconnectedDeviceAdded() {
final List<MediaDevice> devices = new ArrayList<>();
final MediaDevice device1 = mock(MediaDevice.class);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
new file mode 100644
index 000000000000..dc7e313dfcfa
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/wifi/WifiStatusTrackerTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.wifi;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkScoreManager;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class WifiStatusTrackerTest {
+ @Mock Context mContext;
+ @Mock WifiManager mWifiManager;
+ @Mock NetworkScoreManager mNetworkScoreManager;
+ @Mock ConnectivityManager mConnectivityManager;
+ @Mock Runnable mCallback;
+
+ private final ArgumentCaptor<ConnectivityManager.NetworkCallback>
+ mNetworkCallbackCaptor =
+ ArgumentCaptor.forClass(ConnectivityManager.NetworkCallback.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ /**
+ * Verify that we only clear the WifiInfo if the primary network was lost.
+ */
+ @Test
+ public void testWifiInfoClearedOnPrimaryNetworkLost() {
+ WifiStatusTracker wifiStatusTracker = new WifiStatusTracker(mContext, mWifiManager,
+ mNetworkScoreManager, mConnectivityManager, mCallback);
+ wifiStatusTracker.setListening(true);
+
+ verify(mConnectivityManager)
+ .registerNetworkCallback(any(), mNetworkCallbackCaptor.capture(), any());
+
+ // Trigger a validation callback for the primary Wifi network.
+ WifiInfo primaryWifiInfo = Mockito.mock(WifiInfo.class);
+ when(primaryWifiInfo.makeCopy(anyLong())).thenReturn(primaryWifiInfo);
+ when(primaryWifiInfo.isPrimary()).thenReturn(true);
+ int primaryRssi = -55;
+ when(primaryWifiInfo.getRssi()).thenReturn(primaryRssi);
+ NetworkCapabilities primaryCap = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(primaryWifiInfo)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build();
+ Network primaryNetwork = Mockito.mock(Network.class);
+ int primaryNetworkId = 1;
+ when(primaryNetwork.getNetId()).thenReturn(primaryNetworkId);
+ mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(primaryNetwork, primaryCap);
+
+ // Verify primary wifi info is the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Trigger a validation callback for the non-primary Wifi network.
+ WifiInfo nonPrimaryWifiInfo = Mockito.mock(WifiInfo.class);
+ when(nonPrimaryWifiInfo.makeCopy(anyLong())).thenReturn(nonPrimaryWifiInfo);
+ when(nonPrimaryWifiInfo.isPrimary()).thenReturn(false);
+ int nonPrimaryRssi = -75;
+ when(primaryWifiInfo.getRssi()).thenReturn(nonPrimaryRssi);
+ NetworkCapabilities nonPrimaryCap = new NetworkCapabilities.Builder()
+ .addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
+ .setTransportInfo(nonPrimaryWifiInfo)
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ .build();
+ Network nonPrimaryNetwork = Mockito.mock(Network.class);
+ int nonPrimaryNetworkId = 2;
+ when(nonPrimaryNetwork.getNetId()).thenReturn(nonPrimaryNetworkId);
+ mNetworkCallbackCaptor.getValue().onCapabilitiesChanged(nonPrimaryNetwork, nonPrimaryCap);
+
+ // Verify primary wifi info is still the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Lose the non-primary network.
+ mNetworkCallbackCaptor.getValue().onLost(nonPrimaryNetwork);
+
+ // Verify primary wifi info is still the one being used.
+ assertThat(wifiStatusTracker.connected).isTrue();
+ assertThat(wifiStatusTracker.rssi).isEqualTo(primaryRssi);
+
+ // Lose the primary network.
+ mNetworkCallbackCaptor.getValue().onLost(primaryNetwork);
+
+ // Verify we aren't connected anymore.
+ assertThat(wifiStatusTracker.connected).isFalse();
+ }
+}
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index f490c87aa4ec..4a104276c18f 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -32,6 +32,7 @@ android_app {
],
static_libs: [
"junit",
+ "SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayDensityUtils",
],
platform_apis: true,
@@ -55,6 +56,7 @@ android_test {
static_libs: [
"androidx.test.rules",
"mockito-target-minus-junit4",
+ "SettingsLibDeviceStateRotationLock",
"SettingsLibDisplayDensityUtils",
"platform-test-annotations",
"truth-prebuilt",
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
index e425790110aa..3cb143966a7c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/DeviceSpecificSettings.java
@@ -32,5 +32,6 @@ public class DeviceSpecificSettings {
*/
public static final String[] DEVICE_SPECIFIC_SETTINGS_TO_BACKUP = {
Settings.Secure.DISPLAY_DENSITY_FORCED,
+ Settings.Secure.DEVICE_STATE_ROTATION_LOCK
};
}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
index dc166b4c2349..162bb2c720e8 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/GlobalSettings.java
@@ -78,6 +78,7 @@ public class GlobalSettings {
Settings.Global.POWER_BUTTON_LONG_PRESS,
Settings.Global.AUTOMATIC_POWER_SAVE_MODE,
Settings.Global.ADVANCED_BATTERY_USAGE_AMOUNT,
+ Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
Settings.Global.POWER_BUTTON_LONG_PRESS_DURATION_MS,
Settings.Global.USER_PREFERRED_REFRESH_RATE,
Settings.Global.USER_PREFERRED_RESOLUTION_HEIGHT,
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 5eaf553a2047..d1f10a640dbf 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -37,6 +37,7 @@ public class SecureSettings {
Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED,
Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED,
+ Settings.Secure.ADAPTIVE_CHARGING_ENABLED,
Settings.Secure.ADAPTIVE_SLEEP,
Settings.Secure.CAMERA_AUTOROTATE,
Settings.Secure.AUTOFILL_SERVICE,
@@ -117,6 +118,10 @@ public class SecureSettings {
Settings.Secure.FACE_UNLOCK_DISMISSES_KEYGUARD,
Settings.Secure.FACE_UNLOCK_APP_ENABLED,
Settings.Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
+ Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
index acb33c356a8b..fbbdd32c5886 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java
@@ -51,6 +51,7 @@ public class GlobalSettingsValidators {
|| (val == BatteryManager.BATTERY_PLUGGED_AC)
|| (val == BatteryManager.BATTERY_PLUGGED_USB)
|| (val == BatteryManager.BATTERY_PLUGGED_WIRELESS)
+ || (val == BatteryManager.BATTERY_PLUGGED_DOCK)
|| (val
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_USB))
@@ -58,12 +59,29 @@ public class GlobalSettingsValidators {
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_WIRELESS))
|| (val
+ == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
== (BatteryManager.BATTERY_PLUGGED_USB
| BatteryManager.BATTERY_PLUGGED_WIRELESS))
|| (val
+ == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_WIRELESS
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
== (BatteryManager.BATTERY_PLUGGED_AC
| BatteryManager.BATTERY_PLUGGED_USB
- | BatteryManager.BATTERY_PLUGGED_WIRELESS));
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_AC
+ | BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_DOCK))
+ || (val
+ == (BatteryManager.BATTERY_PLUGGED_USB
+ | BatteryManager.BATTERY_PLUGGED_WIRELESS
+ | BatteryManager.BATTERY_PLUGGED_DOCK));
} catch (NumberFormatException e) {
return false;
}
@@ -155,6 +173,7 @@ public class GlobalSettingsValidators {
VALIDATORS.put(Global.DEVICE_CONFIG_SYNC_DISABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.AUTOMATIC_POWER_SAVE_MODE, ANY_INTEGER_VALIDATOR);
VALIDATORS.put(Global.ADVANCED_BATTERY_USAGE_AMOUNT, PERCENTAGE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Global.POWER_BUTTON_LONG_PRESS_DURATION_MS, NONE_NEGATIVE_LONG_VALIDATOR);
VALIDATORS.put(Global.Wearable.HAS_PAY_TOKENS, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 9ee7b654046f..4aadf72930aa 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -59,6 +59,7 @@ public class SecureSettingsValidators {
VALIDATORS.put(Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(
Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_NAVBAR_ENABLED, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.ADAPTIVE_CHARGING_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.ADAPTIVE_SLEEP, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.CAMERA_AUTOROTATE, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.AUTOFILL_SERVICE, NULLABLE_COMPONENT_NAME_VALIDATOR);
@@ -171,6 +172,11 @@ public class SecureSettingsValidators {
Secure.AUTOMATIC_STORAGE_MANAGER_DAYS_TO_RETAIN, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_KEYGUARD_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_DISMISSES_KEYGUARD, BOOLEAN_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
+ NON_NEGATIVE_INTEGER_VALIDATOR);
+ VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
index 440bb67dc788..808ea9ede9dc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsHelper.java
@@ -41,6 +41,7 @@ import android.util.ArraySet;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.LocalePicker;
+import com.android.settingslib.devicestate.DeviceStateRotationLockSettingsManager;
import java.util.ArrayList;
import java.util.HashMap;
@@ -200,6 +201,9 @@ public class SettingsHelper {
} else if (Settings.Global.POWER_BUTTON_LONG_PRESS.equals(name)) {
setLongPressPowerBehavior(cr, value);
return;
+ } else if (Settings.System.ACCELEROMETER_ROTATION.equals(name)
+ && shouldSkipAutoRotateRestore()) {
+ return;
}
// Default case: write the restored value to settings
@@ -236,6 +240,12 @@ public class SettingsHelper {
}
}
+ private boolean shouldSkipAutoRotateRestore() {
+ // When device state based auto rotation settings are available, let's skip the restoring
+ // of the standard auto rotation settings to avoid conflicting setting values.
+ return DeviceStateRotationLockSettingsManager.isDeviceStateRotationLockEnabled(mContext);
+ }
+
public String onBackupValue(String name, String value) {
// Special processing for backing up ringtones & notification sounds
if (Settings.System.RINGTONE.equals(name)
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index ccfeae43e8b6..aa3a983e9971 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -2358,6 +2358,12 @@ class SettingsProtoDumpUtil {
SecureSettingsProto.PowerMenuPrivacy.SHOW);
p.end(powerMenuPrivacyToken);
+ final long extraLowPowerModeToken = p.start(SecureSettingsProto.EXTRA_LOW_POWER_MODE);
+ dumpSetting(s, p,
+ Settings.Secure.EXTRA_AUTOMATIC_POWER_SAVE_MODE,
+ SecureSettingsProto.ExtraLowPowerMode.EXTRA_AUTOMATIC_POWER_SAVE_MODE);
+ p.end(extraLowPowerModeToken);
+
final long printServiceToken = p.start(SecureSettingsProto.PRINT_SERVICE);
dumpSetting(s, p,
Settings.Secure.PRINT_SERVICE_SEARCH_URI,
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index cce515444c1f..b1ce3bf28001 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -110,7 +110,6 @@ public class SettingsBackupTest {
newHashSet(
Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
Settings.Global.ACTIVITY_STARTS_LOGGING_ENABLED,
- Settings.Global.ADAPTIVE_BATTERY_MANAGEMENT_ENABLED,
Settings.Global.ADB_ALLOWED_CONNECTION_TIME,
Settings.Global.ADB_ENABLED,
Settings.Global.ADB_WIFI_ENABLED,
@@ -422,6 +421,7 @@ public class SettingsBackupTest {
Settings.Global.RADIO_NFC,
Settings.Global.RADIO_WIFI,
Settings.Global.RADIO_WIMAX,
+ Settings.Global.REMOVE_GUEST_ON_EXIT,
Settings.Global.RECOMMENDED_NETWORK_EVALUATOR_CACHE_EXPIRY_MS,
Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT,
Settings.Global.RESTRICTED_NETWORKING_MODE,
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
index 4f7b494c0e71..ee76dbf8ce70 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsHelperTest.java
@@ -73,6 +73,8 @@ public class SettingsHelperTest {
when(mContext.getSystemService(eq(Context.TELEPHONY_SERVICE))).thenReturn(
mTelephonyManager);
when(mContext.getResources()).thenReturn(mResources);
+ when(mContext.getApplicationContext()).thenReturn(mContext);
+ when(mContext.getContentResolver()).thenReturn(getContentResolver());
mSettingsHelper = spy(new SettingsHelper(mContext));
}
@@ -305,6 +307,63 @@ public class SettingsHelperTest {
new String[] { "he-IL", "id-ID", "yi" })); // supported
}
+ @Test
+ public void restoreValue_autoRotation_deviceStateAutoRotationDisabled_restoresValue() {
+ when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
+ .thenReturn(new String[]{});
+ int previousValue = 0;
+ int newValue = 1;
+ setAutoRotationSettingValue(previousValue);
+
+ restoreAutoRotationSetting(newValue);
+
+ assertThat(getAutoRotationSettingValue()).isEqualTo(newValue);
+ }
+
+ @Test
+ public void restoreValue_autoRotation_deviceStateAutoRotationEnabled_doesNotRestoreValue() {
+ when(mResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults))
+ .thenReturn(new String[]{"0:1", "1:1"});
+ int previousValue = 0;
+ int newValue = 1;
+ setAutoRotationSettingValue(previousValue);
+
+ restoreAutoRotationSetting(newValue);
+
+ assertThat(getAutoRotationSettingValue()).isEqualTo(previousValue);
+ }
+
+ private int getAutoRotationSettingValue() {
+ return Settings.System.getInt(
+ getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION,
+ /* default= */ -1);
+ }
+
+ private void setAutoRotationSettingValue(int value) {
+ Settings.System.putInt(
+ getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION,
+ value
+ );
+ }
+
+ private void restoreAutoRotationSetting(int newValue) {
+ mSettingsHelper.restoreValue(
+ mContext,
+ getContentResolver(),
+ new ContentValues(),
+ /* destination= */ Settings.System.CONTENT_URI,
+ /* name= */ Settings.System.ACCELEROMETER_ROTATION,
+ /* value= */ String.valueOf(newValue),
+ /* restoredFromSdkInt= */ 0);
+ }
+
+ private ContentResolver getContentResolver() {
+ return InstrumentationRegistry.getInstrumentation().getTargetContext()
+ .getContentResolver();
+ }
+
private void clearLongPressPowerValues() {
ContentResolver cr = InstrumentationRegistry.getInstrumentation().getTargetContext()
.getContentResolver();
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index f05c1e2e76f2..ffd6b522e394 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -93,6 +93,7 @@ android_library {
"SystemUISharedLib",
"SystemUI-statsd",
"SettingsLib",
+ "androidx.core_core-ktx",
"androidx.viewpager2_viewpager2",
"androidx.legacy_legacy-support-v4",
"androidx.recyclerview_recyclerview",
@@ -109,6 +110,7 @@ android_library {
"androidx.arch.core_core-runtime",
"androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
@@ -217,6 +219,7 @@ android_library {
"androidx.arch.core_core-runtime",
"androidx.lifecycle_lifecycle-common-java8",
"androidx.lifecycle_lifecycle-extensions",
+ "androidx.lifecycle_lifecycle-runtime-ktx",
"androidx.dynamicanimation_dynamicanimation",
"androidx-constraintlayout_constraintlayout",
"androidx.exifinterface_exifinterface",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 6f22b49fc6a6..6edf13addbca 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -618,6 +618,17 @@
android:excludeFromRecents="true"
android:visibleToInstantApps="true"/>
+ <activity
+ android:name=".media.MediaProjectionAppSelectorActivity"
+ android:theme="@style/Theme.SystemUI.MediaProjectionAppSelector"
+ android:finishOnCloseSystemDialogs="true"
+ android:excludeFromRecents="true"
+ android:documentLaunchMode="never"
+ android:relinquishTaskIdentity="true"
+ android:configChanges=
+ "screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden"
+ android:visibleToInstantApps="true"/>
+
<!-- started from TvNotificationPanel -->
<activity
android:name=".statusbar.tv.notifications.TvNotificationPanelActivity"
@@ -713,7 +724,7 @@
<service
android:name=".dreams.DreamOverlayService"
- android:enabled="@bool/config_dreamOverlayServiceEnabled"
+ android:enabled="false"
android:exported="true" />
<activity android:name=".keyguard.WorkLockActivity"
diff --git a/packages/SystemUI/TEST_MAPPING b/packages/SystemUI/TEST_MAPPING
index edb5ee30524f..154a6fca9d09 100644
--- a/packages/SystemUI/TEST_MAPPING
+++ b/packages/SystemUI/TEST_MAPPING
@@ -52,6 +52,17 @@
]
},
{
+ "name": "SystemUIGoogleScreenshotTests",
+ "options": [
+ {
+ "exclude-annotation": "org.junit.Ignore"
+ },
+ {
+ "exclude-annotation": "androidx.test.filters.FlakyTest"
+ }
+ ]
+ },
+ {
// Permission indicators
"name": "CtsPermission4TestCases",
"options": [
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index 323db8526470..8ddd430dadbc 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -59,31 +59,31 @@ class ActivityLaunchAnimator(
companion object {
/** The timings when animating a View into an app. */
@JvmField
- val TIMINGS = LaunchAnimator.Timings(
- totalDuration = 500L,
- contentBeforeFadeOutDelay = 0L,
- contentBeforeFadeOutDuration = 150L,
- contentAfterFadeInDelay = 150L,
- contentAfterFadeInDuration = 183L
- )
+ val TIMINGS =
+ LaunchAnimator.Timings(
+ totalDuration = 500L,
+ contentBeforeFadeOutDelay = 0L,
+ contentBeforeFadeOutDuration = 150L,
+ contentAfterFadeInDelay = 150L,
+ contentAfterFadeInDuration = 183L
+ )
/**
* The timings when animating a Dialog into an app. We need to wait at least 200ms before
* showing the app (which is under the dialog window) so that the dialog window dim is fully
* faded out, to avoid flicker.
*/
- val DIALOG_TIMINGS = TIMINGS.copy(
- contentBeforeFadeOutDuration = 200L,
- contentAfterFadeInDelay = 200L
- )
+ val DIALOG_TIMINGS =
+ TIMINGS.copy(contentBeforeFadeOutDuration = 200L, contentAfterFadeInDelay = 200L)
/** The interpolators when animating a View or a dialog into an app. */
- val INTERPOLATORS = LaunchAnimator.Interpolators(
- positionInterpolator = Interpolators.EMPHASIZED,
- positionXInterpolator = createPositionXInterpolator(),
- contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
- contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
- )
+ val INTERPOLATORS =
+ LaunchAnimator.Interpolators(
+ positionInterpolator = Interpolators.EMPHASIZED,
+ positionXInterpolator = createPositionXInterpolator(),
+ contentBeforeFadeOutInterpolator = Interpolators.LINEAR_OUT_SLOW_IN,
+ contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
+ )
/** Durations & interpolators for the navigation bar fading in & out. */
private const val ANIMATION_DURATION_NAV_FADE_IN = 266L
@@ -98,11 +98,12 @@ class ActivityLaunchAnimator(
private const val LAUNCH_TIMEOUT = 1000L
private fun createPositionXInterpolator(): Interpolator {
- val path = Path().apply {
- moveTo(0f, 0f)
- cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
- cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
- }
+ val path =
+ Path().apply {
+ moveTo(0f, 0f)
+ cubicTo(0.1217f, 0.0462f, 0.15f, 0.4686f, 0.1667f, 0.66f)
+ cubicTo(0.1834f, 0.8878f, 0.1667f, 1f, 1f, 1f)
+ }
return PathInterpolator(path)
}
}
@@ -150,29 +151,37 @@ class ActivityLaunchAnimator(
return
}
- val callback = this.callback ?: throw IllegalStateException(
- "ActivityLaunchAnimator.callback must be set before using this animator")
+ val callback =
+ this.callback
+ ?: throw IllegalStateException(
+ "ActivityLaunchAnimator.callback must be set before using this animator"
+ )
val runner = Runner(controller)
val hideKeyguardWithAnimation = callback.isOnKeyguard() && !showOverLockscreen
// Pass the RemoteAnimationAdapter to the intent starter only if we are not hiding the
// keyguard with the animation
- val animationAdapter = if (!hideKeyguardWithAnimation) {
- RemoteAnimationAdapter(
- runner,
- TIMINGS.totalDuration,
- TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
- )
- } else {
- null
- }
+ val animationAdapter =
+ if (!hideKeyguardWithAnimation) {
+ RemoteAnimationAdapter(
+ runner,
+ TIMINGS.totalDuration,
+ TIMINGS.totalDuration - 150 /* statusBarTransitionDelay */
+ )
+ } else {
+ null
+ }
// Register the remote animation for the given package to also animate trampoline
// activity launches.
if (packageName != null && animationAdapter != null) {
try {
- ActivityTaskManager.getService().registerRemoteAnimationForNextActivityStart(
- packageName, animationAdapter, null /* launchCookie */)
+ ActivityTaskManager.getService()
+ .registerRemoteAnimationForNextActivityStart(
+ packageName,
+ animationAdapter,
+ null /* launchCookie */
+ )
} catch (e: RemoteException) {
Log.w(TAG, "Unable to register the remote animation", e)
}
@@ -184,12 +193,15 @@ class ActivityLaunchAnimator(
// keyguard.
val willAnimate =
launchResult == ActivityManager.START_TASK_TO_FRONT ||
- launchResult == ActivityManager.START_SUCCESS ||
- (launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
- hideKeyguardWithAnimation)
-
- Log.i(TAG, "launchResult=$launchResult willAnimate=$willAnimate " +
- "hideKeyguardWithAnimation=$hideKeyguardWithAnimation")
+ launchResult == ActivityManager.START_SUCCESS ||
+ (launchResult == ActivityManager.START_DELIVERED_TO_TOP &&
+ hideKeyguardWithAnimation)
+
+ Log.i(
+ TAG,
+ "launchResult=$launchResult willAnimate=$willAnimate " +
+ "hideKeyguardWithAnimation=$hideKeyguardWithAnimation"
+ )
controller.callOnIntentStartedOnMainThread(willAnimate)
// If we expect an animation, post a timeout to cancel it in case the remote animation is
@@ -206,9 +218,7 @@ class ActivityLaunchAnimator(
private fun Controller.callOnIntentStartedOnMainThread(willAnimate: Boolean) {
if (Looper.myLooper() != Looper.getMainLooper()) {
- this.launchContainer.context.mainExecutor.execute {
- this.onIntentStarted(willAnimate)
- }
+ this.launchContainer.context.mainExecutor.execute { this.onIntentStarted(willAnimate) }
} else {
this.onIntentStarted(willAnimate)
}
@@ -246,8 +256,7 @@ class ActivityLaunchAnimator(
}
/** Create a new animation [Runner] controlled by [controller]. */
- @VisibleForTesting
- fun createRunner(controller: Controller): Runner = Runner(controller)
+ @VisibleForTesting fun createRunner(controller: Controller): Runner = Runner(controller)
interface PendingIntentStarter {
/**
@@ -271,19 +280,16 @@ class ActivityLaunchAnimator(
interface Listener {
/** Called when an activity launch animation started. */
- @JvmDefault
- fun onLaunchAnimationStart() {}
+ @JvmDefault fun onLaunchAnimationStart() {}
/**
* Called when an activity launch animation is finished. This will be called if and only if
* [onLaunchAnimationStart] was called earlier.
*/
- @JvmDefault
- fun onLaunchAnimationEnd() {}
+ @JvmDefault fun onLaunchAnimationEnd() {}
/** Called when an activity launch animation made progress. */
- @JvmDefault
- fun onLaunchAnimationProgress(linearProgress: Float) {}
+ @JvmDefault fun onLaunchAnimationProgress(linearProgress: Float) {}
}
/**
@@ -327,6 +333,17 @@ class ActivityLaunchAnimator(
get() = false
/**
+ * Whether the expandable controller by this [Controller] is below the launching window that
+ * is going to be animated.
+ *
+ * This should be `false` when launching an app from the shade or status bar, given that
+ * they are drawn above all apps. This is usually `true` when using this launcher in a
+ * normal app or a launcher, that are drawn below the animating activity/window.
+ */
+ val isBelowAnimatingWindow: Boolean
+ get() = false
+
+ /**
* The intent was started. If [willAnimate] is false, nothing else will happen and the
* animation will not be started.
*/
@@ -394,9 +411,7 @@ class ActivityLaunchAnimator(
return
}
- context.mainExecutor.execute {
- startAnimation(apps, nonApps, iCallback)
- }
+ context.mainExecutor.execute { startAnimation(apps, nonApps, iCallback) }
}
private fun startAnimation(
@@ -408,9 +423,7 @@ class ActivityLaunchAnimator(
Log.d(TAG, "Remote animation started")
}
- val window = apps?.firstOrNull {
- it.mode == RemoteAnimationTarget.MODE_OPENING
- }
+ val window = apps?.firstOrNull { it.mode == RemoteAnimationTarget.MODE_OPENING }
if (window == null) {
Log.i(TAG, "Aborting the animation as no window is opening")
@@ -420,80 +433,96 @@ class ActivityLaunchAnimator(
return
}
- val navigationBar = nonApps?.firstOrNull {
- it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
- }
+ val navigationBar =
+ nonApps?.firstOrNull {
+ it.windowType == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+ }
val windowBounds = window.screenSpaceBounds
- val endState = LaunchAnimator.State(
- top = windowBounds.top,
- bottom = windowBounds.bottom,
- left = windowBounds.left,
- right = windowBounds.right
- )
+ val endState =
+ LaunchAnimator.State(
+ top = windowBounds.top,
+ bottom = windowBounds.bottom,
+ left = windowBounds.left,
+ right = windowBounds.right
+ )
val callback = this@ActivityLaunchAnimator.callback!!
- val windowBackgroundColor = window.taskInfo?.let { callback.getBackgroundColor(it) }
- ?: window.backgroundColor
+ val windowBackgroundColor =
+ window.taskInfo?.let { callback.getBackgroundColor(it) } ?: window.backgroundColor
// Make sure we use the modified timings when animating a dialog into an app.
- val launchAnimator = if (controller.isDialogLaunch) {
- dialogToAppAnimator
- } else {
- launchAnimator
- }
+ val launchAnimator =
+ if (controller.isDialogLaunch) {
+ dialogToAppAnimator
+ } else {
+ launchAnimator
+ }
// TODO(b/184121838): We should somehow get the top and bottom radius of the window
// instead of recomputing isExpandingFullyAbove here.
val isExpandingFullyAbove =
launchAnimator.isExpandingFullyAbove(controller.launchContainer, endState)
- val endRadius = if (isExpandingFullyAbove) {
- // Most of the time, expanding fully above the root view means expanding in full
- // screen.
- ScreenDecorationsUtils.getWindowCornerRadius(context)
- } else {
- // This usually means we are in split screen mode, so 2 out of 4 corners will have
- // a radius of 0.
- 0f
- }
+ val endRadius =
+ if (isExpandingFullyAbove) {
+ // Most of the time, expanding fully above the root view means expanding in full
+ // screen.
+ ScreenDecorationsUtils.getWindowCornerRadius(context)
+ } else {
+ // This usually means we are in split screen mode, so 2 out of 4 corners will
+ // have
+ // a radius of 0.
+ 0f
+ }
endState.topCornerRadius = endRadius
endState.bottomCornerRadius = endRadius
// We animate the opening window and delegate the view expansion to [this.controller].
val delegate = this.controller
- val controller = object : LaunchAnimator.Controller by delegate {
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- listeners.forEach { it.onLaunchAnimationStart() }
- delegate.onLaunchAnimationStart(isExpandingFullyAbove)
- }
-
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- listeners.forEach { it.onLaunchAnimationEnd() }
- iCallback?.invoke()
- delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
- }
+ val controller =
+ object : Controller by delegate {
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ listeners.forEach { it.onLaunchAnimationStart() }
+ delegate.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
- progress: Float,
- linearProgress: Float
- ) {
- // Apply the state to the window only if it is visible, i.e. when the expanding
- // view is *not* visible.
- if (!state.visible) {
- applyStateToWindow(window, state)
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ listeners.forEach { it.onLaunchAnimationEnd() }
+ iCallback?.invoke()
+ delegate.onLaunchAnimationEnd(isExpandingFullyAbove)
}
- navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
- listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
- delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+ override fun onLaunchAnimationProgress(
+ state: LaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ // Apply the state to the window only if it is visible, i.e. when the
+ // expanding view is *not* visible.
+ if (!state.visible) {
+ applyStateToWindow(window, state, linearProgress)
+ }
+ navigationBar?.let { applyStateToNavigationBar(it, state, linearProgress) }
+
+ listeners.forEach { it.onLaunchAnimationProgress(linearProgress) }
+ delegate.onLaunchAnimationProgress(state, progress, linearProgress)
+ }
}
- }
- animation = launchAnimator.startAnimation(
- controller, endState, windowBackgroundColor, drawHole = true)
+ animation =
+ launchAnimator.startAnimation(
+ controller,
+ endState,
+ windowBackgroundColor,
+ fadeOutWindowBackgroundLayer = !controller.isBelowAnimatingWindow,
+ drawHole = !controller.isBelowAnimatingWindow,
+ )
}
- private fun applyStateToWindow(window: RemoteAnimationTarget, state: LaunchAnimator.State) {
+ private fun applyStateToWindow(
+ window: RemoteAnimationTarget,
+ state: LaunchAnimator.State,
+ linearProgress: Float,
+ ) {
if (transactionApplierView.viewRootImpl == null || !window.leash.isValid) {
// Don't apply any transaction if the view root we synchronize with was detached or
// if the SurfaceControl associated with [window] is not valid, as
@@ -536,19 +565,38 @@ class ActivityLaunchAnimator(
windowCropF.bottom.roundToInt()
)
+ // The alpha of the opening window. If it opens above the expandable, then it should
+ // fade in progressively. Otherwise, it should be fully opaque and will be progressively
+ // revealed as the window background color layer above the window fades out.
+ val alpha =
+ if (controller.isBelowAnimatingWindow) {
+ val windowProgress =
+ LaunchAnimator.getProgress(
+ TIMINGS,
+ linearProgress,
+ TIMINGS.contentAfterFadeInDelay,
+ TIMINGS.contentAfterFadeInDuration
+ )
+
+ INTERPOLATORS.contentAfterFadeInInterpolator.getInterpolation(windowProgress)
+ } else {
+ 1f
+ }
+
// The scale will also be applied to the corner radius, so we divide by the scale to
// keep the original radius. We use the max of (topCornerRadius, bottomCornerRadius) to
// make sure that the window does not draw itself behind the expanding view. This is
// especially important for lock screen animations, where the window is not clipped by
// the shade.
val cornerRadius = maxOf(state.topCornerRadius, state.bottomCornerRadius) / scale
- val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
- .withAlpha(1f)
- .withMatrix(matrix)
- .withWindowCrop(windowCrop)
- .withCornerRadius(cornerRadius)
- .withVisibility(true)
- .build()
+ val params =
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(window.leash)
+ .withAlpha(alpha)
+ .withMatrix(matrix)
+ .withWindowCrop(windowCrop)
+ .withCornerRadius(cornerRadius)
+ .withVisibility(true)
+ .build()
transactionApplier.scheduleApply(params)
}
@@ -565,14 +613,21 @@ class ActivityLaunchAnimator(
return
}
- val fadeInProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress,
- ANIMATION_DELAY_NAV_FADE_IN, ANIMATION_DURATION_NAV_FADE_OUT)
+ val fadeInProgress =
+ LaunchAnimator.getProgress(
+ TIMINGS,
+ linearProgress,
+ ANIMATION_DELAY_NAV_FADE_IN,
+ ANIMATION_DURATION_NAV_FADE_OUT
+ )
val params = SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(navigationBar.leash)
if (fadeInProgress > 0) {
matrix.reset()
matrix.setTranslate(
- 0f, (state.top - navigationBar.sourceContainerBounds.top).toFloat())
+ 0f,
+ (state.top - navigationBar.sourceContainerBounds.top).toFloat()
+ )
windowCrop.set(state.left, 0, state.right, state.height)
params
.withAlpha(NAV_FADE_IN_INTERPOLATOR.getInterpolation(fadeInProgress))
@@ -580,8 +635,13 @@ class ActivityLaunchAnimator(
.withWindowCrop(windowCrop)
.withVisibility(true)
} else {
- val fadeOutProgress = LaunchAnimator.getProgress(TIMINGS, linearProgress, 0,
- ANIMATION_DURATION_NAV_FADE_OUT)
+ val fadeOutProgress =
+ LaunchAnimator.getProgress(
+ TIMINGS,
+ linearProgress,
+ 0,
+ ANIMATION_DURATION_NAV_FADE_OUT
+ )
params.withAlpha(1f - NAV_FADE_OUT_INTERPOLATOR.getInterpolation(fadeOutProgress))
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
index 258ca6bdf79b..b879ba0b1ece 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DelegateLaunchAnimatorController.kt
@@ -23,4 +23,4 @@ package com.android.systemui.animation
*/
open class DelegateLaunchAnimatorController(
protected val delegate: ActivityLaunchAnimator.Controller
-) : ActivityLaunchAnimator.Controller by delegate \ No newline at end of file
+) : ActivityLaunchAnimator.Controller by delegate
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index 63276c971c95..2f36ab9aa93d 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -34,6 +34,9 @@ import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
import android.widget.FrameLayout
+import com.android.internal.jank.InteractionJankMonitor
+import com.android.internal.jank.InteractionJankMonitor.Configuration
+import com.android.internal.jank.InteractionJankMonitor.CujType
import kotlin.math.roundToInt
private const val TAG = "DialogLaunchAnimator"
@@ -48,8 +51,11 @@ private const val TAG = "DialogLaunchAnimator"
* @see showFromDialog
* @see createActivityLaunchController
*/
-class DialogLaunchAnimator @JvmOverloads constructor(
+class DialogLaunchAnimator
+@JvmOverloads
+constructor(
private val dreamManager: IDreamManager,
+ private val interactionJankMonitor: InteractionJankMonitor,
private val launchAnimator: LaunchAnimator = LaunchAnimator(TIMINGS, INTERPOLATORS),
private val isForTesting: Boolean = false
) {
@@ -58,9 +64,10 @@ class DialogLaunchAnimator @JvmOverloads constructor(
// We use the same interpolator for X and Y axis to make sure the dialog does not move out
// of the screen bounds during the animation.
- private val INTERPOLATORS = ActivityLaunchAnimator.INTERPOLATORS.copy(
- positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
- )
+ private val INTERPOLATORS =
+ ActivityLaunchAnimator.INTERPOLATORS.copy(
+ positionXInterpolator = ActivityLaunchAnimator.INTERPOLATORS.positionInterpolator
+ )
private val TAG_LAUNCH_ANIMATION_RUNNING = R.id.tag_launch_animation_running
}
@@ -89,18 +96,22 @@ class DialogLaunchAnimator @JvmOverloads constructor(
fun showFromView(
dialog: Dialog,
view: View,
- animateBackgroundBoundsChange: Boolean = false
+ cuj: DialogCuj? = null,
+ animateBackgroundBoundsChange: Boolean = false,
) {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException(
"showFromView must be called from the main thread and dialog must be created in " +
- "the main thread")
+ "the main thread"
+ )
}
// If the view we are launching from belongs to another dialog, then this means the caller
// intent is to launch a dialog from another dialog.
- val animatedParent = openedDialogs
- .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
+ val animatedParent =
+ openedDialogs.firstOrNull {
+ it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+ }
val animateFrom = animatedParent?.dialogContentWithBackground ?: view
// Make sure we don't run the launch animation from the same view twice at the same time.
@@ -112,16 +123,19 @@ class DialogLaunchAnimator @JvmOverloads constructor(
animateFrom.setTag(TAG_LAUNCH_ANIMATION_RUNNING, true)
- val animatedDialog = AnimatedDialog(
+ val animatedDialog =
+ AnimatedDialog(
launchAnimator,
dreamManager,
+ interactionJankMonitor,
animateFrom,
onDialogDismissed = { openedDialogs.remove(it) },
dialog = dialog,
animateBackgroundBoundsChange,
animatedParent,
- isForTesting
- )
+ isForTesting,
+ cuj
+ )
openedDialogs.add(animatedDialog)
animatedDialog.start()
@@ -136,15 +150,16 @@ class DialogLaunchAnimator @JvmOverloads constructor(
fun showFromDialog(
dialog: Dialog,
animateFrom: Dialog,
+ cuj: DialogCuj? = null,
animateBackgroundBoundsChange: Boolean = false
) {
- val view = openedDialogs
- .firstOrNull { it.dialog == animateFrom }
- ?.dialogContentWithBackground
- ?: throw IllegalStateException(
- "The animateFrom dialog was not animated using " +
- "DialogLaunchAnimator.showFrom(View|Dialog)")
- showFromView(dialog, view, animateBackgroundBoundsChange)
+ val view =
+ openedDialogs.firstOrNull { it.dialog == animateFrom }?.dialogContentWithBackground
+ ?: throw IllegalStateException(
+ "The animateFrom dialog was not animated using " +
+ "DialogLaunchAnimator.showFrom(View|Dialog)")
+ showFromView(
+ dialog, view, animateBackgroundBoundsChange = animateBackgroundBoundsChange, cuj = cuj)
}
/**
@@ -166,9 +181,11 @@ class DialogLaunchAnimator @JvmOverloads constructor(
view: View,
cujType: Int? = null
): ActivityLaunchAnimator.Controller? {
- val animatedDialog = openedDialogs
- .firstOrNull { it.dialog.window.decorView.viewRootImpl == view.viewRootImpl }
- ?: return null
+ val animatedDialog =
+ openedDialogs.firstOrNull {
+ it.dialog.window.decorView.viewRootImpl == view.viewRootImpl
+ }
+ ?: return null
// At this point, we know that the intent of the caller is to dismiss the dialog to show
// an app, so we disable the exit animation into the touch surface because we will never
@@ -231,7 +248,7 @@ class DialogLaunchAnimator @JvmOverloads constructor(
}
private fun disableDialogDismiss() {
- dialog.setDismissOverride { /* Do nothing */ }
+ dialog.setDismissOverride { /* Do nothing */}
}
private fun enableDialogDismiss() {
@@ -248,9 +265,9 @@ class DialogLaunchAnimator @JvmOverloads constructor(
* Ensure that all dialogs currently shown won't animate into their touch surface when
* dismissed.
*
- * This is a temporary API meant to be called right before we both dismiss a dialog and start
- * an activity, which currently does not look good if we animate the dialog into the touch
- * surface at the same time as the activity starts.
+ * This is a temporary API meant to be called right before we both dismiss a dialog and start an
+ * activity, which currently does not look good if we animate the dialog into the touch surface
+ * at the same time as the activity starts.
*
* TODO(b/193634619): Remove this function and animate dialog into opening activity instead.
*/
@@ -270,16 +287,24 @@ class DialogLaunchAnimator @JvmOverloads constructor(
}
}
+/**
+ * The CUJ interaction associated with opening the dialog.
+ *
+ * The optional tag indicates the specific dialog being opened.
+ */
+data class DialogCuj(@CujType val cujType: Int, val tag: String? = null)
+
private class AnimatedDialog(
private val launchAnimator: LaunchAnimator,
private val dreamManager: IDreamManager,
+ private val interactionJankMonitor: InteractionJankMonitor,
/** The view that triggered the dialog after being tapped. */
var touchSurface: View,
/**
- * A callback that will be called with this [AnimatedDialog] after the dialog was
- * dismissed and the exit animation is done.
+ * A callback that will be called with this [AnimatedDialog] after the dialog was dismissed and
+ * the exit animation is done.
*/
private val onDialogDismissed: (AnimatedDialog) -> Unit,
@@ -295,14 +320,17 @@ private class AnimatedDialog(
/**
* Whether synchronization should be disabled, which can be useful if we are running in a test.
*/
- private val forceDisableSynchronization: Boolean
+ private val forceDisableSynchronization: Boolean,
+
+ /** Interaction to which the dialog animation is associated. */
+ private val cuj: DialogCuj? = null
) {
/**
* The DecorView of this dialog window.
*
* Note that we access this DecorView lazily to avoid accessing it before the dialog is created,
* which can sometimes cause crashes (e.g. with the Cast dialog).
- */
+ */
private val decorView by lazy { dialog.window!!.decorView as ViewGroup }
/**
@@ -313,9 +341,7 @@ private class AnimatedDialog(
*/
var dialogContentWithBackground: ViewGroup? = null
- /**
- * The background color of [dialog], taking into consideration its window background color.
- */
+ /** The background color of [dialog], taking into consideration its window background color. */
private var originalDialogBackgroundColor = Color.BLACK
/**
@@ -333,11 +359,12 @@ private class AnimatedDialog(
private var isOriginalDialogViewLaidOut = false
/** A layout listener to animate the dialog height change. */
- private val backgroundLayoutListener = if (animateBackgroundBoundsChange) {
- AnimatedBoundsLayoutListener()
- } else {
- null
- }
+ private val backgroundLayoutListener =
+ if (animateBackgroundBoundsChange) {
+ AnimatedBoundsLayoutListener()
+ } else {
+ null
+ }
/*
* A layout listener in case the dialog (window) size changes (for instance because of a
@@ -346,6 +373,14 @@ private class AnimatedDialog(
private var decorViewLayoutListener: View.OnLayoutChangeListener? = null
fun start() {
+ if (cuj != null) {
+ val config = Configuration.Builder.withView(cuj.cujType, touchSurface)
+ if (cuj.tag != null) {
+ config.setTag(cuj.tag)
+ }
+ interactionJankMonitor.begin(config)
+ }
+
// Create the dialog so that its onCreate() method is called, which usually sets the dialog
// content.
dialog.create()
@@ -353,99 +388,117 @@ private class AnimatedDialog(
val window = dialog.window!!
val isWindowFullScreen =
window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
- val dialogContentWithBackground = if (isWindowFullScreen) {
- // If the dialog window is already fullscreen, then we look for the first ViewGroup that
- // has a background (and is not the DecorView, which always has a background) and
- // animate towards that ViewGroup given that this is probably what represents the actual
- // dialog view.
- var viewGroupWithBackground: ViewGroup? = null
- for (i in 0 until decorView.childCount) {
- viewGroupWithBackground = findFirstViewGroupWithBackground(decorView.getChildAt(i))
- if (viewGroupWithBackground != null) {
- break
+ val dialogContentWithBackground =
+ if (isWindowFullScreen) {
+ // If the dialog window is already fullscreen, then we look for the first ViewGroup
+ // that has a background (and is not the DecorView, which always has a background)
+ // and animate towards that ViewGroup given that this is probably what represents
+ // the actual dialog view.
+ var viewGroupWithBackground: ViewGroup? = null
+ for (i in 0 until decorView.childCount) {
+ viewGroupWithBackground =
+ findFirstViewGroupWithBackground(decorView.getChildAt(i))
+ if (viewGroupWithBackground != null) {
+ break
+ }
}
- }
-
- // Animate that view with the background. Throw if we didn't find one, because otherwise
- // it's not clear what we should animate.
- viewGroupWithBackground
- ?: throw IllegalStateException("Unable to find ViewGroup with background")
- } else {
- // We will make the dialog window (and therefore its DecorView) fullscreen to make it
- // possible to animate outside its bounds.
- //
- // Before that, we add a new View as a child of the DecorView with the same size and
- // gravity as that DecorView, then we add all original children of the DecorView to that
- // new View. Finally we remove the background of the DecorView and add it to the new
- // View, then we make the DecorView fullscreen. This new View now acts as a fake (non
- // fullscreen) window.
- //
- // On top of that, we also add a fullscreen transparent background between the DecorView
- // and the view that we added so that we can dismiss the dialog when this view is
- // clicked. This is necessary because DecorView overrides onTouchEvent and therefore we
- // can't set the click listener directly on the (now fullscreen) DecorView.
- val fullscreenTransparentBackground = FrameLayout(dialog.context)
- decorView.addView(
- fullscreenTransparentBackground,
- 0 /* index */,
- FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
- )
- val dialogContentWithBackground = FrameLayout(dialog.context)
- dialogContentWithBackground.background = decorView.background
-
- // Make the window background transparent. Note that setting the window (or DecorView)
- // background drawable to null leads to issues with background color (not being
- // transparent) or with insets that are not refreshed. Therefore we need to set it to
- // something not null, hence we are using android.R.color.transparent here.
- window.setBackgroundDrawableResource(android.R.color.transparent)
-
- // Close the dialog when clicking outside of it.
- fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
- dialogContentWithBackground.isClickable = true
-
- // Make sure the transparent and dialog backgrounds are not focusable by accessibility
- // features.
- fullscreenTransparentBackground.importantForAccessibility =
- View.IMPORTANT_FOR_ACCESSIBILITY_NO
- dialogContentWithBackground.importantForAccessibility =
- View.IMPORTANT_FOR_ACCESSIBILITY_NO
-
- fullscreenTransparentBackground.addView(
- dialogContentWithBackground,
- FrameLayout.LayoutParams(
- window.attributes.width,
- window.attributes.height,
- window.attributes.gravity
+ // Animate that view with the background. Throw if we didn't find one, because
+ // otherwise
+ // it's not clear what we should animate.
+ viewGroupWithBackground
+ ?: throw IllegalStateException("Unable to find ViewGroup with background")
+ } else {
+ // We will make the dialog window (and therefore its DecorView) fullscreen to make
+ // it possible to animate outside its bounds.
+ //
+ // Before that, we add a new View as a child of the DecorView with the same size and
+ // gravity as that DecorView, then we add all original children of the DecorView to
+ // that new View. Finally we remove the background of the DecorView and add it to
+ // the new View, then we make the DecorView fullscreen. This new View now acts as a
+ // fake (non fullscreen) window.
+ //
+ // On top of that, we also add a fullscreen transparent background between the
+ // DecorView and the view that we added so that we can dismiss the dialog when this
+ // view is clicked. This is necessary because DecorView overrides onTouchEvent and
+ // therefore we can't set the click listener directly on the (now fullscreen)
+ // DecorView.
+ val fullscreenTransparentBackground = FrameLayout(dialog.context)
+ decorView.addView(
+ fullscreenTransparentBackground,
+ 0 /* index */,
+ FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
)
- )
- // Move all original children of the DecorView to the new View we just added.
- for (i in 1 until decorView.childCount) {
- val view = decorView.getChildAt(1)
- decorView.removeViewAt(1)
- dialogContentWithBackground.addView(view)
- }
+ val dialogContentWithBackground = FrameLayout(dialog.context)
+ dialogContentWithBackground.background = decorView.background
+
+ // Make the window background transparent. Note that setting the window (or
+ // DecorView) background drawable to null leads to issues with background color (not
+ // being transparent) or with insets that are not refreshed. Therefore we need to
+ // set it to something not null, hence we are using android.R.color.transparent
+ // here.
+ window.setBackgroundDrawableResource(android.R.color.transparent)
+
+ // Close the dialog when clicking outside of it.
+ fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
+ dialogContentWithBackground.isClickable = true
+
+ // Make sure the transparent and dialog backgrounds are not focusable by
+ // accessibility
+ // features.
+ fullscreenTransparentBackground.importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ dialogContentWithBackground.importantForAccessibility =
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO
+
+ fullscreenTransparentBackground.addView(
+ dialogContentWithBackground,
+ FrameLayout.LayoutParams(
+ window.attributes.width,
+ window.attributes.height,
+ window.attributes.gravity
+ )
+ )
- // Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
- window.setLayout(MATCH_PARENT, MATCH_PARENT)
- decorViewLayoutListener = View.OnLayoutChangeListener {
- v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->
- if (window.attributes.width != MATCH_PARENT ||
- window.attributes.height != MATCH_PARENT) {
- // The dialog size changed, copy its size to dialogContentWithBackground and
- // make the dialog window full screen again.
- val layoutParams = dialogContentWithBackground.layoutParams
- layoutParams.width = window.attributes.width
- layoutParams.height = window.attributes.height
- dialogContentWithBackground.layoutParams = layoutParams
- window.setLayout(MATCH_PARENT, MATCH_PARENT)
+ // Move all original children of the DecorView to the new View we just added.
+ for (i in 1 until decorView.childCount) {
+ val view = decorView.getChildAt(1)
+ decorView.removeViewAt(1)
+ dialogContentWithBackground.addView(view)
}
- }
- decorView.addOnLayoutChangeListener(decorViewLayoutListener)
- dialogContentWithBackground
- }
+ // Make the window fullscreen and add a layout listener to ensure it stays
+ // fullscreen.
+ window.setLayout(MATCH_PARENT, MATCH_PARENT)
+ decorViewLayoutListener =
+ View.OnLayoutChangeListener {
+ v,
+ left,
+ top,
+ right,
+ bottom,
+ oldLeft,
+ oldTop,
+ oldRight,
+ oldBottom ->
+ if (
+ window.attributes.width != MATCH_PARENT ||
+ window.attributes.height != MATCH_PARENT
+ ) {
+ // The dialog size changed, copy its size to dialogContentWithBackground
+ // and make the dialog window full screen again.
+ val layoutParams = dialogContentWithBackground.layoutParams
+ layoutParams.width = window.attributes.width
+ layoutParams.height = window.attributes.height
+ dialogContentWithBackground.layoutParams = layoutParams
+ window.setLayout(MATCH_PARENT, MATCH_PARENT)
+ }
+ }
+ decorView.addOnLayoutChangeListener(decorViewLayoutListener)
+
+ dialogContentWithBackground
+ }
this.dialogContentWithBackground = dialogContentWithBackground
dialogContentWithBackground.setTag(R.id.tag_dialog_background, true)
@@ -453,7 +506,8 @@ private class AnimatedDialog(
originalDialogBackgroundColor =
GhostedViewLaunchAnimatorController.findGradientDrawable(background)
?.color
- ?.defaultColor ?: Color.BLACK
+ ?.defaultColor
+ ?: Color.BLACK
// Make the background view invisible until we start the animation. We use the transition
// visibility like GhostView does so that we don't mess up with the accessibility tree (see
@@ -461,11 +515,20 @@ private class AnimatedDialog(
dialogContentWithBackground.setTransitionVisibility(View.INVISIBLE)
// Make sure the dialog is visible instantly and does not do any window animation.
- window.attributes.windowAnimations = R.style.Animation_LaunchAnimation
+ val attributes = window.attributes
+ attributes.windowAnimations = R.style.Animation_LaunchAnimation
// Ensure that the animation is not clipped by the display cut-out when animating this
// dialog into an app.
- window.attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ attributes.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+
+ // Ensure that the animation is not clipped by the navigation/task bars when animating this
+ // dialog into an app.
+ val wasFittingNavigationBars =
+ attributes.fitInsetsTypes and WindowInsets.Type.navigationBars() != 0
+ attributes.fitInsetsTypes =
+ attributes.fitInsetsTypes and WindowInsets.Type.navigationBars().inv()
+
window.attributes = window.attributes
// We apply the insets ourselves to make sure that the paddings are set on the correct
@@ -473,30 +536,38 @@ private class AnimatedDialog(
window.setDecorFitsSystemWindows(false)
val viewWithInsets = (dialogContentWithBackground.parent as ViewGroup)
viewWithInsets.setOnApplyWindowInsetsListener { view, windowInsets ->
- val insets = windowInsets.getInsets(WindowInsets.Type.displayCutout())
+ val type = if (wasFittingNavigationBars) {
+ WindowInsets.Type.displayCutout() or WindowInsets.Type.navigationBars()
+ } else {
+ WindowInsets.Type.displayCutout()
+ }
+
+ val insets = windowInsets.getInsets(type)
view.setPadding(insets.left, insets.top, insets.right, insets.bottom)
WindowInsets.CONSUMED
}
// Start the animation once the background view is properly laid out.
- dialogContentWithBackground.addOnLayoutChangeListener(object : View.OnLayoutChangeListener {
- override fun onLayoutChange(
- v: View,
- left: Int,
- top: Int,
- right: Int,
- bottom: Int,
- oldLeft: Int,
- oldTop: Int,
- oldRight: Int,
- oldBottom: Int
- ) {
- dialogContentWithBackground.removeOnLayoutChangeListener(this)
-
- isOriginalDialogViewLaidOut = true
- maybeStartLaunchAnimation()
+ dialogContentWithBackground.addOnLayoutChangeListener(
+ object : View.OnLayoutChangeListener {
+ override fun onLayoutChange(
+ v: View,
+ left: Int,
+ top: Int,
+ right: Int,
+ bottom: Int,
+ oldLeft: Int,
+ oldTop: Int,
+ oldRight: Int,
+ oldBottom: Int
+ ) {
+ dialogContentWithBackground.removeOnLayoutChangeListener(this)
+
+ isOriginalDialogViewLaidOut = true
+ maybeStartLaunchAnimation()
+ }
}
- })
+ )
// Disable the dim. We will enable it once we start the animation.
window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
@@ -522,10 +593,12 @@ private class AnimatedDialog(
// Create a ghost of the touch surface (which will make the touch surface invisible) and add
// it to the host dialog. We trigger a one off synchronization to make sure that this is
// done in sync between the two different windows.
- synchronizeNextDraw(then = {
- isTouchSurfaceGhostDrawn = true
- maybeStartLaunchAnimation()
- })
+ synchronizeNextDraw(
+ then = {
+ isTouchSurfaceGhostDrawn = true
+ maybeStartLaunchAnimation()
+ }
+ )
GhostView.addGhost(touchSurface, decorView)
// The ghost of the touch surface was just created, so the touch surface is currently
@@ -587,14 +660,13 @@ private class AnimatedDialog(
onLaunchAnimationEnd = {
touchSurface.setTag(R.id.tag_launch_animation_running, null)
- // We hide the touch surface when the dialog is showing. We will make this
- // view visible again when dismissing the dialog.
+ // We hide the touch surface when the dialog is showing. We will make this view
+ // visible again when dismissing the dialog.
touchSurface.visibility = View.INVISIBLE
isLaunching = false
- // dismiss was called during the animation, dismiss again now to actually
- // dismiss.
+ // dismiss was called during the animation, dismiss again now to actually dismiss.
if (dismissRequested) {
dialog.dismiss()
}
@@ -603,9 +675,11 @@ private class AnimatedDialog(
// at the end of the launch animation, because the lauch animation already correctly
// handles bounds changes.
if (backgroundLayoutListener != null) {
- dialogContentWithBackground!!
- .addOnLayoutChangeListener(backgroundLayoutListener)
+ dialogContentWithBackground!!.addOnLayoutChangeListener(
+ backgroundLayoutListener
+ )
}
+ cuj?.run { interactionJankMonitor.end(cujType) }
}
)
}
@@ -681,16 +755,19 @@ private class AnimatedDialog(
dialogContentWithBackground.visibility = View.INVISIBLE
if (backgroundLayoutListener != null) {
- dialogContentWithBackground
- .removeOnLayoutChangeListener(backgroundLayoutListener)
+ dialogContentWithBackground.removeOnLayoutChangeListener(
+ backgroundLayoutListener
+ )
}
// Make sure that the removal of the ghost and making the touch surface visible is
// done at the same time.
- synchronizeNextDraw(then = {
- onAnimationFinished(true /* instantDismiss */)
- onDialogDismissed(this@AnimatedDialog)
- })
+ synchronizeNextDraw(
+ then = {
+ onAnimationFinished(true /* instantDismiss */)
+ onDialogDismissed(this@AnimatedDialog)
+ }
+ )
}
)
}
@@ -710,55 +787,56 @@ private class AnimatedDialog(
endViewController.launchContainer = decorView
val endState = endViewController.createAnimatorState()
- val controller = object : LaunchAnimator.Controller {
- override var launchContainer: ViewGroup
- get() = startViewController.launchContainer
- set(value) {
- startViewController.launchContainer = value
- endViewController.launchContainer = value
+ val controller =
+ object : LaunchAnimator.Controller {
+ override var launchContainer: ViewGroup
+ get() = startViewController.launchContainer
+ set(value) {
+ startViewController.launchContainer = value
+ endViewController.launchContainer = value
+ }
+
+ override fun createAnimatorState(): LaunchAnimator.State {
+ return startViewController.createAnimatorState()
}
- override fun createAnimatorState(): LaunchAnimator.State {
- return startViewController.createAnimatorState()
- }
+ override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
+ // During launch, onLaunchAnimationStart will be used to remove the temporary
+ // touch surface ghost so it is important to call this before calling
+ // onLaunchAnimationStart on the controller (which will create its own ghost).
+ onLaunchAnimationStart()
- override fun onLaunchAnimationStart(isExpandingFullyAbove: Boolean) {
- // During launch, onLaunchAnimationStart will be used to remove the temporary touch
- // surface ghost so it is important to call this before calling
- // onLaunchAnimationStart on the controller (which will create its own ghost).
- onLaunchAnimationStart()
+ startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+ endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
+ }
- startViewController.onLaunchAnimationStart(isExpandingFullyAbove)
- endViewController.onLaunchAnimationStart(isExpandingFullyAbove)
- }
+ override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
+ startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
+ endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
- override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
- startViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
- endViewController.onLaunchAnimationEnd(isExpandingFullyAbove)
-
- onLaunchAnimationEnd()
- }
+ onLaunchAnimationEnd()
+ }
- override fun onLaunchAnimationProgress(
- state: LaunchAnimator.State,
- progress: Float,
- linearProgress: Float
- ) {
- startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
-
- // The end view is visible only iff the starting view is not visible.
- state.visible = !state.visible
- endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
-
- // If the dialog content is complex, its dimension might change during the launch
- // animation. The animation end position might also change during the exit
- // animation, for instance when locking the phone when the dialog is open. Therefore
- // we update the end state to the new position/size. Usually the dialog dimension or
- // position will change in the early frames, so changing the end state shouldn't
- // really be noticeable.
- endViewController.fillGhostedViewState(endState)
+ override fun onLaunchAnimationProgress(
+ state: LaunchAnimator.State,
+ progress: Float,
+ linearProgress: Float
+ ) {
+ startViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+
+ // The end view is visible only iff the starting view is not visible.
+ state.visible = !state.visible
+ endViewController.onLaunchAnimationProgress(state, progress, linearProgress)
+
+ // If the dialog content is complex, its dimension might change during the
+ // launch animation. The animation end position might also change during the
+ // exit animation, for instance when locking the phone when the dialog is open.
+ // Therefore we update the end state to the new position/size. Usually the
+ // dialog dimension or position will change in the early frames, so changing the
+ // end state shouldn't really be noticeable.
+ endViewController.fillGhostedViewState(endState)
+ }
}
- }
launchAnimator.startAnimation(controller, endState, originalDialogBackgroundColor)
}
@@ -791,7 +869,7 @@ private class AnimatedDialog(
return (touchSurface.parent as? View)?.isShown ?: true
}
- /** A layout listener to animate the change of bounds of the dialog background. */
+ /** A layout listener to animate the change of bounds of the dialog background. */
class AnimatedBoundsLayoutListener : View.OnLayoutChangeListener {
companion object {
private const val ANIMATION_DURATION = 500L
@@ -836,32 +914,35 @@ private class AnimatedDialog(
currentAnimator?.cancel()
currentAnimator = null
- val animator = ValueAnimator.ofFloat(0f, 1f).apply {
- duration = ANIMATION_DURATION
- interpolator = Interpolators.STANDARD
-
- addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- currentAnimator = null
+ val animator =
+ ValueAnimator.ofFloat(0f, 1f).apply {
+ duration = ANIMATION_DURATION
+ interpolator = Interpolators.STANDARD
+
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ currentAnimator = null
+ }
+ }
+ )
+
+ addUpdateListener { animatedValue ->
+ val progress = animatedValue.animatedFraction
+
+ // Compute new bounds.
+ bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
+ bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
+ bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
+ bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()
+
+ // Set the new bounds.
+ view.left = bounds.left
+ view.top = bounds.top
+ view.right = bounds.right
+ view.bottom = bounds.bottom
}
- })
-
- addUpdateListener { animatedValue ->
- val progress = animatedValue.animatedFraction
-
- // Compute new bounds.
- bounds.left = MathUtils.lerp(startLeft, left, progress).roundToInt()
- bounds.top = MathUtils.lerp(startTop, top, progress).roundToInt()
- bounds.right = MathUtils.lerp(startRight, right, progress).roundToInt()
- bounds.bottom = MathUtils.lerp(startBottom, bottom, progress).roundToInt()
-
- // Set the new bounds.
- view.left = bounds.left
- view.top = bounds.top
- view.right = bounds.right
- view.bottom = bounds.bottom
}
- }
currentAnimator = animator
animator.start()
diff --git a/packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
index 962c0023cf3b..3d341af3b397 100644
--- a/packages/SystemUI/src/com/android/keyguard/FontInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
@@ -61,7 +61,7 @@ class FontInterpolator {
var index: Int,
val sortedAxes: MutableList<FontVariationAxis>
) {
- constructor(font: Font, axes: List<FontVariationAxis>):
+ constructor(font: Font, axes: List<FontVariationAxis>) :
this(font.sourceIdentifier,
font.ttcIndex,
axes.toMutableList().apply { sortBy { it.tag } }
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
index 3f7e0f0fb527..eb000ad312d7 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt
@@ -49,17 +49,16 @@ private const val TAG = "GhostedViewLaunchAnimatorController"
* Note: Avoid instantiating this directly and call [ActivityLaunchAnimator.Controller.fromView]
* whenever possible instead.
*/
-open class GhostedViewLaunchAnimatorController(
+open class GhostedViewLaunchAnimatorController @JvmOverloads constructor(
/** The view that will be ghosted and from which the background will be extracted. */
private val ghostedView: View,
/** The [InteractionJankMonitor.CujType] associated to this animation. */
private val cujType: Int? = null,
- private var interactionJankMonitor: InteractionJankMonitor? = null
+ private var interactionJankMonitor: InteractionJankMonitor =
+ InteractionJankMonitor.getInstance(),
) : ActivityLaunchAnimator.Controller {
- constructor(view: View, type: Int) : this(view, type, null)
-
/** The container to which we will add the ghost view and expanding background. */
override var launchContainer = ghostedView.rootView as ViewGroup
private val launchContainerOverlay: ViewGroupOverlay
@@ -105,9 +104,7 @@ open class GhostedViewLaunchAnimatorController(
}
// Perform a BFS to find the largest View with background.
- val views = LinkedList<View>().apply {
- add(view)
- }
+ val views = LinkedList<View>().apply { add(view) }
while (views.isNotEmpty()) {
val v = views.removeFirst()
@@ -161,10 +158,11 @@ open class GhostedViewLaunchAnimatorController(
}
override fun createAnimatorState(): LaunchAnimator.State {
- val state = LaunchAnimator.State(
- topCornerRadius = getCurrentTopCornerRadius(),
- bottomCornerRadius = getCurrentBottomCornerRadius()
- )
+ val state =
+ LaunchAnimator.State(
+ topCornerRadius = getCurrentTopCornerRadius(),
+ bottomCornerRadius = getCurrentBottomCornerRadius()
+ )
fillGhostedViewState(state)
return state
}
@@ -204,7 +202,7 @@ open class GhostedViewLaunchAnimatorController(
val matrix = ghostView?.animationMatrix ?: Matrix.IDENTITY_MATRIX
matrix.getValues(initialGhostViewMatrixValues)
- cujType?.let { interactionJankMonitor?.begin(ghostedView, it) }
+ cujType?.let { interactionJankMonitor.begin(ghostedView, it) }
}
override fun onLaunchAnimationProgress(
@@ -255,13 +253,14 @@ open class GhostedViewLaunchAnimatorController(
launchContainer.getLocationOnScreen(launchContainerLocation)
ghostViewMatrix.postScale(
- scale, scale,
+ scale,
+ scale,
ghostedViewState.centerX - launchContainerLocation[0],
ghostedViewState.centerY - launchContainerLocation[1]
)
ghostViewMatrix.postTranslate(
- (leftChange + rightChange) / 2f,
- (topChange + bottomChange) / 2f
+ (leftChange + rightChange) / 2f,
+ (topChange + bottomChange) / 2f
)
ghostView.animationMatrix = ghostViewMatrix
@@ -289,7 +288,7 @@ open class GhostedViewLaunchAnimatorController(
return
}
- cujType?.let { interactionJankMonitor?.end(it) }
+ cujType?.let { interactionJankMonitor.end(it) }
backgroundDrawable?.wrapped?.alpha = startBackgroundAlpha
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
index a4c5c3025220..9668066be125 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchAnimator.kt
@@ -34,10 +34,7 @@ import kotlin.math.roundToInt
private const val TAG = "LaunchAnimator"
/** A base class to animate a window launch (activity or dialog) from a view . */
-class LaunchAnimator(
- private val timings: Timings,
- private val interpolators: Interpolators
-) {
+class LaunchAnimator(private val timings: Timings, private val interpolators: Interpolators) {
companion object {
internal const val DEBUG = false
private val SRC_MODE = PorterDuffXfermode(PorterDuff.Mode.SRC)
@@ -75,10 +72,10 @@ class LaunchAnimator(
* with the opening window.
*
* This will be used to:
- * - Get the associated [Context].
- * - Compute whether we are expanding fully above the launch container.
- * - Get to overlay to which we initially put the window background layer, until the
- * opening window is made visible (see [openingWindowSyncView]).
+ * - Get the associated [Context].
+ * - Compute whether we are expanding fully above the launch container.
+ * - Get to overlay to which we initially put the window background layer, until the opening
+ * window is made visible (see [openingWindowSyncView]).
*
* This container can be changed to force this [Controller] to animate the expanding view
* inside a different location, for instance to ensure correct layering during the
@@ -132,7 +129,6 @@ class LaunchAnimator(
var bottom: Int = 0,
var left: Int = 0,
var right: Int = 0,
-
var topCornerRadius: Float = 0f,
var bottomCornerRadius: Float = 0f
) {
@@ -202,18 +198,20 @@ class LaunchAnimator(
)
/**
- * Start a launch animation controlled by [controller] towards [endState]. An intermediary
- * layer with [windowBackgroundColor] will fade in then fade out above the expanding view, and
- * should be the same background color as the opening (or closing) window. If [drawHole] is
- * true, then this intermediary layer will be drawn with SRC blending mode while it fades out.
+ * Start a launch animation controlled by [controller] towards [endState]. An intermediary layer
+ * with [windowBackgroundColor] will fade in then (optionally) fade out above the expanding
+ * view, and should be the same background color as the opening (or closing) window.
*
- * TODO(b/184121838): Remove [drawHole] and instead make the StatusBar draw this hole instead.
+ * If [fadeOutWindowBackgroundLayer] is true, then this intermediary layer will fade out during
+ * the second half of the animation, and will have SRC blending mode (ultimately punching a hole
+ * in the [launch container][Controller.launchContainer]) iff [drawHole] is true.
*/
fun startAnimation(
controller: Controller,
endState: State,
windowBackgroundColor: Int,
- drawHole: Boolean = false
+ fadeOutWindowBackgroundLayer: Boolean = true,
+ drawHole: Boolean = false,
): Animation {
val state = controller.createAnimatorState()
@@ -238,8 +236,12 @@ class LaunchAnimator(
val endBottomCornerRadius = endState.bottomCornerRadius
fun maybeUpdateEndState() {
- if (endTop != endState.top || endBottom != endState.bottom ||
- endLeft != endState.left || endRight != endState.right) {
+ if (
+ endTop != endState.top ||
+ endBottom != endState.bottom ||
+ endLeft != endState.left ||
+ endRight != endState.right
+ ) {
endTop = endState.top
endBottom = endState.bottom
endLeft = endState.left
@@ -256,10 +258,11 @@ class LaunchAnimator(
// color, which is usually the same color of the app background. We first fade in this layer
// to hide the expanding view, then we fade it out with SRC mode to draw a hole in the
// launch container and reveal the opening window.
- val windowBackgroundLayer = GradientDrawable().apply {
- setColor(windowBackgroundColor)
- alpha = 0
- }
+ val windowBackgroundLayer =
+ GradientDrawable().apply {
+ setColor(windowBackgroundColor)
+ alpha = 0
+ }
// Update state.
val animator = ValueAnimator.ofFloat(0f, 1f)
@@ -270,38 +273,41 @@ class LaunchAnimator(
// [Controller.openingWindowSyncView] once the opening app window starts to be visible.
val openingWindowSyncView = controller.openingWindowSyncView
val openingWindowSyncViewOverlay = openingWindowSyncView?.overlay
- val moveBackgroundLayerWhenAppIsVisible = openingWindowSyncView != null &&
- openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
+ val moveBackgroundLayerWhenAppIsVisible =
+ openingWindowSyncView != null &&
+ openingWindowSyncView.viewRootImpl != controller.launchContainer.viewRootImpl
val launchContainerOverlay = launchContainer.overlay
var cancelled = false
var movedBackgroundLayer = false
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
- if (DEBUG) {
- Log.d(TAG, "Animation started")
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator?, isReverse: Boolean) {
+ if (DEBUG) {
+ Log.d(TAG, "Animation started")
+ }
+ controller.onLaunchAnimationStart(isExpandingFullyAbove)
+
+ // Add the drawable to the launch container overlay. Overlays always draw
+ // drawables after views, so we know that it will be drawn above any view added
+ // by the controller.
+ launchContainerOverlay.add(windowBackgroundLayer)
}
- controller.onLaunchAnimationStart(isExpandingFullyAbove)
- // Add the drawable to the launch container overlay. Overlays always draw
- // drawables after views, so we know that it will be drawn above any view added
- // by the controller.
- launchContainerOverlay.add(windowBackgroundLayer)
- }
-
- override fun onAnimationEnd(animation: Animator?) {
- if (DEBUG) {
- Log.d(TAG, "Animation ended")
- }
- controller.onLaunchAnimationEnd(isExpandingFullyAbove)
- launchContainerOverlay.remove(windowBackgroundLayer)
+ override fun onAnimationEnd(animation: Animator?) {
+ if (DEBUG) {
+ Log.d(TAG, "Animation ended")
+ }
+ controller.onLaunchAnimationEnd(isExpandingFullyAbove)
+ launchContainerOverlay.remove(windowBackgroundLayer)
- if (moveBackgroundLayerWhenAppIsVisible) {
- openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ if (moveBackgroundLayerWhenAppIsVisible) {
+ openingWindowSyncViewOverlay?.remove(windowBackgroundLayer)
+ }
}
}
- })
+ )
animator.addUpdateListener { animation ->
if (cancelled) {
@@ -333,12 +339,13 @@ class LaunchAnimator(
// The expanding view can/should be hidden once it is completely covered by the opening
// window.
- state.visible = getProgress(
- timings,
- linearProgress,
- timings.contentBeforeFadeOutDelay,
- timings.contentBeforeFadeOutDuration
- ) < 1
+ state.visible =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration
+ ) < 1
if (moveBackgroundLayerWhenAppIsVisible && !state.visible && !movedBackgroundLayer) {
// The expanding view is not visible, so the opening app is visible. If this is the
@@ -352,17 +359,19 @@ class LaunchAnimator(
ViewRootSync.synchronizeNextDraw(launchContainer, openingWindowSyncView, then = {})
}
- val container = if (movedBackgroundLayer) {
- openingWindowSyncView!!
- } else {
- controller.launchContainer
- }
+ val container =
+ if (movedBackgroundLayer) {
+ openingWindowSyncView!!
+ } else {
+ controller.launchContainer
+ }
applyStateToWindowBackgroundLayer(
windowBackgroundLayer,
state,
linearProgress,
container,
+ fadeOutWindowBackgroundLayer,
drawHole
)
controller.onLaunchAnimationProgress(state, progress, linearProgress)
@@ -391,6 +400,7 @@ class LaunchAnimator(
state: State,
linearProgress: Float,
launchContainer: View,
+ fadeOutWindowBackgroundLayer: Boolean,
drawHole: Boolean
) {
// Update position.
@@ -415,23 +425,25 @@ class LaunchAnimator(
// We first fade in the background layer to hide the expanding view, then fade it out
// with SRC mode to draw a hole punch in the status bar and reveal the opening window.
- val fadeInProgress = getProgress(
- timings,
- linearProgress,
- timings.contentBeforeFadeOutDelay,
- timings.contentBeforeFadeOutDuration
- )
+ val fadeInProgress =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentBeforeFadeOutDelay,
+ timings.contentBeforeFadeOutDuration
+ )
if (fadeInProgress < 1) {
val alpha =
interpolators.contentBeforeFadeOutInterpolator.getInterpolation(fadeInProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
- } else {
- val fadeOutProgress = getProgress(
- timings,
- linearProgress,
- timings.contentAfterFadeInDelay,
- timings.contentAfterFadeInDuration
- )
+ } else if (fadeOutWindowBackgroundLayer) {
+ val fadeOutProgress =
+ getProgress(
+ timings,
+ linearProgress,
+ timings.contentAfterFadeInDelay,
+ timings.contentAfterFadeInDuration
+ )
val alpha =
1 - interpolators.contentAfterFadeInInterpolator.getInterpolation(fadeOutProgress)
drawable.alpha = (alpha * 0xFF).roundToInt()
@@ -439,6 +451,8 @@ class LaunchAnimator(
if (drawHole) {
drawable.setXfermode(SRC_MODE)
}
+ } else {
+ drawable.alpha = 0xFF
}
}
}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
index 80a3eb839940..7499302c06b2 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/LaunchableView.kt
@@ -27,4 +27,4 @@ interface LaunchableView {
* [transition][android.view.View.setTransitionVisibility] visibility changes must be blocked.
*/
fun setShouldBlockVisibilityChanges(block: Boolean)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
index 47c11010c072..f9c6841f96b5 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/RemoteTransitionAdapter.kt
@@ -40,6 +40,7 @@ class RemoteTransitionAdapter {
companion object {
/**
* Almost a copy of Transitions#setupStartState.
+ *
* TODO: remove when there is proper cross-process transaction sync.
*/
@SuppressLint("NewApi")
@@ -50,7 +51,8 @@ class RemoteTransitionAdapter {
info: TransitionInfo,
t: SurfaceControl.Transaction
) {
- val isOpening = info.type == WindowManager.TRANSIT_OPEN ||
+ val isOpening =
+ info.type == WindowManager.TRANSIT_OPEN ||
info.type == WindowManager.TRANSIT_TO_FRONT
// Put animating stuff above this line and put static stuff below it.
val zSplitLine = info.changes.size
@@ -59,15 +61,19 @@ class RemoteTransitionAdapter {
// Launcher animates leaf tasks directly, so always reparent all task leashes to root.
t.reparent(leash, info.rootLeash)
- t.setPosition(leash, (change.startAbsBounds.left - info.rootOffset.x).toFloat(), (
- change.startAbsBounds.top - info.rootOffset.y).toFloat())
+ t.setPosition(
+ leash,
+ (change.startAbsBounds.left - info.rootOffset.x).toFloat(),
+ (change.startAbsBounds.top - info.rootOffset.y).toFloat()
+ )
t.show(leash)
// Put all the OPEN/SHOW on top
if (mode == WindowManager.TRANSIT_OPEN || mode == WindowManager.TRANSIT_TO_FRONT) {
if (isOpening) {
t.setLayer(leash, zSplitLine + info.changes.size - layer)
- if (change.flags
- and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0) {
+ if (
+ change.flags and TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT == 0
+ ) {
// if transferred, it should be left visible.
t.setAlpha(leash, 0f)
}
@@ -75,8 +81,9 @@ class RemoteTransitionAdapter {
// put on bottom and leave it visible
t.setLayer(leash, zSplitLine - layer)
}
- } else if (mode == WindowManager.TRANSIT_CLOSE ||
- mode == WindowManager.TRANSIT_TO_BACK) {
+ } else if (
+ mode == WindowManager.TRANSIT_CLOSE || mode == WindowManager.TRANSIT_TO_BACK
+ ) {
if (isOpening) {
// put on bottom and leave visible
t.setLayer(leash, zSplitLine - layer)
@@ -102,10 +109,15 @@ class RemoteTransitionAdapter {
// making leashes means we have to handle them specially.
return change.leash
}
- val leashSurface = SurfaceControl.Builder()
+ val leashSurface =
+ SurfaceControl.Builder()
.setName(change.leash.toString() + "_transition-leash")
- .setContainerLayer().setParent(if (change.parent == null)
- info.rootLeash else info.getChange(change.parent!!)!!.leash).build()
+ .setContainerLayer()
+ .setParent(
+ if (change.parent == null) info.rootLeash
+ else info.getChange(change.parent!!)!!.leash
+ )
+ .build()
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.changes.size - order, info, t)
t.reparent(change.leash, leashSurface)
@@ -118,10 +130,10 @@ class RemoteTransitionAdapter {
private fun newModeToLegacyMode(newMode: Int): Int {
return when (newMode) {
- WindowManager.TRANSIT_OPEN, WindowManager.TRANSIT_TO_FRONT
- -> RemoteAnimationTarget.MODE_OPENING
- WindowManager.TRANSIT_CLOSE, WindowManager.TRANSIT_TO_BACK
- -> RemoteAnimationTarget.MODE_CLOSING
+ WindowManager.TRANSIT_OPEN,
+ WindowManager.TRANSIT_TO_FRONT -> RemoteAnimationTarget.MODE_OPENING
+ WindowManager.TRANSIT_CLOSE,
+ WindowManager.TRANSIT_TO_BACK -> RemoteAnimationTarget.MODE_CLOSING
else -> RemoteAnimationTarget.MODE_CHANGING
}
}
@@ -138,12 +150,13 @@ class RemoteTransitionAdapter {
info: TransitionInfo,
t: SurfaceControl.Transaction
): RemoteAnimationTarget {
- val target = RemoteAnimationTarget(
+ val target =
+ RemoteAnimationTarget(
/* taskId */ if (change.taskInfo != null) change.taskInfo!!.taskId else -1,
/* mode */ newModeToLegacyMode(change.mode),
/* leash */ createLeash(info, change, order, t),
/* isTranslucent */ (change.flags and TransitionInfo.FLAG_TRANSLUCENT != 0 ||
- change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0),
+ change.flags and TransitionInfo.FLAG_SHOW_WALLPAPER != 0),
/* clipRect */ null,
/* contentInsets */ Rect(0, 0, 0, 0),
/* prefixOrderIndex */ order,
@@ -151,15 +164,16 @@ class RemoteTransitionAdapter {
/* localBounds */ rectOffsetTo(change.endAbsBounds, change.endRelOffset),
/* screenSpaceBounds */ Rect(change.endAbsBounds),
/* windowConfig */ if (change.taskInfo != null)
- change.taskInfo!!.configuration.windowConfiguration else
- WindowConfiguration(),
- /* isNotInRecents */ if (change.taskInfo != null)
- !change.taskInfo!!.isRunning else true,
+ change.taskInfo!!.configuration.windowConfiguration
+ else WindowConfiguration(),
+ /* isNotInRecents */ if (change.taskInfo != null) !change.taskInfo!!.isRunning
+ else true,
/* startLeash */ null,
/* startBounds */ Rect(change.startAbsBounds),
/* taskInfo */ change.taskInfo,
/* allowEnterPip */ change.allowEnterPip,
- /* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE)
+ /* windowType */ WindowManager.LayoutParams.INVALID_WINDOW_TYPE
+ )
target.backgroundColor = change.backgroundColor
return target
}
@@ -192,9 +206,7 @@ class RemoteTransitionAdapter {
}
@JvmStatic
- fun adaptRemoteRunner(
- runner: IRemoteAnimationRunner
- ): IRemoteTransition.Stub {
+ fun adaptRemoteRunner(runner: IRemoteAnimationRunner): IRemoteTransition.Stub {
return object : IRemoteTransition.Stub() {
override fun startAnimation(
token: IBinder,
@@ -218,18 +230,24 @@ class RemoteTransitionAdapter {
var displayH = 0f
for (i in info.changes.indices.reversed()) {
val change = info.changes[i]
- if (change.taskInfo != null &&
- change.taskInfo!!.activityType
- == WindowConfiguration.ACTIVITY_TYPE_HOME) {
- isReturnToHome = (change.mode == WindowManager.TRANSIT_OPEN ||
+ if (
+ change.taskInfo != null &&
+ change.taskInfo!!.activityType ==
+ WindowConfiguration.ACTIVITY_TYPE_HOME
+ ) {
+ isReturnToHome =
+ (change.mode == WindowManager.TRANSIT_OPEN ||
change.mode == WindowManager.TRANSIT_TO_FRONT)
launcherTask = change
launcherLayer = info.changes.size - i
} else if (change.flags and TransitionInfo.FLAG_IS_WALLPAPER != 0) {
wallpaper = change
}
- if (change.parent == null && change.endRotation >= 0 &&
- change.endRotation != change.startRotation) {
+ if (
+ change.parent == null &&
+ change.endRotation >= 0 &&
+ change.endRotation != change.startRotation
+ ) {
rotateDelta = change.endRotation - change.startRotation
displayW = change.endAbsBounds.width().toFloat()
displayH = change.endAbsBounds.height().toFloat()
@@ -240,8 +258,13 @@ class RemoteTransitionAdapter {
val counterLauncher = CounterRotator()
val counterWallpaper = CounterRotator()
if (launcherTask != null && rotateDelta != 0 && launcherTask.parent != null) {
- counterLauncher.setup(t, info.getChange(launcherTask.parent!!)!!.leash,
- rotateDelta, displayW, displayH)
+ counterLauncher.setup(
+ t,
+ info.getChange(launcherTask.parent!!)!!.leash,
+ rotateDelta,
+ displayW,
+ displayH
+ )
if (counterLauncher.surface != null) {
t.setLayer(counterLauncher.surface!!, launcherLayer)
}
@@ -257,8 +280,10 @@ class RemoteTransitionAdapter {
val mode = info.changes[i].mode
// Only deal with independent layers
if (!TransitionInfo.isIndependent(change, info)) continue
- if (mode == WindowManager.TRANSIT_CLOSE ||
- mode == WindowManager.TRANSIT_TO_BACK) {
+ if (
+ mode == WindowManager.TRANSIT_CLOSE ||
+ mode == WindowManager.TRANSIT_TO_BACK
+ ) {
t.setLayer(leash!!, info.changes.size * 3 - i)
counterLauncher.addChild(t, leash)
}
@@ -273,8 +298,13 @@ class RemoteTransitionAdapter {
counterLauncher.addChild(t, leashMap[launcherTask.leash])
}
if (wallpaper != null && rotateDelta != 0 && wallpaper.parent != null) {
- counterWallpaper.setup(t, info.getChange(wallpaper.parent!!)!!.leash,
- rotateDelta, displayW, displayH)
+ counterWallpaper.setup(
+ t,
+ info.getChange(wallpaper.parent!!)!!.leash,
+ rotateDelta,
+ displayW,
+ displayH
+ )
if (counterWallpaper.surface != null) {
t.setLayer(counterWallpaper.surface!!, -1)
counterWallpaper.addChild(t, leashMap[wallpaper.leash])
@@ -282,37 +312,47 @@ class RemoteTransitionAdapter {
}
}
t.apply()
- val animationFinishedCallback = object : IRemoteAnimationFinishedCallback {
- override fun onAnimationFinished() {
- val finishTransaction = SurfaceControl.Transaction()
- counterLauncher.cleanUp(finishTransaction)
- counterWallpaper.cleanUp(finishTransaction)
- // Release surface references now. This is apparently to free GPU memory
- // while doing quick operations (eg. during CTS).
- for (i in info.changes.indices.reversed()) {
- info.changes[i].leash.release()
- }
- for (i in leashMap.size - 1 downTo 0) {
- leashMap.valueAt(i).release()
- }
- try {
- finishCallback.onTransitionFinished(null /* wct */,
- finishTransaction)
- } catch (e: RemoteException) {
- Log.e("ActivityOptionsCompat", "Failed to call app controlled" +
- " animation finished callback", e)
+ val animationFinishedCallback =
+ object : IRemoteAnimationFinishedCallback {
+ override fun onAnimationFinished() {
+ val finishTransaction = SurfaceControl.Transaction()
+ counterLauncher.cleanUp(finishTransaction)
+ counterWallpaper.cleanUp(finishTransaction)
+ // Release surface references now. This is apparently to free GPU
+ // memory while doing quick operations (eg. during CTS).
+ for (i in info.changes.indices.reversed()) {
+ info.changes[i].leash.release()
+ }
+ for (i in leashMap.size - 1 downTo 0) {
+ leashMap.valueAt(i).release()
+ }
+ try {
+ finishCallback.onTransitionFinished(
+ null /* wct */,
+ finishTransaction
+ )
+ } catch (e: RemoteException) {
+ Log.e(
+ "ActivityOptionsCompat",
+ "Failed to call app controlled" +
+ " animation finished callback",
+ e
+ )
+ }
}
- }
- override fun asBinder(): IBinder? {
- return null
+ override fun asBinder(): IBinder? {
+ return null
+ }
}
- }
// TODO(bc-unlcok): Pass correct transit type.
runner.onAnimationStart(
- WindowManager.TRANSIT_OLD_NONE,
- appsCompat, wallpapersCompat, nonAppsCompat,
- animationFinishedCallback)
+ WindowManager.TRANSIT_OLD_NONE,
+ appsCompat,
+ wallpapersCompat,
+ nonAppsCompat,
+ animationFinishedCallback
+ )
}
override fun mergeAnimation(
@@ -329,18 +369,14 @@ class RemoteTransitionAdapter {
}
@JvmStatic
- fun adaptRemoteAnimation(
- adapter: RemoteAnimationAdapter
- ): RemoteTransition {
+ fun adaptRemoteAnimation(adapter: RemoteAnimationAdapter): RemoteTransition {
return RemoteTransition(adaptRemoteRunner(adapter.runner), adapter.callingApplication)
}
}
- /**
- * Utility class that takes care of counter-rotating surfaces during a transition animation.
- */
+ /** Utility class that takes care of counter-rotating surfaces during a transition animation. */
class CounterRotator {
- /** Gets the surface with the counter-rotation. */
+ /** Gets the surface with the counter-rotation. */
var surface: SurfaceControl? = null
private set
@@ -358,7 +394,8 @@ class RemoteTransitionAdapter {
parentH: Float
) {
if (rotateDelta == 0) return
- val surface = SurfaceControl.Builder()
+ val surface =
+ SurfaceControl.Builder()
.setName("Transition Unrotate")
.setContainerLayer()
.setParent(parent)
@@ -378,21 +415,19 @@ class RemoteTransitionAdapter {
t.show(surface)
}
- /**
- * Adds a surface that needs to be counter-rotate.
- */
+ /** Adds a surface that needs to be counter-rotate. */
fun addChild(t: SurfaceControl.Transaction, child: SurfaceControl?) {
if (surface == null) return
t.reparent(child!!, surface)
}
/**
- * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove the
- * counter rotation surface.
+ * Clean-up. Since finishTransaction should reset all change leashes, we only need to remove
+ * the counter rotation surface.
*/
fun cleanUp(finishTransaction: SurfaceControl.Transaction) {
if (surface == null) return
finishTransaction.remove(surface!!)
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
index 0ee2bfea55c5..a96f893a8db4 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ShadeInterpolation.kt
@@ -31,7 +31,7 @@ object ShadeInterpolation {
} else {
val oneMinusFrac = 1f - mappedFraction
(1f - 0.5f * (1f - Math.cos((3.14159f * oneMinusFrac * oneMinusFrac).toDouble())))
- .toFloat()
+ .toFloat()
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
index 336101528450..f79b328190dd 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -22,12 +22,14 @@ import android.animation.TimeInterpolator
import android.animation.ValueAnimator
import android.graphics.Canvas
import android.graphics.Typeface
+import android.graphics.fonts.Font
import android.text.Layout
import android.util.SparseArray
private const val TAG_WGHT = "wght"
private const val DEFAULT_ANIMATION_DURATION: Long = 300
+typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
/**
* This class provides text animation between two styles.
*
@@ -59,8 +61,8 @@ class TextAnimator(
private val invalidateCallback: () -> Unit
) {
// Following two members are for mutable for testing purposes.
- internal var textInterpolator: TextInterpolator = TextInterpolator(layout)
- internal var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
+ public var textInterpolator: TextInterpolator = TextInterpolator(layout)
+ public var animator: ValueAnimator = ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
textInterpolator.progress = it.animatedValue as Float
@@ -74,6 +76,59 @@ class TextAnimator(
})
}
+ sealed class PositionedGlyph {
+
+ /**
+ * Mutable X coordinate of the glyph position relative from drawing offset.
+ */
+ var x: Float = 0f
+
+ /**
+ * Mutable Y coordinate of the glyph position relative from the baseline.
+ */
+ var y: Float = 0f
+
+ /**
+ * Mutable text size of the glyph in pixels.
+ */
+ var textSize: Float = 0f
+
+ /**
+ * Mutable color of the glyph.
+ */
+ var color: Int = 0
+
+ /**
+ * Immutable character offset in the text that the current font run start.
+ */
+ abstract var runStart: Int
+ protected set
+
+ /**
+ * Immutable run length of the font run.
+ */
+ abstract var runLength: Int
+ protected set
+
+ /**
+ * Immutable glyph index of the font run.
+ */
+ abstract var glyphIndex: Int
+ protected set
+
+ /**
+ * Immutable font instance for this font run.
+ */
+ abstract var font: Font
+ protected set
+
+ /**
+ * Immutable glyph ID for this glyph.
+ */
+ abstract var glyphId: Int
+ protected set
+ }
+
private val typefaceCache = SparseArray<Typeface?>()
fun updateLayout(layout: Layout) {
@@ -84,6 +139,57 @@ class TextAnimator(
return animator.isRunning
}
+ /**
+ * GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
+ *
+ * This callback is called for each glyphs just before drawing the glyphs. This function will
+ * be called with the intrinsic position, size, color, glyph ID and font instance. You can
+ * mutate the position, size and color for tweaking animations.
+ * Do not keep the reference of passed glyph object. The interpolator reuses that object for
+ * avoiding object allocations.
+ *
+ * Details:
+ * The text is drawn with font run units. The font run is a text segment that draws with the
+ * same font. The {@code runStart} and {@code runLimit} is a range of the font run in the text
+ * that current glyph is in. Once the font run is determined, the system will convert characters
+ * into glyph IDs. The {@code glyphId} is the glyph identifier in the font and
+ * {@code glyphIndex} is the offset of the converted glyph array. Please note that the
+ * {@code glyphIndex} is not a character index, because the character will not be converted to
+ * glyph one-by-one. If there are ligatures including emoji sequence, etc, the glyph ID may be
+ * composed from multiple characters.
+ *
+ * Here is an example of font runs: "fin. 終わり"
+ *
+ * Characters : f i n . _ 終 わ り
+ * Code Points: \u0066 \u0069 \u006E \u002E \u0020 \u7D42 \u308F \u308A
+ * Font Runs : <-- Roboto-Regular.ttf --><-- NotoSans-CJK.otf -->
+ * runStart = 0, runLength = 5 runStart = 5, runLength = 3
+ * Glyph IDs : 194 48 7 8 4367 1039 1002
+ * Glyph Index: 0 1 2 3 0 1 2
+ *
+ * In this example, the "fi" is converted into ligature form, thus the single glyph ID is
+ * assigned for two characters, f and i.
+ *
+ * Example:
+ * ```
+ * private val glyphFilter: GlyphCallback = { glyph, progress ->
+ * val index = glyph.runStart
+ * val i = glyph.glyphIndex
+ * val moveAmount = 1.3f
+ * val sign = (-1 + 2 * ((i + index) % 2))
+ * val turnProgress = if (progress < .5f) progress / 0.5f else (1.0f - progress) / 0.5f
+ *
+ * // You can modify (x, y) coordinates, textSize and color during animation.
+ * glyph.textSize += glyph.textSize * sign * moveAmount * turnProgress
+ * glyph.y += glyph.y * sign * moveAmount * turnProgress
+ * glyph.x += glyph.x * sign * moveAmount * turnProgress
+ * }
+ * ```
+ */
+ var glyphFilter: GlyphCallback?
+ get() = textInterpolator.glyphFilter
+ set(value) { textInterpolator.glyphFilter = value }
+
fun draw(c: Canvas) = textInterpolator.draw(c)
/**
@@ -173,4 +279,4 @@ private fun <V> SparseArray<V>.getOrElse(key: Int, defaultValue: () -> V): V {
put(key, v)
}
return v
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
index 5d5797cbbb3d..ff64c7891128 100644
--- a/packages/SystemUI/src/com/android/keyguard/TextInterpolator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.Canvas
import android.graphics.Paint
@@ -89,8 +89,11 @@ class TextInterpolator(
private var lines = listOf<Line>()
private val fontInterpolator = FontInterpolator()
- // Recycling object for glyph drawing. Will be extended for the longest font run if needed.
- private val tmpDrawPaint = TextPaint()
+ // Recycling object for glyph drawing and tweaking.
+ private val tmpPaint = TextPaint()
+ private val tmpPaintForGlyph by lazy { TextPaint() }
+ private val tmpGlyph by lazy { MutablePositionedGlyph() }
+ // Will be extended for the longest font run if needed.
private var tmpPositionArray = FloatArray(20)
/**
@@ -206,8 +209,8 @@ class TextInterpolator(
} else if (progress == 1f) {
basePaint.set(targetPaint)
} else {
- lerp(basePaint, targetPaint, progress, tmpDrawPaint)
- basePaint.set(tmpDrawPaint)
+ lerp(basePaint, targetPaint, progress, tmpPaint)
+ basePaint.set(tmpPaint)
}
lines.forEach { line ->
@@ -231,7 +234,7 @@ class TextInterpolator(
* @param canvas a canvas.
*/
fun draw(canvas: Canvas) {
- lerp(basePaint, targetPaint, progress, tmpDrawPaint)
+ lerp(basePaint, targetPaint, progress, tmpPaint)
lines.forEachIndexed { lineNo, line ->
line.runs.forEach { run ->
canvas.save()
@@ -241,7 +244,7 @@ class TextInterpolator(
canvas.translate(origin, layout.getLineBaseline(lineNo).toFloat())
run.fontRuns.forEach { fontRun ->
- drawFontRun(canvas, run, fontRun, tmpDrawPaint)
+ drawFontRun(canvas, run, fontRun, tmpPaint)
}
} finally {
canvas.restore()
@@ -330,24 +333,82 @@ class TextInterpolator(
}
}
+ private class MutablePositionedGlyph : TextAnimator.PositionedGlyph() {
+ override var runStart: Int = 0
+ public set
+ override var runLength: Int = 0
+ public set
+ override var glyphIndex: Int = 0
+ public set
+ override lateinit var font: Font
+ public set
+ override var glyphId: Int = 0
+ public set
+ }
+
+ var glyphFilter: GlyphCallback? = null
+
// Draws single font run.
private fun drawFontRun(c: Canvas, line: Run, run: FontRun, paint: Paint) {
var arrayIndex = 0
+ val font = fontInterpolator.lerp(run.baseFont, run.targetFont, progress)
+
+ val glyphFilter = glyphFilter
+ if (glyphFilter == null) {
+ for (i in run.start until run.end) {
+ tmpPositionArray[arrayIndex++] =
+ MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+ tmpPositionArray[arrayIndex++] =
+ MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ }
+ c.drawGlyphs(line.glyphIds, run.start, tmpPositionArray, 0, run.length, font, paint)
+ return
+ }
+
+ tmpGlyph.font = font
+ tmpGlyph.runStart = run.start
+ tmpGlyph.runLength = run.end - run.start
+
+ tmpPaintForGlyph.set(paint)
+ var prevStart = run.start
+
for (i in run.start until run.end) {
- tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
- tmpPositionArray[arrayIndex++] =
- MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ tmpGlyph.glyphId = line.glyphIds[i]
+ tmpGlyph.x = MathUtils.lerp(line.baseX[i], line.targetX[i], progress)
+ tmpGlyph.y = MathUtils.lerp(line.baseY[i], line.targetY[i], progress)
+ tmpGlyph.textSize = paint.textSize
+ tmpGlyph.color = paint.color
+
+ glyphFilter(tmpGlyph, progress)
+
+ if (tmpGlyph.textSize != paint.textSize || tmpGlyph.color != paint.color) {
+ tmpPaintForGlyph.textSize = tmpGlyph.textSize
+ tmpPaintForGlyph.color = tmpGlyph.color
+
+ c.drawGlyphs(
+ line.glyphIds,
+ prevStart,
+ tmpPositionArray,
+ 0,
+ i - prevStart,
+ font,
+ tmpPaintForGlyph)
+ prevStart = i
+ arrayIndex = 0
+ }
+
+ tmpPositionArray[arrayIndex++] = tmpGlyph.x
+ tmpPositionArray[arrayIndex++] = tmpGlyph.y
}
c.drawGlyphs(
line.glyphIds,
- run.start,
+ prevStart,
tmpPositionArray,
0,
- run.length,
- fontInterpolator.lerp(run.baseFont, run.targetFont, progress),
- paint)
+ run.end - prevStart,
+ font,
+ tmpPaintForGlyph)
}
private fun updatePositionsAndFonts(
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
index 4c3cd3c2b441..cc7d23e97d0c 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewHierarchyAnimator.kt
@@ -42,12 +42,13 @@ class ViewHierarchyAnimator {
private val DEFAULT_FADE_IN_INTERPOLATOR = Interpolators.ALPHA_IN
/** The properties used to animate the view bounds. */
- private val PROPERTIES = mapOf(
- Bound.LEFT to createViewProperty(Bound.LEFT),
- Bound.TOP to createViewProperty(Bound.TOP),
- Bound.RIGHT to createViewProperty(Bound.RIGHT),
- Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
- )
+ private val PROPERTIES =
+ mapOf(
+ Bound.LEFT to createViewProperty(Bound.LEFT),
+ Bound.TOP to createViewProperty(Bound.TOP),
+ Bound.RIGHT to createViewProperty(Bound.RIGHT),
+ Bound.BOTTOM to createViewProperty(Bound.BOTTOM)
+ )
private fun createViewProperty(bound: Bound): IntProperty<View> {
return object : IntProperty<View>(bound.label) {
@@ -104,7 +105,8 @@ class ViewHierarchyAnimator {
duration: Long,
ephemeral: Boolean
): Boolean {
- if (!isVisible(
+ if (
+ !occupiesSpace(
rootView.visibility,
rootView.left,
rootView.top,
@@ -132,11 +134,7 @@ class ViewHierarchyAnimator {
duration: Long,
ephemeral: Boolean
): View.OnLayoutChangeListener {
- return createListener(
- interpolator,
- duration,
- ephemeral
- )
+ return createListener(interpolator, duration, ephemeral)
}
/**
@@ -178,7 +176,8 @@ class ViewHierarchyAnimator {
includeFadeIn: Boolean = false,
fadeInInterpolator: Interpolator = DEFAULT_FADE_IN_INTERPOLATOR
): Boolean {
- if (isVisible(
+ if (
+ occupiesSpace(
rootView.visibility,
rootView.left,
rootView.top,
@@ -189,9 +188,13 @@ class ViewHierarchyAnimator {
return false
}
- val listener = createAdditionListener(
- origin, interpolator, duration, ignorePreviousValues = !includeMargins
- )
+ val listener =
+ createAdditionListener(
+ origin,
+ interpolator,
+ duration,
+ ignorePreviousValues = !includeMargins
+ )
addListener(rootView, listener, recursive = true)
if (!includeFadeIn) {
@@ -292,7 +295,7 @@ class ViewHierarchyAnimator {
(view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
- if (!isVisible(view.visibility, left, top, right, bottom)) {
+ if (!occupiesSpace(view.visibility, left, top, right, bottom)) {
setBound(view, Bound.LEFT, left)
setBound(view, Bound.TOP, top)
setBound(view, Bound.RIGHT, right)
@@ -300,24 +303,26 @@ class ViewHierarchyAnimator {
return
}
- val startValues = processStartValues(
- origin,
- left,
- top,
- right,
- bottom,
- startLeft,
- startTop,
- startRight,
- startBottom,
- ignorePreviousValues
- )
- val endValues = mapOf(
- Bound.LEFT to left,
- Bound.TOP to top,
- Bound.RIGHT to right,
- Bound.BOTTOM to bottom
- )
+ val startValues =
+ processStartValues(
+ origin,
+ left,
+ top,
+ right,
+ bottom,
+ startLeft,
+ startTop,
+ startRight,
+ startBottom,
+ ignorePreviousValues
+ )
+ val endValues =
+ mapOf(
+ Bound.LEFT to left,
+ Bound.TOP to top,
+ Bound.RIGHT to right,
+ Bound.BOTTOM to bottom
+ )
val boundsToAnimate = mutableSetOf<Bound>()
if (startValues.getValue(Bound.LEFT) != left) boundsToAnimate.add(Bound.LEFT)
@@ -356,7 +361,8 @@ class ViewHierarchyAnimator {
interpolator: Interpolator = DEFAULT_REMOVAL_INTERPOLATOR,
duration: Long = DEFAULT_DURATION
): Boolean {
- if (!isVisible(
+ if (
+ !occupiesSpace(
rootView.visibility,
rootView.left,
rootView.top,
@@ -370,11 +376,7 @@ class ViewHierarchyAnimator {
val parent = rootView.parent as ViewGroup
// Ensure that rootView's siblings animate nicely around the removal.
- val listener = createUpdateListener(
- interpolator,
- duration,
- ephemeral = true
- )
+ val listener = createUpdateListener(interpolator, duration, ephemeral = true)
for (i in 0 until parent.childCount) {
val child = parent.getChildAt(i)
if (child == rootView) continue
@@ -389,19 +391,21 @@ class ViewHierarchyAnimator {
// them manually during the animation.
parent.overlay.add(rootView)
- val startValues = mapOf(
- Bound.LEFT to rootView.left,
- Bound.TOP to rootView.top,
- Bound.RIGHT to rootView.right,
- Bound.BOTTOM to rootView.bottom
- )
- val endValues = processEndValuesForRemoval(
- destination,
- rootView.left,
- rootView.top,
- rootView.right,
- rootView.bottom
- )
+ val startValues =
+ mapOf(
+ Bound.LEFT to rootView.left,
+ Bound.TOP to rootView.top,
+ Bound.RIGHT to rootView.right,
+ Bound.BOTTOM to rootView.bottom
+ )
+ val endValues =
+ processEndValuesForRemoval(
+ destination,
+ rootView.left,
+ rootView.top,
+ rootView.right,
+ rootView.bottom
+ )
val boundsToAnimate = mutableSetOf<Bound>()
if (rootView.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT)
@@ -443,20 +447,24 @@ class ViewHierarchyAnimator {
(animation.animatedValue as Float) * startAlphas[i]
}
}
- animator.addListener(object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- rootView.animate()
- .alpha(0f)
- .setInterpolator(Interpolators.ALPHA_OUT)
- .setDuration(duration / 2)
- .withEndAction { parent.overlay.remove(rootView) }
- .start()
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ rootView
+ .animate()
+ .alpha(0f)
+ .setInterpolator(Interpolators.ALPHA_OUT)
+ .setDuration(duration / 2)
+ .withEndAction { parent.overlay.remove(rootView) }
+ .start()
+ }
}
- })
+ )
animator.start()
} else {
// Fade out the view during the second half of the removal.
- rootView.animate()
+ rootView
+ .animate()
.alpha(0f)
.setInterpolator(Interpolators.ALPHA_OUT)
.setDuration(duration / 2)
@@ -483,21 +491,23 @@ class ViewHierarchyAnimator {
) {
for (i in 0 until rootView.childCount) {
val child = rootView.getChildAt(i)
- val childStartValues = mapOf(
- Bound.LEFT to child.left,
- Bound.TOP to child.top,
- Bound.RIGHT to child.right,
- Bound.BOTTOM to child.bottom
- )
- val childEndValues = processChildEndValuesForRemoval(
- destination,
- child.left,
- child.top,
- child.right,
- child.bottom,
- endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT),
- endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP)
- )
+ val childStartValues =
+ mapOf(
+ Bound.LEFT to child.left,
+ Bound.TOP to child.top,
+ Bound.RIGHT to child.right,
+ Bound.BOTTOM to child.bottom
+ )
+ val childEndValues =
+ processChildEndValuesForRemoval(
+ destination,
+ child.left,
+ child.top,
+ child.right,
+ child.bottom,
+ endValues.getValue(Bound.RIGHT) - endValues.getValue(Bound.LEFT),
+ endValues.getValue(Bound.BOTTOM) - endValues.getValue(Bound.TOP)
+ )
val boundsToAnimate = mutableSetOf<Bound>()
if (child.left != endValues.getValue(Bound.LEFT)) boundsToAnimate.add(Bound.LEFT)
@@ -520,17 +530,17 @@ class ViewHierarchyAnimator {
}
/**
- * Returns whether the given [visibility] and bounds are consistent with a view being
- * currently visible on screen.
+ * Returns whether the given [visibility] and bounds are consistent with a view being a
+ * contributing part of the hierarchy.
*/
- private fun isVisible(
+ private fun occupiesSpace(
visibility: Int,
left: Int,
top: Int,
right: Int,
bottom: Int
): Boolean {
- return visibility == View.VISIBLE && left != right && top != bottom
+ return visibility != View.GONE && left != right && top != bottom
}
/**
@@ -543,6 +553,7 @@ class ViewHierarchyAnimator {
* not newly introduced margins are included.
*
* Base case
+ * ```
* 1) origin=TOP
* x---------x x---------x x---------x x---------x x---------x
* x---------x | | | | | |
@@ -561,11 +572,11 @@ class ViewHierarchyAnimator {
* x -> x---x -> | | -> | | -> | |
* x-----x x-------x | |
* x---------x
- *
+ * ```
* In case the start and end values differ in the direction of the origin, and
* [ignorePreviousValues] is false, the previous values are used and a translation is
* included in addition to the view expansion.
- *
+ * ```
* origin=TOP_LEFT - (0,0,0,0) -> (30,30,70,70)
* x
* x--x
@@ -574,6 +585,7 @@ class ViewHierarchyAnimator {
* x----x | |
* | |
* x------x
+ * ```
*/
private fun processStartValues(
origin: Hotspot?,
@@ -598,42 +610,54 @@ class ViewHierarchyAnimator {
var bottom = startBottom
if (origin != null) {
- left = when (origin) {
- Hotspot.CENTER -> (newLeft + newRight) / 2
- Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft)
- Hotspot.TOP, Hotspot.BOTTOM -> newLeft
- Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max(
- startRight,
- newRight
- )
- }
- top = when (origin) {
- Hotspot.CENTER -> (newTop + newBottom) / 2
- Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop)
- Hotspot.LEFT, Hotspot.RIGHT -> newTop
- Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max(
- startBottom,
- newBottom
- )
- }
- right = when (origin) {
- Hotspot.CENTER -> (newLeft + newRight) / 2
- Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> max(
- startRight,
- newRight
- )
- Hotspot.TOP, Hotspot.BOTTOM -> newRight
- Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> min(startLeft, newLeft)
- }
- bottom = when (origin) {
- Hotspot.CENTER -> (newTop + newBottom) / 2
- Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> max(
- startBottom,
- newBottom
- )
- Hotspot.LEFT, Hotspot.RIGHT -> newBottom
- Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> min(startTop, newTop)
- }
+ left =
+ when (origin) {
+ Hotspot.CENTER -> (newLeft + newRight) / 2
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT -> min(startLeft, newLeft)
+ Hotspot.TOP,
+ Hotspot.BOTTOM -> newLeft
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT -> max(startRight, newRight)
+ }
+ top =
+ when (origin) {
+ Hotspot.CENTER -> (newTop + newBottom) / 2
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT -> min(startTop, newTop)
+ Hotspot.LEFT,
+ Hotspot.RIGHT -> newTop
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom)
+ }
+ right =
+ when (origin) {
+ Hotspot.CENTER -> (newLeft + newRight) / 2
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT -> max(startRight, newRight)
+ Hotspot.TOP,
+ Hotspot.BOTTOM -> newRight
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT -> min(startLeft, newLeft)
+ }
+ bottom =
+ when (origin) {
+ Hotspot.CENTER -> (newTop + newBottom) / 2
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT -> max(startBottom, newBottom)
+ Hotspot.LEFT,
+ Hotspot.RIGHT -> newBottom
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT -> min(startTop, newTop)
+ }
}
return mapOf(
@@ -649,6 +673,7 @@ class ViewHierarchyAnimator {
* view's starting bounds.
*
* Examples:
+ * ```
* 1) destination=TOP
* x---------x x---------x x---------x x---------x x---------x
* | | | | | | x---------x
@@ -667,6 +692,7 @@ class ViewHierarchyAnimator {
* | | -> | | -> | | -> x---x -> x
* | | x-------x x-----x
* x---------x
+ * ```
*/
private fun processEndValuesForRemoval(
destination: Hotspot,
@@ -675,32 +701,54 @@ class ViewHierarchyAnimator {
right: Int,
bottom: Int
): Map<Bound, Int> {
- val endLeft = when (destination) {
- Hotspot.CENTER -> (left + right) / 2
- Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP ->
- left
- Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> right
- }
- val endTop = when (destination) {
- Hotspot.CENTER -> (top + bottom) / 2
- Hotspot.LEFT, Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT ->
- top
- Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT -> bottom
- }
- val endRight = when (destination) {
- Hotspot.CENTER -> (left + right) / 2
- Hotspot.TOP, Hotspot.TOP_RIGHT, Hotspot.RIGHT,
- Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM ->
- right
- Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> left
- }
- val endBottom = when (destination) {
- Hotspot.CENTER -> (top + bottom) / 2
- Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM,
- Hotspot.BOTTOM_LEFT, Hotspot.LEFT ->
- bottom
- Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> top
- }
+ val endLeft =
+ when (destination) {
+ Hotspot.CENTER -> (left + right) / 2
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP -> left
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT -> right
+ }
+ val endTop =
+ when (destination) {
+ Hotspot.CENTER -> (top + bottom) / 2
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT -> top
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT -> bottom
+ }
+ val endRight =
+ when (destination) {
+ Hotspot.CENTER -> (left + right) / 2
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM -> right
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT -> left
+ }
+ val endBottom =
+ when (destination) {
+ Hotspot.CENTER -> (top + bottom) / 2
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT -> bottom
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT -> top
+ }
return mapOf(
Bound.LEFT to endLeft,
@@ -718,6 +766,7 @@ class ViewHierarchyAnimator {
* its center is at the [destination].
*
* Examples:
+ * ```
* 1) destination=TOP
* The child maintains its left and right positions, but is shifted up so that its
* center is on the parent's end top edge.
@@ -725,6 +774,7 @@ class ViewHierarchyAnimator {
* The child shifts so that its center is on the parent's end bottom left corner.
* 3) destination=CENTER
* The child shifts so that its own center is on the parent's end center.
+ * ```
*/
private fun processChildEndValuesForRemoval(
destination: Hotspot,
@@ -738,32 +788,54 @@ class ViewHierarchyAnimator {
val halfWidth = (right - left) / 2
val halfHeight = (bottom - top) / 2
- val endLeft = when (destination) {
- Hotspot.CENTER -> (parentWidth / 2) - halfWidth
- Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> -halfWidth
- Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth
- Hotspot.TOP, Hotspot.BOTTOM -> left
- }
- val endTop = when (destination) {
- Hotspot.CENTER -> (parentHeight / 2) - halfHeight
- Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> -halfHeight
- Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT ->
- parentHeight - halfHeight
- Hotspot.LEFT, Hotspot.RIGHT -> top
- }
- val endRight = when (destination) {
- Hotspot.CENTER -> (parentWidth / 2) + halfWidth
- Hotspot.TOP_RIGHT, Hotspot.RIGHT, Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth
- Hotspot.BOTTOM_LEFT, Hotspot.LEFT, Hotspot.TOP_LEFT -> halfWidth
- Hotspot.TOP, Hotspot.BOTTOM -> right
- }
- val endBottom = when (destination) {
- Hotspot.CENTER -> (parentHeight / 2) + halfHeight
- Hotspot.BOTTOM_RIGHT, Hotspot.BOTTOM, Hotspot.BOTTOM_LEFT ->
- parentHeight + halfHeight
- Hotspot.TOP_LEFT, Hotspot.TOP, Hotspot.TOP_RIGHT -> halfHeight
- Hotspot.LEFT, Hotspot.RIGHT -> bottom
- }
+ val endLeft =
+ when (destination) {
+ Hotspot.CENTER -> (parentWidth / 2) - halfWidth
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT -> -halfWidth
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT -> parentWidth - halfWidth
+ Hotspot.TOP,
+ Hotspot.BOTTOM -> left
+ }
+ val endTop =
+ when (destination) {
+ Hotspot.CENTER -> (parentHeight / 2) - halfHeight
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT -> -halfHeight
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT -> parentHeight - halfHeight
+ Hotspot.LEFT,
+ Hotspot.RIGHT -> top
+ }
+ val endRight =
+ when (destination) {
+ Hotspot.CENTER -> (parentWidth / 2) + halfWidth
+ Hotspot.TOP_RIGHT,
+ Hotspot.RIGHT,
+ Hotspot.BOTTOM_RIGHT -> parentWidth + halfWidth
+ Hotspot.BOTTOM_LEFT,
+ Hotspot.LEFT,
+ Hotspot.TOP_LEFT -> halfWidth
+ Hotspot.TOP,
+ Hotspot.BOTTOM -> right
+ }
+ val endBottom =
+ when (destination) {
+ Hotspot.CENTER -> (parentHeight / 2) + halfHeight
+ Hotspot.BOTTOM_RIGHT,
+ Hotspot.BOTTOM,
+ Hotspot.BOTTOM_LEFT -> parentHeight + halfHeight
+ Hotspot.TOP_LEFT,
+ Hotspot.TOP,
+ Hotspot.TOP_RIGHT -> halfHeight
+ Hotspot.LEFT,
+ Hotspot.RIGHT -> bottom
+ }
return mapOf(
Bound.LEFT to endLeft,
@@ -833,44 +905,49 @@ class ViewHierarchyAnimator {
duration: Long,
ephemeral: Boolean
) {
- val propertyValuesHolders = buildList {
- bounds.forEach { bound ->
- add(
- PropertyValuesHolder.ofInt(
- PROPERTIES[bound],
- startValues.getValue(bound),
- endValues.getValue(bound)
- )
- )
- }
- }.toTypedArray()
+ val propertyValuesHolders =
+ buildList {
+ bounds.forEach { bound ->
+ add(
+ PropertyValuesHolder.ofInt(
+ PROPERTIES[bound],
+ startValues.getValue(bound),
+ endValues.getValue(bound)
+ )
+ )
+ }
+ }
+ .toTypedArray()
(view.getTag(R.id.tag_animator) as? ObjectAnimator)?.cancel()
val animator = ObjectAnimator.ofPropertyValuesHolder(view, *propertyValuesHolders)
animator.interpolator = interpolator
animator.duration = duration
- animator.addListener(object : AnimatorListenerAdapter() {
- var cancelled = false
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ var cancelled = false
- override fun onAnimationEnd(animation: Animator) {
- view.setTag(R.id.tag_animator, null /* tag */)
- bounds.forEach { view.setTag(it.overrideTag, null /* tag */) }
-
- // When an animation is cancelled, a new one might be taking over. We shouldn't
- // unregister the listener yet.
- if (ephemeral && !cancelled) {
- // The duration is the same for the whole hierarchy, so it's safe to remove
- // the listener recursively. We do this because some descendant views might
- // not change bounds, and therefore not animate and leak the listener.
- recursivelyRemoveListener(view)
+ override fun onAnimationEnd(animation: Animator) {
+ view.setTag(R.id.tag_animator, null /* tag */)
+ bounds.forEach { view.setTag(it.overrideTag, null /* tag */) }
+
+ // When an animation is cancelled, a new one might be taking over. We
+ // shouldn't unregister the listener yet.
+ if (ephemeral && !cancelled) {
+ // The duration is the same for the whole hierarchy, so it's safe to
+ // remove the listener recursively. We do this because some descendant
+ // views might not change bounds, and therefore not animate and leak the
+ // listener.
+ recursivelyRemoveListener(view)
+ }
}
- }
- override fun onAnimationCancel(animation: Animator?) {
- cancelled = true
+ override fun onAnimationCancel(animation: Animator?) {
+ cancelled = true
+ }
}
- })
+ )
bounds.forEach { bound -> setBound(view, bound, startValues.getValue(bound)) }
@@ -902,7 +979,15 @@ class ViewHierarchyAnimator {
/** An enum used to determine the origin of addition animations. */
enum class Hotspot {
- CENTER, LEFT, TOP_LEFT, TOP, TOP_RIGHT, RIGHT, BOTTOM_RIGHT, BOTTOM, BOTTOM_LEFT
+ CENTER,
+ LEFT,
+ TOP_LEFT,
+ TOP,
+ TOP_RIGHT,
+ RIGHT,
+ BOTTOM_RIGHT,
+ BOTTOM,
+ BOTTOM_LEFT
}
private enum class Bound(val label: String, val overrideTag: Int) {
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
index 76de7b503451..77640f1992e1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ViewRootSync.kt
@@ -11,37 +11,36 @@ object ViewRootSync {
/**
* Synchronize the next draw between the view roots of [view] and [otherView], then run [then].
*
- * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the
- * next transactions) or disabled (temporarily, on low ram devices). In this case, [then] will
- * be called without synchronizing.
+ * Note that in some cases, the synchronization might not be possible (e.g. WM consumed the next
+ * transactions) or disabled (temporarily, on low ram devices). In this case, [then] will be
+ * called without synchronizing.
*/
- fun synchronizeNextDraw(
- view: View,
- otherView: View,
- then: () -> Unit
- ) {
- if (!view.isAttachedToWindow || view.viewRootImpl == null ||
- !otherView.isAttachedToWindow || otherView.viewRootImpl == null ||
- view.viewRootImpl == otherView.viewRootImpl) {
+ fun synchronizeNextDraw(view: View, otherView: View, then: () -> Unit) {
+ if (
+ !view.isAttachedToWindow ||
+ view.viewRootImpl == null ||
+ !otherView.isAttachedToWindow ||
+ otherView.viewRootImpl == null ||
+ view.viewRootImpl == otherView.viewRootImpl
+ ) {
// No need to synchronize if either the touch surface or dialog view is not attached
// to a window.
then()
return
}
- surfaceSyncer = SurfaceSyncer().apply {
- val syncId = setupSync(Runnable { then() })
- addToSync(syncId, view)
- addToSync(syncId, otherView)
- markSyncReady(syncId)
- }
+ surfaceSyncer =
+ SurfaceSyncer().apply {
+ val syncId = setupSync(Runnable { then() })
+ addToSync(syncId, view)
+ addToSync(syncId, otherView)
+ markSyncReady(syncId)
+ }
}
- /**
- * A Java-friendly API for [synchronizeNextDraw].
- */
+ /** A Java-friendly API for [synchronizeNextDraw]. */
@JvmStatic
fun synchronizeNextDraw(view: View, otherView: View, then: Runnable) {
synchronizeNextDraw(view, otherView, then::run)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/docs/camera.md b/packages/SystemUI/docs/camera.md
index cabc65c53caa..a1c845832049 100644
--- a/packages/SystemUI/docs/camera.md
+++ b/packages/SystemUI/docs/camera.md
@@ -1,34 +1,23 @@
# How double-click power launches the camera
-
-_as of august 2020_
-
+_Last update: July 2022_
## Sequence of events
-
-
-
-1. [PhoneWindowManager.java](/services/core/java/com/android/server/policy/PhoneWindowManager.java) is responsible for all power button presses (see `interceptPowerKeyDown`).
-2. Even though PWMgr has a lot of logic to detect all manner of power button multipresses and gestures, it also checks with GestureLauncherService, which is also [offered the chance](/services/core/java/com/android/server/policy/PhoneWindowManager.java#943) to [intercept](/services/core/java/com/android/server/GestureLauncherService.java#358) the power key.
-3. GLS is responsible for the camera timeout, and if it detects one, it [forwards it to the StatusBarManagerService](/services/core/java/com/android/server/GestureLauncherService.java#475) (which hands it off to SystemUI).
-4. Inside SystemUI, [onCameraLaunchDetected](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#3927) looks at the keyguard state and determines
+1. [PhoneWindowManager.java](/services/core/java/com/android/server/policy/PhoneWindowManager.java) is responsible for all power button presses (see `interceptPowerKeyDown`)
+2. Even though `PhoneWindowManager` has a lot of logic to detect all manner of power button multi-presses and gestures, it also checks with `GestureLauncherService`, which is also [offered the chance](/services/core/java/com/android/server/policy/PhoneWindowManager.java#943) to [intercept](/services/core/java/com/android/server/GestureLauncherService.java) the power key
+3. `GestureLauncherService` is responsible for the camera timeout, and if it detects one, it [forwards it to the StatusBarManagerService](/services/core/java/com/android/server/GestureLauncherService.java) (which hands it off to SystemUI)
+4. Inside SystemUI, `onCameraLaunchDetected` in [CentralSurfacesCommandQueueCallbacks.java](/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java) looks at the keyguard state and determines
1. whether the camera is even allowed
2. whether the screen is on; if not, we need to delay until that happens
3. whether the device is locked (defined as "keyguard is showing").
-5. If the device is unlocked (no keyguard), the camera is launched immediately. [Callsite in onCameraLaunchGestureDetected](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#4047).
-6. If the keyguard is up, however, [KeyguardBottomAreaView.launchCamera](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#477) takes over to handle the "secure camera" (a different intent, usually directing to the same app, but giving that app the cue to not allow access to the photo roll, etc).
-7. If the intent [would have to launch a resolver](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#480) (the user has multiple cameras installed and hasn’t chosen one to always launch for the `SECURE_CAMERA_INTENT`),
- 1. In order to show the resolver, the lockscreen "bouncer" (authentication method) [is first presented](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#523).
-8. Otherwise (just one secure camera), [it is launched](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#501) (with some window animation gymnastics).
-
-
-## Which intent launches?
-
-
-
-* If the keyguard is not showing (device is unlocked)
- * `CameraIntents.getInsecureCameraIntent()`, defined to be `MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA`.
- * [Callsite](/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java#3950) in StatusBar.java.
-* If the keyguard is showing (device locked)
- * [KeyguardBottomAreaView.getCameraIntent()](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#366) is consulted, which allows the "keyguard right button" (which we don’t actually show) to control the camera intent. The [default implementation](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#831) returns one of `CameraIntents.getInsecureCameraIntent()` or `CameraIntents.getSecureCameraIntent()`, which are `MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA` and `MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE`, respectively.
- * [Callsite](/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java#523) in KeyguardBottomAreaView.java.
-* Note that starting in Android 12, as required by some OEMs, if the special string resource `config_cameraGesturePackage` is nonempty, this will be treated as a package name to be added to the insecure camera intent, constraining the invocation to that single app and typically preventing implicit intent resolution. This package must be on the device or the camera gesture will no longer work properly. \ No newline at end of file
+5. If the device is unlocked (no keyguard), the camera is launched immediately
+6. If the keyguard is up, however, [NotificationPanelViewController.launchCamera](/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java) takes over to handle the "secure camera" (a different intent, usually directing to the same app, but giving that app the cue to not allow access to the photo roll, etc).
+7. If the intent would have to launch a resolver (because the user has multiple camera apps installed and has not chosen one to always launch for the `SECURE_CAMERA_INTENT`, then - in order to show the resolver, the lockscreen "bouncer" (authentication method) is first presented
+8. Otherwise (just one secure camera), it is launched
+
+## Which intent launches the camera app?
+[CameraGestureHelper](/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt) encapsulate this logic. Roughly:
+* If the keyguard is not showing (device is unlocked)
+ * `CameraIntents.getInsecureCameraIntent()`, defined to be `MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA`.
+* If the keyguard is showing (device is locked)
+ * one of `CameraIntents.getInsecureCameraIntent()` or `CameraIntents.getSecureCameraIntent()`, which are `MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA` and `MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE`, respectively
+* Note that starting in Android 12, as required by some OEMs, if the special string resource `config_cameraGesturePackage` is nonempty, this will be treated as a package name to be added to the insecure camera intent, constraining the invocation to that single app and typically preventing implicit intent resolution. This package must be on the device or the camera gesture will no longer work properly
diff --git a/packages/SystemUI/docs/device-entry/doze.md b/packages/SystemUI/docs/device-entry/doze.md
index 5ff8851b4c69..6b6dce5da169 100644
--- a/packages/SystemUI/docs/device-entry/doze.md
+++ b/packages/SystemUI/docs/device-entry/doze.md
@@ -17,6 +17,9 @@ Note: The default UI used in AOD shares views with the Lock Screen and does not
### DOZE
Device is asleep and listening for enabled pulsing and wake-up gesture triggers. In this state, no UI shows.
+### DOZE_SUSPEND_TRIGGERS
+Device is asleep and not listening for any triggers to wake up. This state is used only when CAR_MODE is active. In this state, no UI shows.
+
### DOZE_AOD
Device is asleep, showing UI, and listening for enabled pulsing and wake-up triggers. In this state, screen brightness is handled by [DozeScreenBrightness][5] which uses the brightness sensor specified by `doze_brightness_sensor_type` in the [SystemUI config][6]. To save power, this should be a low-powered sensor that shouldn't trigger as often as the light sensor used for on-screen adaptive brightness.
diff --git a/packages/SystemUI/docs/user-file-manager.md b/packages/SystemUI/docs/user-file-manager.md
new file mode 100644
index 000000000000..52fa2066fbe1
--- /dev/null
+++ b/packages/SystemUI/docs/user-file-manager.md
@@ -0,0 +1,33 @@
+# UserFileManager
+
+This class is used to generate file paths and SharedPreferences that is compatible for specific OS
+users in SystemUI. Due to constraints in SystemUI, we can only read/write files as the system user.
+Therefore, for secondary users, we want to store secondary user specific files into the system user
+directory.
+
+
+## Usages
+
+Inject UserFileManager into your class.
+
+### fun getFile(fileName: String, userId: Int): File
+Add a file name and user id. You can retrieve the current user id from UserTracker. This will
+return a java.io File object that contains the file path to write/read to.
+
+i.e. `fileManager.getFile("example.xml", userTracker.userId)`
+
+### fun getSharedPreferences(fileName: String, mode: Int, userId: Int): SharedPreferences
+Add a file name, user id, and PreferencesMode. You can retrieve the current user id from
+UserTracker. This returns SharedPreferences object that is tied to the specific user. Note that if
+the SharedPreferences file does not exist, one will be created automatically. See
+[SharedPreferences documentation](https://developer.android.com/reference/android/content/Context#getSharedPreferences(java.lang.String,%20int))
+for more details.
+
+i.e. `fileManager.getSharedPreferences("prefs.xml", userTracker.userId, 0)`
+
+## Handling User Removal
+
+This class will listen for Intent.ACTION_USER_REMOVED and remove directories that no longer
+corresponding to active users. Additionally, upon start up, the class will run the same query for
+deletion to ensure that there is no stale data.
+
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 3cf76456de91..eac765ab45b2 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -105,6 +105,11 @@ public interface BcSmartspaceDataPlugin extends Plugin {
void setDozeAmount(float amount);
/**
+ * Set the current keyguard bypass enabled status.
+ */
+ default void setKeyguardBypassEnabled(boolean enabled) {}
+
+ /**
* Overrides how Intents/PendingIntents gets launched. Mostly to support auth from
* the lockscreen.
*/
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
index 3058d9466121..bef61b867f7d 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockPlugin.java
@@ -23,7 +23,9 @@ import java.util.TimeZone;
/**
* Plugin used to replace main clock in keyguard.
+ * @deprecated Migrating to ClockProviderPlugin
*/
+@Deprecated
@ProvidesInterface(action = ClockPlugin.ACTION, version = ClockPlugin.VERSION)
public interface ClockPlugin extends Plugin {
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
new file mode 100644
index 000000000000..0c1916074e0c
--- /dev/null
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/ClockProviderPlugin.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.plugins
+
+import android.graphics.drawable.Drawable
+import android.view.View
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.systemui.plugins.annotations.ProvidesInterface
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+
+/** Identifies a clock design */
+typealias ClockId = String
+
+/** A Plugin which exposes the ClockProvider interface */
+@ProvidesInterface(action = ClockProviderPlugin.ACTION, version = ClockProviderPlugin.VERSION)
+interface ClockProviderPlugin : Plugin, ClockProvider {
+ companion object {
+ const val ACTION = "com.android.systemui.action.PLUGIN_CLOCK_PROVIDER"
+ const val VERSION = 1
+ }
+}
+
+/** Interface for building clocks and providing information about those clocks */
+interface ClockProvider {
+ /** Returns metadata for all clocks this provider knows about */
+ fun getClocks(): List<ClockMetadata>
+
+ /** Initializes and returns the target clock design */
+ fun createClock(id: ClockId): Clock
+
+ /** A static thumbnail for rendering in some examples */
+ fun getClockThumbnail(id: ClockId): Drawable?
+}
+
+/** Interface for controlling an active clock */
+interface Clock {
+ /** A small version of the clock, appropriate for smaller viewports */
+ val smallClock: View
+
+ /** A large version of the clock, appropriate when a bigger viewport is available */
+ val largeClock: View
+
+ /** Events that clocks may need to respond to */
+ val events: ClockEvents
+
+ /** Triggers for various animations */
+ val animation: ClockAnimation
+
+ /** Optional method for dumping debug information */
+ fun dump(pw: PrintWriter) { }
+}
+
+/** Events that should call when various rendering parameters change */
+interface ClockEvents {
+ /** Call every time tick */
+ fun onTimeTick()
+
+ /** Call whenever timezone changes */
+ fun onTimeZoneChanged(timeZone: TimeZone) { }
+
+ /** Call whenever the text time format changes (12hr vs 24hr) */
+ fun onTimeFormatChanged(is24Hr: Boolean) { }
+
+ /** Call whenever the locale changes */
+ fun onLocaleChanged(locale: Locale) { }
+
+ /** Call whenever font settings change */
+ fun onFontSettingChanged() { }
+
+ /** Call whenever the color pallete should update */
+ fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) { }
+}
+
+/** Methods which trigger various clock animations */
+interface ClockAnimation {
+ /** Initializes the doze & fold animation positions. Defaults to neither folded nor dozing. */
+ fun initialize(dozeFraction: Float, foldFraction: Float) { }
+
+ /** Runs an enter animation (if any) */
+ fun enter() { }
+
+ /** Sets how far into AOD the device currently is. */
+ fun doze(fraction: Float) { }
+
+ /** Sets how far into the folding animation the device is. */
+ fun fold(fraction: Float) { }
+
+ /** Runs the battery animation (if any). */
+ fun charge() { }
+}
+
+/** Some data about a clock design */
+data class ClockMetadata(
+ val clockId: ClockId,
+ val name: String
+)
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
index 0f10589dfcf9..bd628ccb3c08 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/FalsingManager.java
@@ -29,7 +29,7 @@ import java.lang.annotation.RetentionPolicy;
/**
* Interface that decides whether a touch on the phone was accidental. i.e. Pocket Dialing.
*
- * {@see com.android.systemui.classifier.FalsingManagerImpl}
+ * {@see com.android.systemui.classifier.BrightLineFalsingManager}
*/
@ProvidesInterface(version = FalsingManager.VERSION)
public interface FalsingManager {
diff --git a/packages/SystemUI/proguard.flags b/packages/SystemUI/proguard.flags
index e74b6c78ec80..5b9299c278a9 100644
--- a/packages/SystemUI/proguard.flags
+++ b/packages/SystemUI/proguard.flags
@@ -5,9 +5,9 @@
-keep class com.android.systemui.statusbar.car.CarStatusBar
-keep class com.android.systemui.statusbar.phone.CentralSurfaces
-keep class com.android.systemui.statusbar.tv.TvStatusBar
--keep class com.android.systemui.car.CarSystemUIFactory
--keep class com.android.systemui.SystemUIFactory
--keep class com.android.systemui.tv.TvSystemUIFactory
+-keep class ** extends com.android.systemui.SystemUIInitializer {
+ *;
+}
-keep class * extends com.android.systemui.CoreStartable
-keep class * implements com.android.systemui.CoreStartable$Injector
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_off.xml
new file mode 100644
index 000000000000..538f3284a301
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_off.xml
@@ -0,0 +1,725 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="7.062"
+ android:valueTo="8.578"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="221"
+ android:propertyName="translateX"
+ android:startOffset="28"
+ android:valueFrom="8.578"
+ android:valueTo="-1.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="translateX"
+ android:startOffset="248"
+ android:valueFrom="-1.789"
+ android:valueTo="-7"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="3.312"
+ android:valueTo="2.016"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="221"
+ android:propertyName="translateY"
+ android:startOffset="28"
+ android:valueFrom="2.016"
+ android:valueTo="-8.789"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="translateY"
+ android:startOffset="248"
+ android:valueFrom="-8.789"
+ android:valueTo="-3.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="180"
+ android:valueTo="90"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="166"
+ android:propertyName="rotation"
+ android:startOffset="28"
+ android:valueFrom="90"
+ android:valueTo="90"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="55"
+ android:propertyName="rotation"
+ android:startOffset="193"
+ android:valueFrom="90"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_N_1_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-135"
+ android:valueTo="-180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="-7"
+ android:valueTo="-8.859"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="221"
+ android:propertyName="translateX"
+ android:startOffset="28"
+ android:valueFrom="-8.859"
+ android:valueTo="1.69"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="translateX"
+ android:startOffset="248"
+ android:valueFrom="1.69"
+ android:valueTo="7"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="-3.594"
+ android:valueTo="-1.922"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="221"
+ android:propertyName="translateY"
+ android:startOffset="28"
+ android:valueFrom="-1.922"
+ android:valueTo="8.627"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="translateY"
+ android:startOffset="248"
+ android:valueFrom="8.627"
+ android:valueTo="3.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="360"
+ android:valueTo="270"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="166"
+ android:propertyName="rotation"
+ android:startOffset="28"
+ android:valueFrom="270"
+ android:valueTo="270"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="55"
+ android:propertyName="rotation"
+ android:startOffset="193"
+ android:valueFrom="270"
+ android:valueTo="180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="rotation"
+ android:startOffset="248"
+ android:valueFrom="180"
+ android:valueTo="180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_1_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-135"
+ android:valueTo="-180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M8.52 3.53 C8.52,3.53 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.95 -7.53,0.95 C-7.53,0.95 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.11,2.11 7.11,2.11 C7.11,2.11 7.14,2.14 7.14,2.14 C7.14,2.14 8.48,3.49 8.48,3.49 C8.48,3.49 8.5,3.51 8.5,3.51 C8.5,3.51 8.52,3.53 8.52,3.53c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -6.08,2.36 -6.08,2.36 C-6.08,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 7.49,1.78 7.49,1.78 C7.49,1.78 8.87,0.41 8.87,0.41 C8.87,0.41 8.95,0.5 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="66"
+ android:propertyName="pathData"
+ android:startOffset="28"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -6.08,2.36 -6.08,2.36 C-6.08,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 7.49,1.78 7.49,1.78 C7.49,1.78 8.87,0.41 8.87,0.41 C8.87,0.41 8.95,0.5 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -2.78,5.89 -2.78,5.89 C-2.78,5.89 -1.29,4.47 -1.29,4.47 C-1.29,4.47 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="72"
+ android:propertyName="pathData"
+ android:startOffset="94"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -2.78,5.89 -2.78,5.89 C-2.78,5.89 -1.29,4.47 -1.29,4.47 C-1.29,4.47 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 0.36,8.83 0.36,8.83 C0.36,8.83 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="166"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 0.36,8.83 0.36,8.83 C0.36,8.83 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 3.22,8.8 3.22,8.8 C3.22,8.8 3.21,8.79 3.21,8.79 C3.21,8.79 3.24,8.8 3.24,8.8 C3.24,8.8 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="55"
+ android:propertyName="pathData"
+ android:startOffset="193"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 3.22,8.8 3.22,8.8 C3.22,8.8 3.21,8.79 3.21,8.79 C3.21,8.79 3.24,8.8 3.24,8.8 C3.24,8.8 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 5.82,6.14 5.82,6.14 C5.82,6.14 5.81,6.14 5.81,6.14 C5.81,6.14 5.83,6.14 5.83,6.14 C5.83,6.14 4.39,4.78 4.39,4.78 C4.39,4.78 4.37,4.76 4.37,4.76 C4.37,4.76 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:startOffset="248"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 5.82,6.14 5.82,6.14 C5.82,6.14 5.81,6.14 5.81,6.14 C5.81,6.14 5.83,6.14 5.83,6.14 C5.83,6.14 4.39,4.78 4.39,4.78 C4.39,4.78 4.37,4.76 4.37,4.76 C4.37,4.76 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.91 3.09 C8.91,3.09 8.91,3.1 8.91,3.1 C8.91,3.1 8.9,3.09 8.9,3.09 C8.9,3.09 7.49,1.74 7.49,1.74 C7.49,1.74 7.5,1.75 7.5,1.75 C7.5,1.75 7.48,1.73 7.48,1.73 C7.48,1.73 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.79,2.2 8.91,3.09c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="290"
+ android:valueFrom="M8.91 3.09 C8.91,3.09 8.91,3.1 8.91,3.1 C8.91,3.1 8.9,3.09 8.9,3.09 C8.9,3.09 7.49,1.74 7.49,1.74 C7.49,1.74 7.5,1.75 7.5,1.75 C7.5,1.75 7.48,1.73 7.48,1.73 C7.48,1.73 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.79,2.2 8.91,3.09c "
+ android:valueTo="M9.05 0.57 C9.05,0.57 9.05,0.58 9.05,0.58 C9.05,0.58 9.04,0.57 9.04,0.57 C9.04,0.57 7.6,1.83 7.6,1.83 C7.6,1.83 7.61,1.85 7.61,1.85 C7.61,1.85 7.59,1.83 7.59,1.83 C7.59,1.83 7.61,1.86 7.61,1.86 C7.61,1.86 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 9.05,0.58 9.05,0.58 C9.05,0.58 9.05,0.57 9.05,0.57c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="pathData"
+ android:startOffset="317"
+ android:valueFrom="M9.05 0.57 C9.05,0.57 9.05,0.58 9.05,0.58 C9.05,0.58 9.04,0.57 9.04,0.57 C9.04,0.57 7.6,1.83 7.6,1.83 C7.6,1.83 7.61,1.85 7.61,1.85 C7.61,1.85 7.59,1.83 7.59,1.83 C7.59,1.83 7.61,1.86 7.61,1.86 C7.61,1.86 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 9.05,0.58 9.05,0.58 C9.05,0.58 9.05,0.57 9.05,0.57c "
+ android:valueTo="M5.94 -2.52 C5.94,-2.52 5.94,-2.51 5.94,-2.51 C5.94,-2.51 5.93,-2.52 5.93,-2.52 C5.93,-2.52 4.53,-1.12 4.53,-1.12 C4.53,-1.12 4.55,-1.11 4.55,-1.11 C4.55,-1.11 4.53,-1.13 4.53,-1.13 C4.53,-1.13 4.55,-1.09 4.55,-1.09 C4.55,-1.09 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 5.94,-2.52 5.94,-2.52 C5.94,-2.52 5.94,-2.52 5.94,-2.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-8.52 -3.53 C-8.52,-3.53 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.95 7.53,-0.95 C7.53,-0.95 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.11,-2.11 -7.11,-2.11 C-7.11,-2.11 -7.14,-2.14 -7.14,-2.14 C-7.14,-2.14 -8.48,-3.49 -8.48,-3.49 C-8.48,-3.49 -8.5,-3.51 -8.5,-3.51 C-8.5,-3.51 -8.52,-3.53 -8.52,-3.53c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 6.08,-2.36 6.08,-2.36 C6.08,-2.36 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -7.49,-1.78 -7.49,-1.78 C-7.49,-1.78 -8.87,-0.41 -8.87,-0.41 C-8.87,-0.41 -8.95,-0.5 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="66"
+ android:propertyName="pathData"
+ android:startOffset="28"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 6.08,-2.36 6.08,-2.36 C6.08,-2.36 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -7.49,-1.78 -7.49,-1.78 C-7.49,-1.78 -8.87,-0.41 -8.87,-0.41 C-8.87,-0.41 -8.95,-0.5 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 2.78,-5.89 2.78,-5.89 C2.78,-5.89 1.29,-4.47 1.29,-4.47 C1.29,-4.47 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="72"
+ android:propertyName="pathData"
+ android:startOffset="94"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 2.78,-5.89 2.78,-5.89 C2.78,-5.89 1.29,-4.47 1.29,-4.47 C1.29,-4.47 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 -0.36,-8.83 -0.36,-8.83 C-0.36,-8.83 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="166"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 -0.36,-8.83 -0.36,-8.83 C-0.36,-8.83 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -3.22,-8.8 -3.22,-8.8 C-3.22,-8.8 -3.21,-8.79 -3.21,-8.79 C-3.21,-8.79 -3.24,-8.8 -3.24,-8.8 C-3.24,-8.8 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="55"
+ android:propertyName="pathData"
+ android:startOffset="193"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -3.22,-8.8 -3.22,-8.8 C-3.22,-8.8 -3.21,-8.79 -3.21,-8.79 C-3.21,-8.79 -3.24,-8.8 -3.24,-8.8 C-3.24,-8.8 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -5.81,-6.14 -5.81,-6.14 C-5.81,-6.14 -5.81,-6.13 -5.81,-6.13 C-5.81,-6.13 -5.83,-6.14 -5.83,-6.14 C-5.83,-6.14 -4.39,-4.78 -4.39,-4.78 C-4.39,-4.78 -4.37,-4.76 -4.37,-4.76 C-4.37,-4.76 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:startOffset="248"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -5.81,-6.14 -5.81,-6.14 C-5.81,-6.14 -5.81,-6.13 -5.81,-6.13 C-5.81,-6.13 -5.83,-6.14 -5.83,-6.14 C-5.83,-6.14 -4.39,-4.78 -4.39,-4.78 C-4.39,-4.78 -4.37,-4.76 -4.37,-4.76 C-4.37,-4.76 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.91 -3.09 C-8.91,-3.09 -8.91,-3.09 -8.91,-3.09 C-8.91,-3.09 -8.9,-3.09 -8.9,-3.09 C-8.9,-3.09 -7.49,-1.74 -7.49,-1.74 C-7.49,-1.74 -7.5,-1.75 -7.5,-1.75 C-7.5,-1.75 -7.48,-1.73 -7.48,-1.73 C-7.48,-1.73 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.79,-2.2 -8.91,-3.09c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="290"
+ android:valueFrom="M-8.91 -3.09 C-8.91,-3.09 -8.91,-3.09 -8.91,-3.09 C-8.91,-3.09 -8.9,-3.09 -8.9,-3.09 C-8.9,-3.09 -7.49,-1.74 -7.49,-1.74 C-7.49,-1.74 -7.5,-1.75 -7.5,-1.75 C-7.5,-1.75 -7.48,-1.73 -7.48,-1.73 C-7.48,-1.73 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.79,-2.2 -8.91,-3.09c "
+ android:valueTo="M-9.05 -0.57 C-9.05,-0.57 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.04,-0.57 -9.04,-0.57 C-9.04,-0.57 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.85 -7.61,-1.85 C-7.61,-1.85 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.86 -7.61,-1.86 C-7.61,-1.86 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.05,-0.57 -9.05,-0.57c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="pathData"
+ android:startOffset="317"
+ android:valueFrom="M-9.05 -0.57 C-9.05,-0.57 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.04,-0.57 -9.04,-0.57 C-9.04,-0.57 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.85 -7.61,-1.85 C-7.61,-1.85 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.86 -7.61,-1.86 C-7.61,-1.86 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.05,-0.57 -9.05,-0.57c "
+ android:valueTo="M-5.94 2.52 C-5.94,2.52 -5.94,2.51 -5.94,2.51 C-5.94,2.51 -5.93,2.52 -5.93,2.52 C-5.93,2.52 -4.53,1.12 -4.53,1.12 C-4.53,1.12 -4.55,1.11 -4.55,1.11 C-4.55,1.11 -4.53,1.13 -4.53,1.13 C-4.53,1.13 -4.55,1.09 -4.55,1.09 C-4.55,1.09 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -5.94,2.52 -5.94,2.52 C-5.94,2.52 -5.94,2.52 -5.94,2.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="61"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c "
+ android:valueTo="M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="61"
+ android:valueFrom="M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c "
+ android:valueTo="M6.15 -2.35 C6.15,-2.35 6.15,-2.35 6.15,-2.35 C6.16,-2.34 6.15,-2.35 6.16,-2.35 C6.16,-2.35 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 4.72,-0.93 4.72,-0.93 C4.72,-0.93 4.7,-0.94 4.7,-0.94 C4.7,-0.94 6.15,-2.35 6.15,-2.35c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="143"
+ android:propertyName="pathData"
+ android:startOffset="88"
+ android:valueFrom="M6.15 -2.35 C6.15,-2.35 6.15,-2.35 6.15,-2.35 C6.16,-2.34 6.15,-2.35 6.16,-2.35 C6.16,-2.35 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 4.72,-0.93 4.72,-0.93 C4.72,-0.93 4.7,-0.94 4.7,-0.94 C4.7,-0.94 6.15,-2.35 6.15,-2.35c "
+ android:valueTo="M-0.65 -9.12 C-0.65,-9.12 -0.66,-9.13 -0.66,-9.13 C-0.65,-9.12 -0.66,-9.12 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -0.65,-9.12 -0.65,-9.12c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="232"
+ android:valueFrom="M-0.65 -9.12 C-0.65,-9.12 -0.66,-9.13 -0.66,-9.13 C-0.65,-9.12 -0.66,-9.12 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -0.65,-9.12 -0.65,-9.12c "
+ android:valueTo="M-3.21 -8.85 C-3.21,-8.85 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -3.21,-8.85 -3.21,-8.85c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="pathData"
+ android:startOffset="248"
+ android:valueFrom="M-3.21 -8.85 C-3.21,-8.85 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -3.21,-8.85 -3.21,-8.85c "
+ android:valueTo="M-8.52 -3.53 C-8.52,-3.53 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.11,-2.11 -7.11,-2.11 C-7.11,-2.11 -8.52,-3.53 -8.52,-3.53c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="61"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c "
+ android:valueTo="M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="28"
+ android:propertyName="pathData"
+ android:startOffset="61"
+ android:valueFrom="M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c "
+ android:valueTo="M-6.15 2.35 C-6.15,2.35 -6.15,2.35 -6.15,2.35 C-6.16,2.34 -6.15,2.35 -6.15,2.35 C-6.15,2.35 -6.14,2.36 -6.14,2.36 C-6.14,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -4.72,0.93 -4.72,0.93 C-4.72,0.93 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -6.15,2.35 -6.15,2.35c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="143"
+ android:propertyName="pathData"
+ android:startOffset="88"
+ android:valueFrom="M-6.15 2.35 C-6.15,2.35 -6.15,2.35 -6.15,2.35 C-6.16,2.34 -6.15,2.35 -6.15,2.35 C-6.15,2.35 -6.14,2.36 -6.14,2.36 C-6.14,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -4.72,0.93 -4.72,0.93 C-4.72,0.93 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -6.15,2.35 -6.15,2.35c "
+ android:valueTo="M0.65 9.12 C0.65,9.12 0.66,9.13 0.66,9.13 C0.65,9.12 0.66,9.13 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 0.65,9.12 0.65,9.12c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="232"
+ android:valueFrom="M0.65 9.12 C0.65,9.12 0.66,9.13 0.66,9.13 C0.65,9.12 0.66,9.13 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 0.65,9.12 0.65,9.12c "
+ android:valueTo="M3.21 8.85 C3.21,8.85 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 3.21,8.85 3.21,8.85c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="418"
+ android:propertyName="pathData"
+ android:startOffset="248"
+ android:valueFrom="M3.21 8.85 C3.21,8.85 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 3.21,8.85 3.21,8.85c "
+ android:valueTo="M8.52 3.53 C8.52,3.53 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.11,2.11 7.11,2.11 C7.11,2.11 8.52,3.53 8.52,3.53c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-135"
+ android:valueTo="-180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="683"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_2_G_N_1_T_0"
+ android:rotation="-135"
+ android:translateX="12.008"
+ android:translateY="11.992">
+ <group
+ android:name="_R_G_L_2_G_T_1"
+ android:rotation="180"
+ android:translateX="7.062"
+ android:translateY="3.312">
+ <group
+ android:name="_R_G_L_2_G"
+ android:translateX="6.984"
+ android:translateY="3.547">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.6 -1.06 C-4.6,-1.06 -9.55,-1.06 -9.55,-1.06 C-9.55,-1.06 -9.55,-6.01 -9.55,-6.01 C-9.55,-6.01 -7.55,-6 -7.55,-6 C-7.55,-6 -7.51,-3.04 -7.51,-3.04 C-7.51,-3.04 -4.6,-3.05 -4.6,-3.05 C-4.6,-3.05 -4.6,-1.06 -4.6,-1.06c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_1_T_0"
+ android:rotation="-135"
+ android:translateX="12.008"
+ android:translateY="11.992">
+ <group
+ android:name="_R_G_L_1_G_T_1"
+ android:rotation="360"
+ android:translateX="-7"
+ android:translateY="-3.594">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="6.984"
+ android:translateY="3.547">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.6 -1.06 C-4.6,-1.06 -9.55,-1.06 -9.55,-1.06 C-9.55,-1.06 -9.55,-6.01 -9.55,-6.01 C-9.55,-6.01 -7.55,-6 -7.55,-6 C-7.55,-6 -7.51,-3.04 -7.51,-3.04 C-7.51,-3.04 -4.6,-3.05 -4.6,-3.05 C-4.6,-3.05 -4.6,-1.06 -4.6,-1.06c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="-135"
+ android:translateX="12.008"
+ android:translateY="11.992">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M8.52 3.53 C8.52,3.53 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.95 -7.53,0.95 C-7.53,0.95 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.11,2.11 7.11,2.11 C7.11,2.11 7.14,2.14 7.14,2.14 C7.14,2.14 8.48,3.49 8.48,3.49 C8.48,3.49 8.5,3.51 8.5,3.51 C8.5,3.51 8.52,3.53 8.52,3.53c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-8.52 -3.53 C-8.52,-3.53 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.95 7.53,-0.95 C7.53,-0.95 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.11,-2.11 -7.11,-2.11 C-7.11,-2.11 -7.14,-2.14 -7.14,-2.14 C-7.14,-2.14 -8.48,-3.49 -8.48,-3.49 C-8.48,-3.49 -8.5,-3.51 -8.5,-3.51 C-8.5,-3.51 -8.52,-3.53 -8.52,-3.53c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c " />
+ <path
+ android:name="_R_G_L_0_G_D_3_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_on.xml
new file mode 100644
index 000000000000..bd67d9f8dbaa
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_auto_rotate_icon_on.xml
@@ -0,0 +1,781 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="367"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M8.52 3.53 C8.52,3.53 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.95 -7.53,0.95 C-7.53,0.95 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.11,2.11 7.11,2.11 C7.11,2.11 7.14,2.14 7.14,2.14 C7.14,2.14 8.48,3.49 8.48,3.49 C8.48,3.49 8.5,3.51 8.5,3.51 C8.5,3.51 8.52,3.53 8.52,3.53c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -6.08,2.36 -6.08,2.36 C-6.08,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 7.49,1.78 7.49,1.78 C7.49,1.78 8.87,0.41 8.87,0.41 C8.87,0.41 8.95,0.5 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:startOffset="17"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -6.08,2.36 -6.08,2.36 C-6.08,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 7.49,1.78 7.49,1.78 C7.49,1.78 8.87,0.41 8.87,0.41 C8.87,0.41 8.95,0.5 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -2.78,5.89 -2.78,5.89 C-2.78,5.89 -1.29,4.47 -1.29,4.47 C-1.29,4.47 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="45"
+ android:propertyName="pathData"
+ android:startOffset="59"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -2.78,5.89 -2.78,5.89 C-2.78,5.89 -1.29,4.47 -1.29,4.47 C-1.29,4.47 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 0.36,8.83 0.36,8.83 C0.36,8.83 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="103"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 0.36,8.83 0.36,8.83 C0.36,8.83 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 3.22,8.8 3.22,8.8 C3.22,8.8 3.21,8.79 3.21,8.79 C3.21,8.79 3.24,8.8 3.24,8.8 C3.24,8.8 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="34"
+ android:propertyName="pathData"
+ android:startOffset="121"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 3.22,8.8 3.22,8.8 C3.22,8.8 3.21,8.79 3.21,8.79 C3.21,8.79 3.24,8.8 3.24,8.8 C3.24,8.8 1.8,7.44 1.8,7.44 C1.8,7.44 1.78,7.42 1.78,7.42 C1.78,7.42 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.82 3.18 C8.26,3.74 5.82,6.14 5.82,6.14 C5.82,6.14 5.81,6.14 5.81,6.14 C5.81,6.14 5.83,6.14 5.83,6.14 C5.83,6.14 4.39,4.78 4.39,4.78 C4.39,4.78 4.37,4.76 4.37,4.76 C4.37,4.76 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="26"
+ android:propertyName="pathData"
+ android:startOffset="155"
+ android:valueFrom="M8.82 3.18 C8.26,3.74 5.82,6.14 5.82,6.14 C5.82,6.14 5.81,6.14 5.81,6.14 C5.81,6.14 5.83,6.14 5.83,6.14 C5.83,6.14 4.39,4.78 4.39,4.78 C4.39,4.78 4.37,4.76 4.37,4.76 C4.37,4.76 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.7,2.29 8.82,3.18c "
+ android:valueTo="M8.91 3.09 C8.91,3.09 8.91,3.1 8.91,3.1 C8.91,3.1 8.9,3.09 8.9,3.09 C8.9,3.09 7.49,1.74 7.49,1.74 C7.49,1.74 7.5,1.75 7.5,1.75 C7.5,1.75 7.48,1.73 7.48,1.73 C7.48,1.73 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.79,2.2 8.91,3.09c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="181"
+ android:valueFrom="M8.91 3.09 C8.91,3.09 8.91,3.1 8.91,3.1 C8.91,3.1 8.9,3.09 8.9,3.09 C8.9,3.09 7.49,1.74 7.49,1.74 C7.49,1.74 7.5,1.75 7.5,1.75 C7.5,1.75 7.48,1.73 7.48,1.73 C7.48,1.73 7.5,1.77 7.5,1.77 C7.5,1.77 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 8.36,-0.12 9.05,0.58 C9.86,1.4 9.79,2.2 8.91,3.09c "
+ android:valueTo="M9.05 0.57 C9.05,0.57 9.05,0.58 9.05,0.58 C9.05,0.58 9.04,0.57 9.04,0.57 C9.04,0.57 7.6,1.83 7.6,1.83 C7.6,1.83 7.61,1.85 7.61,1.85 C7.61,1.85 7.59,1.83 7.59,1.83 C7.59,1.83 7.61,1.86 7.61,1.86 C7.61,1.86 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 9.05,0.58 9.05,0.58 C9.05,0.58 9.05,0.57 9.05,0.57c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="52"
+ android:propertyName="pathData"
+ android:startOffset="198"
+ android:valueFrom="M9.05 0.57 C9.05,0.57 9.05,0.58 9.05,0.58 C9.05,0.58 9.04,0.57 9.04,0.57 C9.04,0.57 7.6,1.83 7.6,1.83 C7.6,1.83 7.61,1.85 7.61,1.85 C7.61,1.85 7.59,1.83 7.59,1.83 C7.59,1.83 7.61,1.86 7.61,1.86 C7.61,1.86 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 9.05,0.58 9.05,0.58 C9.05,0.58 9.05,0.57 9.05,0.57c "
+ android:valueTo="M5.94 -2.52 C5.94,-2.52 5.94,-2.51 5.94,-2.51 C5.94,-2.51 5.93,-2.52 5.93,-2.52 C5.93,-2.52 4.53,-1.12 4.53,-1.12 C4.53,-1.12 4.55,-1.11 4.55,-1.11 C4.55,-1.11 4.53,-1.13 4.53,-1.13 C4.53,-1.13 4.55,-1.09 4.55,-1.09 C4.55,-1.09 4.54,-1.11 4.54,-1.11 C4.54,-1.11 5.95,-2.51 5.95,-2.51 C5.95,-2.51 5.94,-2.52 5.94,-2.52 C5.94,-2.52 5.94,-2.52 5.94,-2.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="367"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-8.52 -3.53 C-8.52,-3.53 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.95 7.53,-0.95 C7.53,-0.95 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.11,-2.11 -7.11,-2.11 C-7.11,-2.11 -7.14,-2.14 -7.14,-2.14 C-7.14,-2.14 -8.48,-3.49 -8.48,-3.49 C-8.48,-3.49 -8.5,-3.51 -8.5,-3.51 C-8.5,-3.51 -8.52,-3.53 -8.52,-3.53c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 6.08,-2.36 6.08,-2.36 C6.08,-2.36 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -7.49,-1.78 -7.49,-1.78 C-7.49,-1.78 -8.87,-0.41 -8.87,-0.41 C-8.87,-0.41 -8.95,-0.5 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="41"
+ android:propertyName="pathData"
+ android:startOffset="17"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 6.08,-2.36 6.08,-2.36 C6.08,-2.36 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -7.49,-1.78 -7.49,-1.78 C-7.49,-1.78 -8.87,-0.41 -8.87,-0.41 C-8.87,-0.41 -8.95,-0.5 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 2.78,-5.89 2.78,-5.89 C2.78,-5.89 1.29,-4.47 1.29,-4.47 C1.29,-4.47 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="45"
+ android:propertyName="pathData"
+ android:startOffset="59"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 2.78,-5.89 2.78,-5.89 C2.78,-5.89 1.29,-4.47 1.29,-4.47 C1.29,-4.47 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 -0.36,-8.83 -0.36,-8.83 C-0.36,-8.83 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="103"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 -0.36,-8.83 -0.36,-8.83 C-0.36,-8.83 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -3.22,-8.8 -3.22,-8.8 C-3.22,-8.8 -3.21,-8.79 -3.21,-8.79 C-3.21,-8.79 -3.24,-8.8 -3.24,-8.8 C-3.24,-8.8 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="34"
+ android:propertyName="pathData"
+ android:startOffset="121"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -3.22,-8.8 -3.22,-8.8 C-3.22,-8.8 -3.21,-8.79 -3.21,-8.79 C-3.21,-8.79 -3.24,-8.8 -3.24,-8.8 C-3.24,-8.8 -1.8,-7.44 -1.8,-7.44 C-1.8,-7.44 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.82 -3.18 C-8.26,-3.74 -5.81,-6.14 -5.81,-6.14 C-5.81,-6.14 -5.81,-6.13 -5.81,-6.13 C-5.81,-6.13 -5.83,-6.14 -5.83,-6.14 C-5.83,-6.14 -4.39,-4.78 -4.39,-4.78 C-4.39,-4.78 -4.37,-4.76 -4.37,-4.76 C-4.37,-4.76 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="26"
+ android:propertyName="pathData"
+ android:startOffset="155"
+ android:valueFrom="M-8.82 -3.18 C-8.26,-3.74 -5.81,-6.14 -5.81,-6.14 C-5.81,-6.14 -5.81,-6.13 -5.81,-6.13 C-5.81,-6.13 -5.83,-6.14 -5.83,-6.14 C-5.83,-6.14 -4.39,-4.78 -4.39,-4.78 C-4.39,-4.78 -4.37,-4.76 -4.37,-4.76 C-4.37,-4.76 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.7,-2.29 -8.82,-3.18c "
+ android:valueTo="M-8.91 -3.09 C-8.91,-3.09 -8.91,-3.09 -8.91,-3.09 C-8.91,-3.09 -8.9,-3.09 -8.9,-3.09 C-8.9,-3.09 -7.49,-1.74 -7.49,-1.74 C-7.49,-1.74 -7.5,-1.75 -7.5,-1.75 C-7.5,-1.75 -7.48,-1.73 -7.48,-1.73 C-7.48,-1.73 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.79,-2.2 -8.91,-3.09c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="181"
+ android:valueFrom="M-8.91 -3.09 C-8.91,-3.09 -8.91,-3.09 -8.91,-3.09 C-8.91,-3.09 -8.9,-3.09 -8.9,-3.09 C-8.9,-3.09 -7.49,-1.74 -7.49,-1.74 C-7.49,-1.74 -7.5,-1.75 -7.5,-1.75 C-7.5,-1.75 -7.48,-1.73 -7.48,-1.73 C-7.48,-1.73 -7.5,-1.77 -7.5,-1.77 C-7.5,-1.77 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -8.36,0.12 -9.05,-0.58 C-9.86,-1.4 -9.79,-2.2 -8.91,-3.09c "
+ android:valueTo="M-9.05 -0.57 C-9.05,-0.57 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.04,-0.57 -9.04,-0.57 C-9.04,-0.57 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.85 -7.61,-1.85 C-7.61,-1.85 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.86 -7.61,-1.86 C-7.61,-1.86 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.05,-0.57 -9.05,-0.57c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="52"
+ android:propertyName="pathData"
+ android:startOffset="198"
+ android:valueFrom="M-9.05 -0.57 C-9.05,-0.57 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.04,-0.57 -9.04,-0.57 C-9.04,-0.57 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.85 -7.61,-1.85 C-7.61,-1.85 -7.59,-1.83 -7.59,-1.83 C-7.59,-1.83 -7.61,-1.86 -7.61,-1.86 C-7.61,-1.86 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -9.05,-0.58 -9.05,-0.58 C-9.05,-0.58 -9.05,-0.57 -9.05,-0.57c "
+ android:valueTo="M-5.94 2.52 C-5.94,2.52 -5.94,2.51 -5.94,2.51 C-5.94,2.51 -5.93,2.52 -5.93,2.52 C-5.93,2.52 -4.53,1.12 -4.53,1.12 C-4.53,1.12 -4.55,1.11 -4.55,1.11 C-4.55,1.11 -4.53,1.13 -4.53,1.13 C-4.53,1.13 -4.55,1.09 -4.55,1.09 C-4.55,1.09 -4.54,1.11 -4.54,1.11 C-4.54,1.11 -5.95,2.51 -5.95,2.51 C-5.95,2.51 -5.94,2.52 -5.94,2.52 C-5.94,2.52 -5.94,2.52 -5.94,2.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="38"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c "
+ android:valueTo="M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="38"
+ android:valueFrom="M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c "
+ android:valueTo="M6.15 -2.35 C6.15,-2.35 6.15,-2.35 6.15,-2.35 C6.16,-2.34 6.15,-2.35 6.16,-2.35 C6.16,-2.35 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 4.72,-0.93 4.72,-0.93 C4.72,-0.93 4.7,-0.94 4.7,-0.94 C4.7,-0.94 6.15,-2.35 6.15,-2.35c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="90"
+ android:propertyName="pathData"
+ android:startOffset="55"
+ android:valueFrom="M6.15 -2.35 C6.15,-2.35 6.15,-2.35 6.15,-2.35 C6.16,-2.34 6.15,-2.35 6.16,-2.35 C6.16,-2.35 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 4.72,-0.93 4.72,-0.93 C4.72,-0.93 4.7,-0.94 4.7,-0.94 C4.7,-0.94 6.15,-2.35 6.15,-2.35c "
+ android:valueTo="M-0.65 -9.12 C-0.65,-9.12 -0.66,-9.13 -0.66,-9.13 C-0.65,-9.12 -0.66,-9.12 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -0.65,-9.12 -0.65,-9.12c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="10"
+ android:propertyName="pathData"
+ android:startOffset="145"
+ android:valueFrom="M-0.65 -9.12 C-0.65,-9.12 -0.66,-9.13 -0.66,-9.13 C-0.65,-9.12 -0.66,-9.12 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -0.65,-9.12 -0.65,-9.12c "
+ android:valueTo="M-3.21 -8.85 C-3.21,-8.85 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -3.21,-8.85 -3.21,-8.85c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="pathData"
+ android:startOffset="155"
+ android:valueFrom="M-3.21 -8.85 C-3.21,-8.85 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -1.8,-7.43 -1.8,-7.43 C-1.8,-7.43 -3.21,-8.85 -3.21,-8.85c "
+ android:valueTo="M-8.52 -3.53 C-8.52,-3.53 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.94 7.53,-0.94 C7.53,-0.94 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.11,-2.11 -7.11,-2.11 C-7.11,-2.11 -8.52,-3.53 -8.52,-3.53c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="38"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c "
+ android:valueTo="M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="pathData"
+ android:startOffset="38"
+ android:valueFrom="M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c "
+ android:valueTo="M-6.15 2.35 C-6.15,2.35 -6.15,2.35 -6.15,2.35 C-6.16,2.34 -6.15,2.35 -6.15,2.35 C-6.15,2.35 -6.14,2.36 -6.14,2.36 C-6.14,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -4.72,0.93 -4.72,0.93 C-4.72,0.93 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -6.15,2.35 -6.15,2.35c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="90"
+ android:propertyName="pathData"
+ android:startOffset="55"
+ android:valueFrom="M-6.15 2.35 C-6.15,2.35 -6.15,2.35 -6.15,2.35 C-6.16,2.34 -6.15,2.35 -6.15,2.35 C-6.15,2.35 -6.14,2.36 -6.14,2.36 C-6.14,2.36 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -4.72,0.93 -4.72,0.93 C-4.72,0.93 -4.7,0.94 -4.7,0.94 C-4.7,0.94 -6.15,2.35 -6.15,2.35c "
+ android:valueTo="M0.65 9.12 C0.65,9.12 0.66,9.13 0.66,9.13 C0.65,9.12 0.66,9.13 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 0.65,9.12 0.65,9.12c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="10"
+ android:propertyName="pathData"
+ android:startOffset="145"
+ android:valueFrom="M0.65 9.12 C0.65,9.12 0.66,9.13 0.66,9.13 C0.65,9.12 0.66,9.13 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 0.65,9.12 0.65,9.12c "
+ android:valueTo="M3.21 8.85 C3.21,8.85 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 3.21,8.85 3.21,8.85c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="pathData"
+ android:startOffset="155"
+ android:valueFrom="M3.21 8.85 C3.21,8.85 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 1.8,7.43 1.8,7.43 C1.8,7.43 3.21,8.85 3.21,8.85c "
+ android:valueTo="M8.52 3.53 C8.52,3.53 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.11,2.11 7.11,2.11 C7.11,2.11 8.52,3.53 8.52,3.53c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="-135"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="-7"
+ android:valueTo="-8.859"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="138"
+ android:propertyName="translateX"
+ android:startOffset="17"
+ android:valueFrom="-8.859"
+ android:valueTo="1.734"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="translateX"
+ android:startOffset="155"
+ android:valueFrom="1.734"
+ android:valueTo="7"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="-3.594"
+ android:valueTo="-1.922"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="138"
+ android:propertyName="translateY"
+ android:startOffset="17"
+ android:valueFrom="-1.922"
+ android:valueTo="8.671"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="translateY"
+ android:startOffset="155"
+ android:valueFrom="8.671"
+ android:valueTo="3.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="360"
+ android:valueTo="270"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="103"
+ android:propertyName="rotation"
+ android:startOffset="17"
+ android:valueFrom="270"
+ android:valueTo="270"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="34"
+ android:propertyName="rotation"
+ android:startOffset="121"
+ android:valueFrom="270"
+ android:valueTo="180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="rotation"
+ android:startOffset="155"
+ android:valueFrom="180"
+ android:valueTo="180"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_N_3_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="-135"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="7.062"
+ android:valueTo="8.578"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="138"
+ android:propertyName="translateX"
+ android:startOffset="17"
+ android:valueFrom="8.578"
+ android:valueTo="-1.656"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="translateX"
+ android:startOffset="155"
+ android:valueFrom="-1.656"
+ android:valueTo="-7"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="translateY"
+ android:startOffset="0"
+ android:valueFrom="3.312"
+ android:valueTo="2.016"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="138"
+ android:propertyName="translateY"
+ android:startOffset="17"
+ android:valueFrom="2.016"
+ android:valueTo="-8.656"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="261"
+ android:propertyName="translateY"
+ android:startOffset="155"
+ android:valueFrom="-8.656"
+ android:valueTo="-3.5"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="180"
+ android:valueTo="90"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="103"
+ android:propertyName="rotation"
+ android:startOffset="17"
+ android:valueFrom="90"
+ android:valueTo="90"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="34"
+ android:propertyName="rotation"
+ android:startOffset="121"
+ android:valueFrom="90"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_N_3_T_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="367"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="-135"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_2_G"
+ android:rotation="0"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M8.52 3.53 C8.52,3.53 2.94,9.08 2.94,9.08 C2.3,9.72 1.27,9.74 0.65,9.12 C0.65,9.12 -7.53,0.95 -7.53,0.95 C-7.53,0.95 -4.7,0.94 -4.7,0.94 C-4.7,0.94 1.78,7.42 1.78,7.42 C1.78,7.42 7.11,2.11 7.11,2.11 C7.11,2.11 7.14,2.14 7.14,2.14 C7.14,2.14 8.48,3.49 8.48,3.49 C8.48,3.49 8.5,3.51 8.5,3.51 C8.5,3.51 8.52,3.53 8.52,3.53c " />
+ <path
+ android:name="_R_G_L_2_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-8.52 -3.53 C-8.52,-3.53 -2.94,-9.08 -2.94,-9.08 C-2.3,-9.72 -1.27,-9.74 -0.65,-9.12 C-0.65,-9.12 7.53,-0.95 7.53,-0.95 C7.53,-0.95 4.7,-0.94 4.7,-0.94 C4.7,-0.94 -1.78,-7.42 -1.78,-7.42 C-1.78,-7.42 -7.11,-2.11 -7.11,-2.11 C-7.11,-2.11 -7.14,-2.14 -7.14,-2.14 C-7.14,-2.14 -8.48,-3.49 -8.48,-3.49 C-8.48,-3.49 -8.5,-3.51 -8.5,-3.51 C-8.5,-3.51 -8.52,-3.53 -8.52,-3.53c " />
+ <path
+ android:name="_R_G_L_2_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M7.54 -0.94 C7.54,-0.94 7.54,-0.95 7.54,-0.95 C7.55,-0.94 7.54,-0.94 7.55,-0.94 C7.55,-0.94 7.53,-0.94 7.53,-0.94 C7.53,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94 C7.54,-0.94 7.52,-0.94 7.52,-0.94 C7.52,-0.94 7.54,-0.94 7.54,-0.94c " />
+ <path
+ android:name="_R_G_L_2_G_D_3_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-7.54 0.94 C-7.54,0.94 -7.54,0.95 -7.54,0.95 C-7.55,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.53,0.94 -7.53,0.94 C-7.53,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94 C-7.54,0.94 -7.52,0.94 -7.52,0.94 C-7.52,0.94 -7.54,0.94 -7.54,0.94c " />
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_3_T_0"
+ android:rotation="0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_1_G_T_1"
+ android:rotation="360"
+ android:translateX="-7"
+ android:translateY="-3.594">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="6.984"
+ android:translateY="3.547">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.6 -1.06 C-4.6,-1.06 -9.55,-1.06 -9.55,-1.06 C-9.55,-1.06 -9.55,-6.01 -9.55,-6.01 C-9.55,-6.01 -7.55,-6 -7.55,-6 C-7.55,-6 -7.51,-3.04 -7.51,-3.04 C-7.51,-3.04 -4.6,-3.05 -4.6,-3.05 C-4.6,-3.05 -4.6,-1.06 -4.6,-1.06c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G_N_3_T_0"
+ android:rotation="0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:rotation="180"
+ android:translateX="7.062"
+ android:translateY="3.312">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="6.984"
+ android:translateY="3.547">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-4.6 -1.06 C-4.6,-1.06 -9.55,-1.06 -9.55,-1.06 C-9.55,-1.06 -9.55,-6.01 -9.55,-6.01 C-9.55,-6.01 -7.55,-6 -7.55,-6 C-7.55,-6 -7.51,-3.04 -7.51,-3.04 C-7.51,-3.04 -4.6,-3.05 -4.6,-3.05 C-4.6,-3.05 -4.6,-1.06 -4.6,-1.06c " />
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_off.xml b/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_off.xml
new file mode 100644
index 000000000000..17a76116db25
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_off.xml
@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <aapt:attr name="android:drawable">
+ <vector
+ android:height="24dp"
+ android:width="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group android:name="_R_G_L_2_G"
+ android:translateX="14.125"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-1 6.17 C-1,6.17 0.88,4.29 0.88,4.29 C0.88,4.29 -1,2.41 -1,2.41 C-1,2.41 -1,6.17 -1,6.17c M0.88 -4.29 C0.88,-4.29 -1,-6.17 -1,-6.17 C-1,-6.17 -1,-2.41 -1,-2.41 C-1,-2.41 0.88,-4.29 0.88,-4.29c M-2 -10 C-2,-10 3.71,-4.29 3.71,-4.29 C3.71,-4.29 -0.59,0 -0.59,0 C-0.59,0 3.71,4.29 3.71,4.29 C3.71,4.29 -2,10 -2,10 C-2,10 -3,10 -3,10 C-3,10 -3,2.41 -3,2.41 C-3,2.41 -7.59,7 -7.59,7 C-7.59,7 -9.01,5.59 -9.01,5.59 C-9.01,5.59 -3.41,0 -3.41,0 C-3.41,0 -9.01,-5.59 -9.01,-5.59 C-9.01,-5.59 -7.59,-7 -7.59,-7 C-7.59,-7 -3,-2.41 -3,-2.41 C-3,-2.41 -3,-10 -3,-10 C-3,-10 -2,-10 -2,-10c "/>
+ </group>
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="14.125"
+ android:translateY="12"
+ android:pivotX="-9.109"
+ android:scaleX="1"
+ android:scaleY="1">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M-9.09 -1.5 C-8.26,-1.5 -7.59,-0.83 -7.59,0 C-7.59,0.83 -8.26,1.5 -9.09,1.5 C-9.91,1.5 -10.59,0.83 -10.59,0 C-10.59,-0.83 -9.91,-1.5 -9.09,-1.5c "/>
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="14.125"
+ android:translateY="12"
+ android:pivotX="4.875"
+ android:scaleX="1"
+ android:scaleY="1">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillColor="#ffffff"
+ android:fillAlpha="1"
+ android:fillType="nonZero"
+ android:pathData=" M4.92 -1.5 C5.75,-1.5 6.42,-0.83 6.42,0 C6.42,0.83 5.75,1.5 4.92,1.5 C4.09,1.5 3.42,0.83 3.42,0 C3.42,-0.83 4.09,-1.5 4.92,-1.5c "/>
+ </group>
+ </group>
+ <group android:name="time_group"/>
+ </vector>
+ </aapt:attr>
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator
+ android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr
+ name="android:interpolator">
+ <pathInterpolator
+ android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="scaleX"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator
+ android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:propertyName="scaleY"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator
+ android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0"/>
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:propertyName="translateX"
+ android:duration="150"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType"/>
+ </set>
+ </aapt:attr>
+ </target>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_on.xml b/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_on.xml
new file mode 100644
index 000000000000..2dba48cf155d
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_on.xml
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="scaleX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="scaleY"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.35,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="267"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_2_G"
+ android:translateX="14.125"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-1 6.17 C-1,6.17 0.88,4.29 0.88,4.29 C0.88,4.29 -1,2.41 -1,2.41 C-1,2.41 -1,6.17 -1,6.17c M0.88 -4.29 C0.88,-4.29 -1,-6.17 -1,-6.17 C-1,-6.17 -1,-2.41 -1,-2.41 C-1,-2.41 0.88,-4.29 0.88,-4.29c M-2 -10 C-2,-10 3.71,-4.29 3.71,-4.29 C3.71,-4.29 -0.59,0 -0.59,0 C-0.59,0 3.71,4.29 3.71,4.29 C3.71,4.29 -2,10 -2,10 C-2,10 -3,10 -3,10 C-3,10 -3,2.41 -3,2.41 C-3,2.41 -7.59,7 -7.59,7 C-7.59,7 -9.01,5.59 -9.01,5.59 C-9.01,5.59 -3.41,0 -3.41,0 C-3.41,0 -9.01,-5.59 -9.01,-5.59 C-9.01,-5.59 -7.59,-7 -7.59,-7 C-7.59,-7 -3,-2.41 -3,-2.41 C-3,-2.41 -3,-10 -3,-10 C-3,-10 -2,-10 -2,-10c " />
+ </group>
+ <group
+ android:name="_R_G_L_1_G"
+ android:pivotX="-9.109"
+ android:scaleX="0"
+ android:scaleY="0"
+ android:translateX="14.125"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-9.09 -1.5 C-8.26,-1.5 -7.59,-0.83 -7.59,0 C-7.59,0.83 -8.26,1.5 -9.09,1.5 C-9.91,1.5 -10.59,0.83 -10.59,0 C-10.59,-0.83 -9.91,-1.5 -9.09,-1.5c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:pivotX="4.875"
+ android:scaleX="0"
+ android:scaleY="0"
+ android:translateX="14.125"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M4.92 -1.5 C5.75,-1.5 6.42,-0.83 6.42,0 C6.42,0.83 5.75,1.5 4.92,1.5 C4.09,1.5 3.42,0.83 3.42,0 C3.42,-0.83 4.09,-1.5 4.92,-1.5c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_search.xml b/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_search.xml
new file mode 100644
index 000000000000..3697769cfa34
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/drawable/qs_bluetooth_icon_search.xml
@@ -0,0 +1,268 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="167"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="167"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="500"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="167"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_4_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="167"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="fillAlpha"
+ android:startOffset="500"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1017"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="14.125"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-1 6.17 C-1,6.17 0.88,4.29 0.88,4.29 C0.88,4.29 -1,2.41 -1,2.41 C-1,2.41 -1,6.17 -1,6.17c M0.88 -4.29 C0.88,-4.29 -1,-6.17 -1,-6.17 C-1,-6.17 -1,-2.41 -1,-2.41 C-1,-2.41 0.88,-4.29 0.88,-4.29c M-2 -10 C-2,-10 3.71,-4.29 3.71,-4.29 C3.71,-4.29 -0.59,0 -0.59,0 C-0.59,0 3.71,4.29 3.71,4.29 C3.71,4.29 -2,10 -2,10 C-2,10 -3,10 -3,10 C-3,10 -3,2.41 -3,2.41 C-3,2.41 -7.59,7 -7.59,7 C-7.59,7 -9.01,5.59 -9.01,5.59 C-9.01,5.59 -3.41,0 -3.41,0 C-3.41,0 -9.01,-5.59 -9.01,-5.59 C-9.01,-5.59 -7.59,-7 -7.59,-7 C-7.59,-7 -3,-2.41 -3,-2.41 C-3,-2.41 -3,-10 -3,-10 C-3,-10 -2,-10 -2,-10c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M4.56 2.33 C4.56,2.33 2.24,0.01 2.24,0.01 C2.24,0.01 4.57,-2.31 4.57,-2.31 C4.84,-1.59 5,-0.82 5,0 C5,0.82 4.84,1.61 4.56,2.33c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M6.27 -4.03 C6.27,-4.03 7.53,-5.29 7.53,-5.29 C8.46,-3.77 9,-1.99 9.01,-0.1 C9.01,1.85 8.44,3.67 7.47,5.21 C7.47,5.21 6.27,4.01 6.27,4.01 C6.89,2.81 7.25,1.44 7.25,-0.01 C7.25,-1.46 6.9,-2.82 6.27,-4.03c " />
+ <path
+ android:name="_R_G_L_0_G_D_3_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-8.6 -2.33 C-8.6,-2.33 -6.28,-0.01 -6.28,-0.01 C-6.28,-0.01 -8.61,2.31 -8.61,2.31 C-8.88,1.59 -9.04,0.82 -9.04,0 C-9.04,-0.82 -8.88,-1.61 -8.6,-2.33c " />
+ <path
+ android:name="_R_G_L_0_G_D_4_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-10.31 4.03 C-10.31,4.03 -11.57,5.29 -11.57,5.29 C-12.5,3.77 -13.04,1.99 -13.05,0.1 C-13.05,-1.85 -12.48,-3.67 -11.51,-5.21 C-11.51,-5.21 -10.31,-4.01 -10.31,-4.01 C-10.93,-2.81 -11.29,-1.44 -11.29,0.01 C-11.29,1.46 -10.94,2.82 -10.31,4.03c " />
+ <path
+ android:name="_R_G_L_0_G_D_5_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#000000"
+ android:fillType="nonZero"
+ android:pathData=" M-9.09 0 C-9.09,0 -9.09,0 -9.09,0 C-9.09,0 -9.09,0 -9.09,0 C-9.09,0 -9.09,0 -9.09,0 C-9.09,0 -9.09,0 -9.09,0c " />
+ <path
+ android:name="_R_G_L_0_G_D_6_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#000000"
+ android:fillType="nonZero"
+ android:pathData=" M4.92 0 C4.92,0 4.92,0 4.92,0 C4.92,0 4.92,0 4.92,0 C4.92,0 4.92,0 4.92,0 C4.92,0 4.92,0 4.92,0c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res-keyguard/font/clock.xml b/packages/SystemUI/res-keyguard/font/clock.xml
index d0867e9aa546..0137dc39921f 100644
--- a/packages/SystemUI/res-keyguard/font/clock.xml
+++ b/packages/SystemUI/res-keyguard/font/clock.xml
@@ -22,6 +22,7 @@
** Should include all numeric glyphs in all supported locales.
** Recommended: font with variable width to support AOD => LS animations
-->
+<!-- TODO: Remove when clock migration complete -->
<font-family xmlns:android="http://schemas.android.com/apk/res/android">
<font android:typeface="monospace"/>
</font-family> \ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
index 36035fc87e40..01e3de2315af 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_user_switcher.xml
@@ -45,6 +45,7 @@
style="@style/Bouncer.UserSwitcher.Spinner.Header"
android:clickable="false"
android:id="@+id/user_switcher_header"
+ android:textDirection="locale"
android:layout_width="@dimen/bouncer_user_switcher_width"
android:layout_height="wrap_content" />
</LinearLayout>>
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index 87a9825af1cb..6a38507b2ad7 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,7 +31,7 @@
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:paddingStart="@dimen/clock_padding_start">
- <com.android.keyguard.AnimatableClockView
+ <com.android.systemui.shared.clocks.AnimatableClockView
android:id="@+id/animatable_clock_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
@@ -53,7 +53,7 @@
android:layout_height="wrap_content"
android:layout_below="@id/keyguard_slice_view"
android:visibility="gone">
- <com.android.keyguard.AnimatableClockView
+ <com.android.systemui.shared.clocks.AnimatableClockView
android:id="@+id/animatable_clock_view_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res-keyguard/values/attrs.xml b/packages/SystemUI/res-keyguard/values/attrs.xml
index 25be37adac5b..f2a1e26dc9ce 100644
--- a/packages/SystemUI/res-keyguard/values/attrs.xml
+++ b/packages/SystemUI/res-keyguard/values/attrs.xml
@@ -41,10 +41,4 @@
<attr name="passwordStyle" format="reference" />
<attr name="numPadKeyStyle" format="reference" />
-
- <declare-styleable name="AnimatableClockView">
- <attr name="dozeWeight" format="integer" />
- <attr name="lockScreenWeight" format="integer" />
- <attr name="chargeAnimationDelay" format="integer" />
- </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/dimens.xml b/packages/SystemUI/res-keyguard/values/dimens.xml
index 77f1803523a8..acf3e4dcf02a 100644
--- a/packages/SystemUI/res-keyguard/values/dimens.xml
+++ b/packages/SystemUI/res-keyguard/values/dimens.xml
@@ -102,12 +102,13 @@
screen. -->
<item name="half_opened_bouncer_height_ratio" type="dimen" format="float">0.0</item>
- <!-- The actual amount of translation that is applied to the bouncer when it animates from one
- side of the screen to the other in one-handed mode. Note that it will always translate from
- the side of the screen to the other (it will "jump" closer to the destination while the
- opacity is zero), but this controls how much motion will actually be applied to it while
- animating. Larger values will cause it to move "faster" while fading out/in. -->
- <dimen name="one_handed_bouncer_move_animation_translation">120dp</dimen>
+ <!-- The actual amount of translation that is applied to the security when it animates from one
+ side of the screen to the other in one-handed or user switcher mode. Note that it will
+ always translate from the side of the screen to the other (it will "jump" closer to the
+ destination while the opacity is zero), but this controls how much motion will actually be
+ applied to it while animating. Larger values will cause it to move "faster" while
+ fading out/in. -->
+ <dimen name="security_shift_animation_translation">120dp</dimen>
<dimen name="bouncer_user_switcher_header_text_size">20sp</dimen>
diff --git a/packages/SystemUI/res-keyguard/values/donottranslate.xml b/packages/SystemUI/res-keyguard/values/donottranslate.xml
index e677797dc4a7..f9872f8da192 100644
--- a/packages/SystemUI/res-keyguard/values/donottranslate.xml
+++ b/packages/SystemUI/res-keyguard/values/donottranslate.xml
@@ -23,11 +23,5 @@
<!-- Skeleton string format for displaying the date shorter. -->
<string name="abbrev_month_day_no_year">MMMd</string>
- <!-- Skeleton string format for displaying the time in 12-hour format. -->
- <string name="clock_12hr_format">hm</string>
-
- <!-- Skeleton string format for displaying the time in 24-hour format. -->
- <string name="clock_24hr_format">Hm</string>
-
<string name="num_pad_key_ratio">1</string>
</resources>
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index 1be2d997719d..babe924c4615 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -164,21 +164,21 @@
Displayed in a dialog box. -->
<string name="kg_password_wrong_pin_code_pukked">Incorrect SIM PIN code you must now contact your carrier to unlock your device.</string>
<!-- Instructions telling the user that they entered the wrong SIM PIN while trying
- to unlock the keyguard. Displayed in a dialog box. -->
- <plurals name="kg_password_wrong_pin_code">
- <item quantity="one">Incorrect SIM PIN code, you have <xliff:g id="number">%d</xliff:g> remaining attempt before you must contact your carrier to unlock your device.</item>
- <item quantity="other">Incorrect SIM PIN code, you have <xliff:g id="number">%d</xliff:g> remaining attempts.</item>
- </plurals>
+ to unlock the keyguard. Displayed in a dialog box. [CHAR LIMIT=NONE] -->
+ <string name="kg_password_wrong_pin_code"> {count, plural,
+ =1 {Incorrect SIM PIN code, you have # remaining attempt before you must contact your carrier to unlock your device.}
+ other {Incorrect SIM PIN code, you have # remaining attempts. }
+ }</string>
<!-- Instructions telling the user that they have exhausted SIM PUK retries and the SIM is now unusable.
Displayed in a dialog box. -->
<string name="kg_password_wrong_puk_code_dead">SIM is unusable. Contact your carrier.</string>
<!-- Instructions telling the user that they entered the wrong puk while trying
- to unlock the keyguard. Displayed in a dialog box. -->
- <plurals name="kg_password_wrong_puk_code">
- <item quantity="one">Incorrect SIM PUK code, you have <xliff:g id="number">%d</xliff:g> remaining attempt before SIM becomes permanently unusable.</item>
- <item quantity="other">Incorrect SIM PUK code, you have <xliff:g id="number">%d</xliff:g> remaining attempts before SIM becomes permanently unusable.</item>
- </plurals>
+ to unlock the keyguard. Displayed in a dialog box. [CHAR LIMIT=NONE] -->
+ <string name="kg_password_wrong_puk_code">{count, plural,
+ =1 {Incorrect SIM PUK code, you have # remaining attempt before SIM becomes permanently unusable.}
+ other {Incorrect SIM PUK code, you have # remaining attempts before SIM becomes permanently unusable.}
+ }</string>
<!-- Instructions telling the user that the operation to unlock the keyguard
with SIM PIN failed. Displayed in one line in a large font. -->
<string name="kg_password_pin_failed">SIM PIN operation failed!</string>
@@ -223,21 +223,17 @@
<!-- Error message indicating that the camera privacy sensor has been turned on [CHAR LIMIT=53] -->
<string name="kg_face_sensor_privacy_enabled">To use Face Unlock, turn on camera access in Settings</string>
- <!-- Instructions telling the user remaining times when enter SIM PIN view. -->
- <plurals name="kg_password_default_pin_message">
- <item quantity="one">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
-attempt before you must contact your carrier to unlock your device.</item>
- <item quantity="other">Enter SIM PIN. You have <xliff:g id="number">%d</xliff:g> remaining
-attempts.</item>
- </plurals>
-
- <!-- Instructions telling the user remaining times when enter SIM PUK view. -->
- <plurals name="kg_password_default_puk_message">
- <item quantity="one">SIM is now disabled. Enter PUK code to continue. You have <xliff:g id="
-number">%d</xliff:g> remaining attempt before SIM becomes permanently unusable. Contact carrier for details.</item>
- <item quantity="other">SIM is now disabled. Enter PUK code to continue. You have <xliff:g id="
-number">%d</xliff:g> remaining attempts before SIM becomes permanently unusable. Contact carrier for details.</item>
- </plurals>
+ <!-- Instructions telling the user remaining times when enter SIM PIN view. [CHAR LIMIT=NONE] -->
+ <string name="kg_password_default_pin_message">{count, plural,
+ =1 {Enter SIM PIN. You have # remaining attempt before you must contact your carrier to unlock your device.}
+ other {Enter SIM PIN. You have # remaining attempts.}
+ }</string>
+
+ <!-- Instructions telling the user remaining times when enter SIM PUK view. [CHAR LIMIT=NONE] -->
+ <string name="kg_password_default_puk_message">{count, plural,
+ =1 {SIM is now disabled. Enter PUK code to continue. You have # remaining attempt before SIM becomes permanently unusable. Contact carrier for details.}
+ other {SIM is now disabled. Enter PUK code to continue. You have # remaining attempts before SIM becomes permanently unusable. Contact carrier for details.}
+ }</string>
<!-- Name of the "Default" clock face, which is the clock face that will be shown by default. [CHAR LIMIT=15]-->
<string name="clock_title_default">Default</string>
diff --git a/packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml b/packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml
new file mode 100644
index 000000000000..5fd7ee29d838
--- /dev/null
+++ b/packages/SystemUI/res/drawable/broadcast_dialog_btn_bg.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorAccentPrimary" />
+ <corners android:radius="@dimen/broadcast_dialog_btn_radius" />
+</shape>
diff --git a/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml b/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
new file mode 100644
index 000000000000..1992c7733bd3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_aqi_badge_bg.xml
@@ -0,0 +1,20 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@color/dream_overlay_aqi_unknown" />
+ <corners android:radius="@dimen/dream_aqi_badge_corner_radius" />
+</shape> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/dream_overlay_camera_off.xml b/packages/SystemUI/res/drawable/dream_overlay_camera_off.xml
new file mode 100644
index 000000000000..159655e39d24
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_camera_off.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="24dp"
+ android:viewportWidth="56"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M21.872,5.873L20.926,6.813L21.492,7.38C21.392,7.566 21.332,7.773 21.332,8V16C21.332,16.733 21.932,17.333 22.666,17.333H30.666C30.892,17.333 31.099,17.273 31.286,17.173L33.186,19.073L34.126,18.133L31.999,16L21.872,5.873ZM31.999,10.986V8C31.999,7.266 31.399,6.666 30.666,6.666H24.552L25.886,8H30.666V12.78L31.999,14.113V13.013L34.666,15.666V8.333L31.999,10.986ZM22.666,8.553V16H30.112L22.666,8.553Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml b/packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml
new file mode 100644
index 000000000000..087dde78833f
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_mic_and_camera_off.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="24dp"
+ android:viewportWidth="56"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M15.332,7.333C15.332,6.966 15.632,6.666 15.999,6.666C16.366,6.666 16.666,6.966 16.666,7.333V10.78L17.879,11.993C17.952,11.786 17.999,11.566 17.999,11.333V7.333C17.999,6.226 17.106,5.333 15.999,5.333C14.892,5.333 13.999,6.226 13.999,7.333V8.113L15.332,9.446V7.333ZM9.872,5.873L8.926,6.813L16.692,14.58C16.472,14.633 16.239,14.666 15.999,14.666C14.159,14.666 12.666,13.173 12.666,11.333H11.332C11.332,13.686 13.072,15.62 15.332,15.946V18H16.666V15.946C17.046,15.893 17.412,15.786 17.759,15.64L21.186,19.066L22.126,18.126L9.872,5.873ZM19.332,11.333H20.666C20.666,12.313 20.359,13.213 19.846,13.96L18.872,12.986C19.159,12.5 19.332,11.94 19.332,11.333Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+ <path
+ android:pathData="M33.872,5.873L32.926,6.813L33.492,7.38C33.392,7.566 33.332,7.773 33.332,8V16C33.332,16.733 33.932,17.333 34.666,17.333H42.666C42.892,17.333 43.099,17.273 43.286,17.173L45.186,19.073L46.126,18.133L43.999,16L33.872,5.873ZM43.999,10.986V8C43.999,7.266 43.399,6.666 42.666,6.666H36.552L37.886,8H42.666V12.78L43.999,14.113V13.013L46.666,15.666V8.333L43.999,10.986ZM34.666,8.553V16H42.112L34.666,8.553Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/dream_overlay_mic_off.xml b/packages/SystemUI/res/drawable/dream_overlay_mic_off.xml
new file mode 100644
index 000000000000..693250d39f95
--- /dev/null
+++ b/packages/SystemUI/res/drawable/dream_overlay_mic_off.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="56dp"
+ android:height="24dp"
+ android:viewportWidth="56"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M12,0L44,0A12,12 0,0 1,56 12L56,12A12,12 0,0 1,44 24L12,24A12,12 0,0 1,0 12L0,12A12,12 0,0 1,12 0z"
+ android:fillColor="#5F6368"/>
+ <path
+ android:pathData="M27.807,7.133C27.807,6.767 28.107,6.467 28.473,6.467C28.84,6.467 29.14,6.767 29.14,7.133V10.58L30.353,11.793C30.427,11.587 30.473,11.367 30.473,11.133V7.133C30.473,6.027 29.58,5.133 28.473,5.133C27.367,5.133 26.473,6.027 26.473,7.133V7.913L27.807,9.247V7.133ZM22.347,5.673L21.4,6.613L29.167,14.38C28.947,14.433 28.713,14.467 28.473,14.467C26.633,14.467 25.14,12.973 25.14,11.133H23.807C23.807,13.487 25.547,15.42 27.807,15.747V17.8H29.14V15.747C29.52,15.693 29.887,15.587 30.233,15.44L33.66,18.867L34.6,17.927L22.347,5.673ZM31.807,11.133H33.14C33.14,12.113 32.833,13.013 32.32,13.76L31.347,12.787C31.633,12.3 31.807,11.74 31.807,11.133Z"
+ android:fillColor="#ffffff"
+ android:fillType="evenOdd"/>
+</vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_pause_container.xml b/packages/SystemUI/res/drawable/ic_media_pause_container.xml
index b92e63575b95..ea9eb8cd5475 100644
--- a/packages/SystemUI/res/drawable/ic_media_pause_container.xml
+++ b/packages/SystemUI/res/drawable/ic_media_pause_container.xml
@@ -22,11 +22,6 @@
android:viewportHeight="48"
android:viewportWidth="48">
<group android:name="_R_G">
- <group android:name="_R_G_L_1_G"
- android:translateX="24"
- android:translateY="24"
- android:scaleX="0.5"
- android:scaleY="0.5"/>
<group android:name="_R_G_L_0_G"
android:translateX="24"
android:translateY="24"
@@ -46,7 +41,7 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
- android:duration="500"
+ android:duration="250"
android:startOffset="0"
android:valueFrom="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "
android:valueTo="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "
@@ -62,7 +57,7 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
- android:duration="517"
+ android:duration="267"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
@@ -70,4 +65,4 @@
</set>
</aapt:attr>
</target>
-</animated-vector> \ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_media_play_container.xml b/packages/SystemUI/res/drawable/ic_media_play_container.xml
index 2fc9fc88a4fd..4cb011a89f35 100644
--- a/packages/SystemUI/res/drawable/ic_media_play_container.xml
+++ b/packages/SystemUI/res/drawable/ic_media_play_container.xml
@@ -41,7 +41,7 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="pathData"
- android:duration="500"
+ android:duration="250"
android:startOffset="0"
android:valueFrom="M48 0.25 C48,0.25 48,0 48,0 C47.75,26 31.25,48 0,48 C0,48 0,48 0,48 C-30,48 -48,25.75 -48,-0.25 C-48,-0.25 -48,-0.25 -48,-0.25 C-47.75,-23.5 -32.25,-47.75 0.5,-48 C0.5,-48 0.5,-48 0.5,-48 C28,-47.75 47.75,-29.75 48,0.25c "
android:valueTo="M48 -16 C48,-16 48,16 48,16 C48,33.67 33.67,48 16,48 C16,48 -16,48 -16,48 C-33.67,48 -48,33.67 -48,16 C-48,16 -48,-16 -48,-16 C-48,-33.67 -33.67,-48 -16,-48 C-16,-48 16,-48 16,-48 C33.67,-48 48,-33.67 48,-16c "
@@ -57,7 +57,7 @@
<aapt:attr name="android:animation">
<set android:ordering="together">
<objectAnimator android:propertyName="translateX"
- android:duration="517"
+ android:duration="267"
android:startOffset="0"
android:valueFrom="0"
android:valueTo="1"
@@ -65,4 +65,4 @@
</set>
</aapt:attr>
</target>
-</animated-vector> \ No newline at end of file
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/ic_qs_screen_saver.xml b/packages/SystemUI/res/drawable/ic_qs_screen_saver.xml
new file mode 100644
index 000000000000..263a3d1c894c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screen_saver.xml
@@ -0,0 +1,24 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path android:fillColor="@android:color/white"
+ android:pathData="M5,15H19L14.5,9L11,13.5L8.5,10.5ZM6,21Q5.575,21 5.287,20.712Q5,20.425 5,20V19H3Q2.175,19 1.588,18.413Q1,17.825 1,17V6Q1,5.175 1.588,4.588Q2.175,4 3,4H21Q21.825,4 22.413,4.588Q23,5.175 23,6V17Q23,17.825 22.413,18.413Q21.825,19 21,19H19V20Q19,20.425 18.712,20.712Q18.425,21 18,21ZM3,17H21Q21,17 21,17Q21,17 21,17V6Q21,6 21,6Q21,6 21,6H3Q3,6 3,6Q3,6 3,6V17Q3,17 3,17Q3,17 3,17ZM3,17Q3,17 3,17Q3,17 3,17V6Q3,6 3,6Q3,6 3,6Q3,6 3,6Q3,6 3,6V17Q3,17 3,17Q3,17 3,17Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_qs_screen_saver_undocked.xml b/packages/SystemUI/res/drawable/ic_qs_screen_saver_undocked.xml
new file mode 100644
index 000000000000..1d1b4f507acf
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_qs_screen_saver_undocked.xml
@@ -0,0 +1,30 @@
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24"
+ android:tint="?android:attr/colorControlNormal">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M14.5,9l-3.5,4.51l-2.5,-3.01l-3.5,4.5l14,0z"
+ />
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M21,4H3C1.9,4 1,4.9 1,6V17C1,18.1 1.9,19 3,19H5H19H21C22.1,19 23,18.1 23,17V6C23,4.9 22.1,4 21,4ZM21,17H3V6H21V17Z"
+ />
+</vector>
diff --git a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
index 3a08a7111d9a..41123c84ded1 100644
--- a/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
+++ b/packages/SystemUI/res/drawable/keyguard_bottom_affordance_bg.xml
@@ -16,19 +16,13 @@
* limitations under the License.
*/
-->
-<ripple
+<shape
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:color="?android:attr/textColorPrimary">
- <item>
- <shape
- android:shape="rectangle">
- <solid android:color="?androidprv:attr/colorSurface"/>
- <size
- android:width="@dimen/keyguard_affordance_width"
- android:height="@dimen/keyguard_affordance_height"/>
- <corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
- </shape>
- </item>
-</ripple>
-
+ android:shape="rectangle">
+ <solid android:color="?androidprv:attr/colorSurface"/>
+ <size
+ android:width="@dimen/keyguard_affordance_width"
+ android:height="@dimen/keyguard_affordance_height"/>
+ <corners android:radius="@dimen/keyguard_affordance_fixed_radius"/>
+</shape>
diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
new file mode 100644
index 000000000000..f239a8dd4291
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_airplane_icon_off.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c M1.5 1.5 C1.5,1.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
new file mode 100644
index 000000000000..d46fafa25816
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_airplane_icon_on.xml
@@ -0,0 +1,98 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_1_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="667"
+ android:pathData="M 12.125,34.75C 12.104,30.958 12.021,15.792 12,12"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="517"
+ android:pathData="M 12,12C 12.021,8.312 12.104,-6.436999999999999 12.125,-10.125"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.3,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="683"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateX="12.125"
+ android:translateY="34.75">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M10 4 C10,4 10,2 10,2 C10,2 1.5,-3 1.5,-3 C1.5,-3 1.5,-8.5 1.5,-8.5 C1.5,-9.33 0.83,-10 0,-10 C-0.83,-10 -1.5,-9.33 -1.5,-8.5 C-1.5,-8.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5 C1.5,1.5 10,4 10,4c " />
+ </group>
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-1.5 -3.02 C-1.5,-3.02 -1.5,-8.5 -1.5,-8.5 C-1.5,-9.33 -0.83,-10 0,-10 C0.83,-10 1.5,-9.33 1.5,-8.5 C1.5,-8.5 1.5,-3 1.5,-3 C1.5,-3 10,2 10,2 C10,2 10,4 10,4 C10,4 1.51,1.49 1.51,1.49 C1.51,1.49 -1.5,-3.02 -1.5,-3.02c M1.5 1.5 C1.5,1.5 -1.5,-3 -1.5,-3 C-1.5,-3 -10,2 -10,2 C-10,2 -10,4 -10,4 C-10,4 -1.5,1.5 -1.5,1.5 C-1.5,1.5 -1.5,7 -1.5,7 C-1.5,7 -4,8.5 -4,8.5 C-4,8.5 -4,10 -4,10 C-4,10 0,9 0,9 C0,9 4,10 4,10 C4,10 4,8.5 4,8.5 C4,8.5 1.5,7 1.5,7 C1.5,7 1.5,1.5 1.5,1.5c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_battery_saver_icon_off.xml b/packages/SystemUI/res/drawable/qs_battery_saver_icon_off.xml
new file mode 100644
index 000000000000..7b9f23d133ea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_battery_saver_icon_off.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:pathData=" M3.67 -8 C3.67,-8 2,-8 2,-8 C2,-8 2,-10 2,-10 C2,-10 -2,-10 -2,-10 C-2,-10 -2,-8 -2,-8 C-2,-8 -3.67,-8 -3.67,-8 C-4.4,-8 -5,-7.4 -5,-6.67 C-5,-6.67 -5,8.66 -5,8.66 C-5,9.4 -4.4,10 -3.67,10 C-3.67,10 3.66,10 3.66,10 C4.4,10 5,9.4 5,8.67 C5,8.67 5,-6.67 5,-6.67 C5,-7.4 4.4,-8 3.67,-8c "
+ android:strokeAlpha="1"
+ android:strokeColor="#ffffff"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2" />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M2 -10 C2,-10 2,-7 2,-7 C2,-7 -2,-7 -2,-7 C-2,-7 -2,-10 -2,-10 C-2,-10 2,-10 2,-10c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_battery_saver_icon_on.xml b/packages/SystemUI/res/drawable/qs_battery_saver_icon_on.xml
new file mode 100644
index 000000000000..5e4af398e017
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_battery_saver_icon_on.xml
@@ -0,0 +1,636 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_4_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="317"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="317"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="417"
+ android:pathData="M 0,0.875C 0,-0.9690000000000001 0,-8.344000000000001 0,-10.188"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c1,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_4_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="112"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c1,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="250"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="217"
+ android:propertyName="fillAlpha"
+ android:startOffset="333"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="550"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:pathData="M -0.875,9C -0.854,6.156000000000001 -0.896,11.844 -0.875,9"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="550"
+ android:pathData="M -0.875,9C -0.854,6.156000000000001 -0.771,-5.218 -0.75,-8.062"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="83">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_3_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="67"
+ android:valueTo="67"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="550"
+ android:propertyName="rotation"
+ android:startOffset="83"
+ android:valueFrom="67"
+ android:valueTo="192"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="417"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="417"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="267"
+ android:propertyName="fillAlpha"
+ android:startOffset="500"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="767"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:pathData="M 2.125,9.375C 2.146,6.468999999999999 2.104,12.281 2.125,9.375"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:pathData="M 2.125,9.375C 2.146,6.468999999999999 2.229,-5.155999999999999 2.25,-8.062"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="250">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_2_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="28"
+ android:valueTo="28"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="250"
+ android:valueFrom="28"
+ android:valueTo="153"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="533"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="533"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="fillAlpha"
+ android:startOffset="617"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="867"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="350"
+ android:pathData="M -2,10C -2,6.99 -2,13.01 -2,10"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:pathData="M -2,10C -2,6.99 -2,-5.052 -2,-8.062"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="350">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_1_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="350"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="28"
+ android:valueTo="28"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="600"
+ android:propertyName="rotation"
+ android:startOffset="350"
+ android:valueFrom="28"
+ android:valueTo="153"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="883"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="fillAlpha"
+ android:startOffset="883"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="667"
+ android:pathData="M 0,11.5C 0,9.729 0,13.271 0,11.5"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="0">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:pathData="M 0,11.5C 0,9.729 0,2.646 0,0.875"
+ android:propertyName="translateXY"
+ android:propertyXName="translateX"
+ android:propertyYName="translateY"
+ android:startOffset="667">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_T_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-48"
+ android:valueTo="-48"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="667"
+ android:propertyName="rotation"
+ android:startOffset="667"
+ android:valueFrom="-48"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.6,0 0,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="1350"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_5_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_5_G_D_0_P_0"
+ android:pathData=" M3.67 -8 C3.67,-8 2,-8 2,-8 C2,-8 2,-10 2,-10 C2,-10 -2,-10 -2,-10 C-2,-10 -2,-8 -2,-8 C-2,-8 -3.67,-8 -3.67,-8 C-4.4,-8 -5,-7.4 -5,-6.67 C-5,-6.67 -5,8.66 -5,8.66 C-5,9.4 -4.4,10 -3.67,10 C-3.67,10 3.66,10 3.66,10 C4.4,10 5,9.4 5,8.67 C5,8.67 5,-6.67 5,-6.67 C5,-7.4 4.4,-8 3.67,-8c "
+ android:strokeAlpha="1"
+ android:strokeColor="#ffffff"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeWidth="2" />
+ <path
+ android:name="_R_G_L_5_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M2 -10 C2,-10 2,-7 2,-7 C2,-7 -2,-7 -2,-7 C-2,-7 -2,-10 -2,-10 C-2,-10 2,-10 2,-10c " />
+ </group>
+ <group
+ android:name="_R_G_L_4_G_N_6_T_0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_4_G_T_1"
+ android:rotation="0"
+ android:translateX="0"
+ android:translateY="0.875">
+ <group
+ android:name="_R_G_L_4_G"
+ android:translateY="-0.875">
+ <path
+ android:name="_R_G_L_4_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_3_G_N_6_T_0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_3_G_T_1"
+ android:rotation="67"
+ android:scaleX="0.7"
+ android:scaleY="0.7"
+ android:translateX="-0.875"
+ android:translateY="9">
+ <group
+ android:name="_R_G_L_3_G"
+ android:translateY="-0.875">
+ <path
+ android:name="_R_G_L_3_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_2_G_N_6_T_0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_2_G_T_1"
+ android:rotation="28"
+ android:scaleX="0.85"
+ android:scaleY="0.85"
+ android:translateX="2.125"
+ android:translateY="9.375">
+ <group
+ android:name="_R_G_L_2_G"
+ android:translateY="-0.875">
+ <path
+ android:name="_R_G_L_2_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_1_G_N_6_T_0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_1_G_T_1"
+ android:rotation="28"
+ android:scaleX="0.55"
+ android:scaleY="0.55"
+ android:translateX="-2"
+ android:translateY="10">
+ <group
+ android:name="_R_G_L_1_G"
+ android:translateY="-0.875">
+ <path
+ android:name="_R_G_L_1_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+ </group>
+ </group>
+ </group>
+ <group
+ android:name="_R_G_L_0_G_N_6_T_0"
+ android:translateX="12"
+ android:translateY="12">
+ <group
+ android:name="_R_G_L_0_G_T_1"
+ android:rotation="-48"
+ android:translateX="0"
+ android:translateY="11.5">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateY="-0.875">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3 2 C3,2 1,2 1,2 C1,2 1,4 1,4 C1,4 -1,4 -1,4 C-1,4 -1,2 -1,2 C-1,2 -3,2 -3,2 C-3,2 -3,0 -3,0 C-3,0 -1,0 -1,0 C-1,0 -1,-2 -1,-2 C-1,-2 1,-2 1,-2 C1,-2 1,0 1,0 C1,0 3,0 3,0 C3,0 3,2 3,2c " />
+ </group>
+ </group>
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_camera_access_icon_off.xml b/packages/SystemUI/res/drawable/qs_camera_access_icon_off.xml
new file mode 100644
index 000000000000..8f9ecbe482e3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_camera_access_icon_off.xml
@@ -0,0 +1,110 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5 1.3 C5,1.3 8.35,4.63 8.35,4.63 C8.35,4.63 8.35,-4.58 8.35,-4.58 C8.35,-4.58 5,-1.25 5,-1.25 C5,-1.25 5,-5 5,-5 C5,-5.92 4.25,-6.67 3.33,-6.67 C3.33,-6.67 -6.84,-6.68 -6.84,-6.68 C-6.54,-6.37 -6.07,-5.56 -5.49,-5 C-5.49,-5 3.33,-5 3.33,-5 C3.33,-5 3.33,-1.91 3.33,-1.91 C3.33,-1.91 3.32,3.73 3.32,3.73 C3.32,3.73 5,5.23 5,5.23 C5,5.23 5,2.14 5,2.14 C5,2.14 5,1.3 5,1.3c M3.34 5 C3.34,5 -6.67,5 -6.67,5 C-6.67,5 -6.67,-5.01 -6.67,-5.01 C-6.67,-5.01 -5.5,-5 -5.5,-5 C-5.98,-5.57 -6.46,-6.23 -6.83,-6.68 C-7.84,-6.64 -8.34,-5.77 -8.34,-5.01 C-8.34,-5.01 -8.34,5 -8.34,5 C-8.34,5.91 -7.58,6.66 -6.67,6.66 C-6.67,6.66 3.34,6.66 3.34,6.66 C4.13,6.66 4.88,6.1 5,5.23 C4.73,4.96 3.73,4.13 3.34,3.73 C3.34,3.73 3.34,5 3.34,5c "
+ android:valueTo="M5 1.3 C5,1.3 8.35,4.63 8.35,4.63 C8.35,4.63 8.35,-4.58 8.35,-4.58 C8.35,-4.58 5,-1.25 5,-1.25 C5,-1.25 5,-5 5,-5 C5,-5.92 4.25,-6.67 3.33,-6.67 C3.33,-6.67 -4.28,-6.67 -4.28,-6.67 C-3.98,-6.36 -3.13,-5.56 -2.55,-4.99 C-2.55,-4.99 3.33,-5 3.33,-5 C3.33,-5 3.33,-1.91 3.33,-1.91 C3.33,-1.91 3.33,0.9 3.33,0.9 C3.33,0.9 5.01,2.59 5.01,2.59 C5.01,2.59 5,2.14 5,2.14 C5,2.14 5,1.3 5,1.3c M3.34 5 C3.34,5 -6.67,5 -6.67,5 C-6.67,5 -6.67,-5.01 -6.67,-5.01 C-6.67,-5.01 -5.5,-5 -5.5,-5 C-6.02,-5.5 -6.69,-6.17 -7.12,-6.61 C-7.82,-6.41 -8.34,-5.77 -8.34,-5.01 C-8.34,-5.01 -8.34,5 -8.34,5 C-8.34,5.91 -7.58,6.66 -6.67,6.66 C-6.67,6.66 3.34,6.66 3.34,6.66 C4.13,6.66 4.79,6.11 4.97,5.37 C4.7,5.1 3.73,4.13 3.34,3.73 C3.34,3.73 3.34,5 3.34,5c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-10.96 -7.79 C-10.96,-7.79 -10.96,-7.79 -10.96,-7.79 C-10.96,-7.79 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 -10.96,-7.79 -10.96,-7.79c "
+ android:valueTo="M7.44 10.61 C7.44,10.61 -10.96,-7.79 -10.96,-7.79 C-10.96,-7.79 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 8.84,9.21 8.84,9.21 C8.84,9.21 7.44,10.61 7.44,10.61c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M5 1.3 C5,1.3 8.35,4.63 8.35,4.63 C8.35,4.63 8.35,-4.58 8.35,-4.58 C8.35,-4.58 5,-1.25 5,-1.25 C5,-1.25 5,-5 5,-5 C5,-5.92 4.25,-6.67 3.33,-6.67 C3.33,-6.67 -6.84,-6.68 -6.84,-6.68 C-6.54,-6.37 -6.07,-5.56 -5.49,-5 C-5.49,-5 3.33,-5 3.33,-5 C3.33,-5 3.33,-1.91 3.33,-1.91 C3.33,-1.91 3.32,3.73 3.32,3.73 C3.32,3.73 5,5.23 5,5.23 C5,5.23 5,2.14 5,2.14 C5,2.14 5,1.3 5,1.3c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-10.96 -7.79 C-10.96,-7.79 -10.96,-7.79 -10.96,-7.79 C-10.96,-7.79 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 -10.96,-7.79 -10.96,-7.79c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_camera_access_icon_on.xml b/packages/SystemUI/res/drawable/qs_camera_access_icon_on.xml
new file mode 100644
index 000000000000..beb8b72d29ad
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_camera_access_icon_on.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M7.44 10.61 C7.44,10.61 -10.96,-7.79 -10.96,-7.79 C-10.96,-7.79 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 8.84,9.21 8.84,9.21 C8.84,9.21 7.44,10.61 7.44,10.61c "
+ android:valueTo="M7.44 10.62 C7.44,10.62 7.45,10.62 7.45,10.62 C7.45,10.62 8.85,9.22 8.85,9.22 C8.85,9.22 8.84,9.22 8.84,9.22 C8.84,9.22 7.44,10.62 7.44,10.62c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.55,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="333"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5 1.3 C5,1.3 8.35,4.63 8.35,4.63 C8.35,4.63 8.35,-4.58 8.35,-4.58 C8.35,-4.58 5,-1.25 5,-1.25 C5,-1.25 5,-5 5,-5 C5,-5.92 4.25,-6.67 3.33,-6.67 C3.33,-6.67 -4.28,-6.67 -4.28,-6.67 C-3.98,-6.36 -3.13,-5.56 -2.55,-4.99 C-2.55,-4.99 3.33,-5 3.33,-5 C3.33,-5 3.33,-1.91 3.33,-1.91 C3.33,-1.91 3.33,0.9 3.33,0.9 C3.33,0.9 5.01,2.59 5.01,2.59 C5.01,2.59 5,2.14 5,2.14 C5,2.14 5,1.3 5,1.3c M3.34 5 C3.34,5 -6.67,5 -6.67,5 C-6.67,5 -6.67,-5.01 -6.67,-5.01 C-6.67,-5.01 -5.5,-5 -5.5,-5 C-6.02,-5.5 -6.69,-6.17 -7.12,-6.61 C-7.82,-6.41 -8.34,-5.77 -8.34,-5.01 C-8.34,-5.01 -8.34,5 -8.34,5 C-8.34,5.91 -7.58,6.66 -6.67,6.66 C-6.67,6.66 3.34,6.66 3.34,6.66 C4.13,6.66 4.79,6.11 4.97,5.37 C4.7,5.1 3.73,4.13 3.34,3.73 C3.34,3.73 3.34,5 3.34,5c "
+ android:valueTo="M5 1.3 C5,1.3 8.35,4.63 8.35,4.63 C8.35,4.63 8.35,-4.58 8.35,-4.58 C8.35,-4.58 5,-1.25 5,-1.25 C5,-1.25 5,-5 5,-5 C5,-5.92 4.25,-6.67 3.33,-6.67 C3.33,-6.67 -6.84,-6.68 -6.84,-6.68 C-6.54,-6.37 -6.07,-5.56 -5.49,-5 C-5.49,-5 3.33,-5 3.33,-5 C3.33,-5 3.33,-1.91 3.33,-1.91 C3.33,-1.91 3.32,3.73 3.32,3.73 C3.32,3.73 5,5.23 5,5.23 C5,5.23 5,2.14 5,2.14 C5,2.14 5,1.3 5,1.3c M3.34 5 C3.34,5 -6.67,5 -6.67,5 C-6.67,5 -6.67,-5.01 -6.67,-5.01 C-6.67,-5.01 -5.5,-5 -5.5,-5 C-5.98,-5.57 -6.46,-6.23 -6.83,-6.68 C-7.84,-6.64 -8.34,-5.77 -8.34,-5.01 C-8.34,-5.01 -8.34,5 -8.34,5 C-8.34,5.91 -7.58,6.66 -6.67,6.66 C-6.67,6.66 3.34,6.66 3.34,6.66 C4.13,6.66 4.88,6.1 5,5.23 C4.73,4.96 3.73,4.13 3.34,3.73 C3.34,3.73 3.34,5 3.34,5c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="350"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M7.44 10.61 C7.44,10.61 -10.96,-7.79 -10.96,-7.79 C-10.96,-7.79 -9.56,-9.19 -9.56,-9.19 C-9.56,-9.19 8.84,9.21 8.84,9.21 C8.84,9.21 7.44,10.61 7.44,10.61c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M5 1.3 C5,1.3 8.35,4.63 8.35,4.63 C8.35,4.63 8.35,-4.58 8.35,-4.58 C8.35,-4.58 5,-1.25 5,-1.25 C5,-1.25 5,-5 5,-5 C5,-5.92 4.25,-6.67 3.33,-6.67 C3.33,-6.67 -4.28,-6.67 -4.28,-6.67 C-3.98,-6.36 -3.13,-5.56 -2.55,-4.99 C-2.55,-4.99 3.33,-5 3.33,-5 C3.33,-5 3.33,-1.91 3.33,-1.91 C3.33,-1.91 3.33,0.9 3.33,0.9 C3.33,0.9 5.01,2.59 5.01,2.59 C5.01,2.59 5,2.14 5,2.14 C5,2.14 5,1.3 5,1.3c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_dnd_icon_off.xml b/packages/SystemUI/res/drawable/qs_dnd_icon_off.xml
new file mode 100644
index 000000000000..e42381a4d5a3
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dnd_icon_off.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="183"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="-225"
+ android:scaleX="0.85"
+ android:scaleY="0.85"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-2.83 -4.24 C-2.83,-4.24 4.24,2.83 4.24,2.83 C4.24,2.83 2.83,4.24 2.83,4.24 C2.83,4.24 -4.24,-2.83 -4.24,-2.83 C-4.24,-2.83 -2.83,-4.24 -2.83,-4.24c M7.07 7.09 C4.65,9.51 1.78,10.02 0,10.02 C-5.52,10.02 -10,5.54 -10,0.02 C-10,-1.76 -9.49,-4.52 -7.07,-7.07 C-7.07,-7.07 -5.66,-5.67 -5.66,-5.67 C-7.73,-3.44 -8,-1.2 -8,0.02 C-8,4.43 -4.41,8.02 0,8.02 C1.22,8.02 3.42,7.71 5.67,5.64 C5.67,5.64 7.07,7.09 7.07,7.09c M-7.06 -7.1 C-4.62,-9.54 -1.81,-9.94 -0.03,-9.94 C5.49,-9.94 9.97,-5.46 9.97,0.06 C9.97,1.84 9.49,4.63 7.07,7.05 C7.07,7.05 5.66,5.64 5.66,5.64 C7.67,3.51 7.97,1.28 7.97,0.06 C7.97,-4.35 4.38,-7.94 -0.03,-7.94 C-1.25,-7.94 -3.43,-7.88 -5.65,-5.66 C-5.65,-5.66 -7.06,-7.1 -7.06,-7.1c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_dnd_icon_on.xml b/packages/SystemUI/res/drawable/qs_dnd_icon_on.xml
new file mode 100644
index 000000000000..a63cb238f5a0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_dnd_icon_on.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="500"
+ android:propertyName="rotation"
+ android:startOffset="0"
+ android:valueFrom="-225"
+ android:valueTo="-45"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="517"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:rotation="-225"
+ android:scaleX="0.85"
+ android:scaleY="0.85"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-2.83 -4.24 C-2.83,-4.24 4.24,2.83 4.24,2.83 C4.24,2.83 2.83,4.24 2.83,4.24 C2.83,4.24 -4.24,-2.83 -4.24,-2.83 C-4.24,-2.83 -2.83,-4.24 -2.83,-4.24c M7.07 7.09 C4.65,9.51 1.78,10.02 0,10.02 C-5.52,10.02 -10,5.54 -10,0.02 C-10,-1.76 -9.49,-4.52 -7.07,-7.07 C-7.07,-7.07 -5.66,-5.67 -5.66,-5.67 C-7.73,-3.44 -8,-1.2 -8,0.02 C-8,4.43 -4.41,8.02 0,8.02 C1.22,8.02 3.42,7.71 5.67,5.64 C5.67,5.64 7.07,7.09 7.07,7.09c M-7.06 -7.1 C-4.62,-9.54 -1.81,-9.94 -0.03,-9.94 C5.49,-9.94 9.97,-5.46 9.97,0.06 C9.97,1.84 9.49,4.63 7.07,7.05 C7.07,7.05 5.66,5.64 5.66,5.64 C7.67,3.51 7.97,1.28 7.97,0.06 C7.97,-4.35 4.38,-7.94 -0.03,-7.94 C-1.25,-7.94 -3.43,-7.88 -5.65,-5.66 C-5.65,-5.66 -7.06,-7.1 -7.06,-7.1c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_location_icon_off.xml b/packages/SystemUI/res/drawable/qs_location_icon_off.xml
new file mode 100644
index 000000000000..97eb91cd33b0
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_location_icon_off.xml
@@ -0,0 +1,166 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="175"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M3.98 4.01 C3.98,4.01 2.56,2.54 2.56,2.54 C3.34,1.49 4.41,0.01 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -4.01,-6.61 -4.61,-4.61 C-4.61,-4.61 -6.24,-6.22 -6.24,-6.22 C-5.27,-8.11 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.86,-0.99 6.49,0.02 C6.12,1.04 4.57,3.23 3.98,4.01c "
+ android:valueTo="M4.62 3.11 C4.62,3.11 3.2,1.65 3.2,1.65 C3.77,0.85 4.43,-0.12 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.55,-6.78 -4.16,-5.65 C-4.16,-5.65 -5.67,-7.13 -5.67,-7.13 C-4.81,-8.35 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.85,-0.89 6.49,0.02 C6.13,0.94 5.16,2.35 4.62,3.11c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="75"
+ android:propertyName="pathData"
+ android:startOffset="175"
+ android:valueFrom="M4.62 3.11 C4.62,3.11 3.2,1.65 3.2,1.65 C3.77,0.85 4.43,-0.12 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.55,-6.78 -4.16,-5.65 C-4.16,-5.65 -5.67,-7.13 -5.67,-7.13 C-4.81,-8.35 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.85,-0.89 6.49,0.02 C6.13,0.94 5.16,2.35 4.62,3.11c "
+ android:valueTo="M5.18 2.33 C5.18,2.33 3.75,0.88 3.75,0.88 C4.13,0.29 4.45,-0.23 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.16,-6.94 -3.61,-6.46 C-3.61,-6.46 -5.07,-7.86 -5.07,-7.86 C-4.42,-8.54 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.84,-0.81 6.49,0.02 C6.14,0.86 5.68,1.58 5.18,2.33c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="125"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-0.96 -0.67 C0.37,-0.16 1.79,-0.77 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-2.08,-4.88 -2.84,-3.58 -2.35,-2.11 C-2.16,-1.55 -1.58,-0.91 -0.96,-0.67c "
+ android:valueTo="M-0.29 -0.52 C0.81,-0.39 1.86,-0.95 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.93,-4.94 -2.6,-3.89 -2.49,-2.82 C-2.18,-2.46 -0.67,-0.84 -0.29,-0.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="pathData"
+ android:startOffset="125"
+ android:valueFrom="M-0.29 -0.52 C0.81,-0.39 1.86,-0.95 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.93,-4.94 -2.6,-3.89 -2.49,-2.82 C-2.18,-2.46 -0.67,-0.84 -0.29,-0.52c "
+ android:valueTo="M0.87 -0.65 C1.56,-0.93 2.02,-1.3 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.63,-5.06 -2.14,-4.44 -2.33,-3.91 C-2.11,-3.68 0.66,-0.85 0.87,-0.65c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.667,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="75"
+ android:propertyName="pathData"
+ android:startOffset="175"
+ android:valueFrom="M0.87 -0.65 C1.56,-0.93 2.02,-1.3 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.63,-5.06 -2.14,-4.44 -2.33,-3.91 C-2.11,-3.68 0.66,-0.85 0.87,-0.65c "
+ android:valueTo="M1.8 -1.24 C2.03,-1.47 2.21,-1.76 2.33,-2.05 C2.44,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.25,-5.21 -1.57,-5.02 -1.77,-4.78 C-1.77,-4.78 1.8,-1.24 1.8,-1.24c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.333,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="125"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-9.89 -7.81 C-9.89,-7.81 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 -8.49,-9.21 -8.49,-9.21 C-8.49,-9.21 -9.89,-7.81 -9.89,-7.81c "
+ android:valueTo="M-9.89 -7.81 C-9.89,-7.81 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 -8.49,-9.21 -8.49,-9.21 C-8.49,-9.21 -9.89,-7.81 -9.89,-7.81c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="125"
+ android:propertyName="pathData"
+ android:startOffset="125"
+ android:valueFrom="M-9.89 -7.81 C-9.89,-7.81 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 -8.49,-9.21 -8.49,-9.21 C-8.49,-9.21 -9.89,-7.81 -9.89,-7.81c "
+ android:valueTo="M8.5 10.6 C8.5,10.6 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="267"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-5.05 -2.1 C-5.05,-1.06 -4.64,-0.4 -4.23,0.3 C-3.81,1 -3.28,1.75 -2.65,2.55 C-2.22,3.1 -1.78,3.71 -1.35,4.39 C-0.92,5.06 -0.39,5.6 0.01,6.43 C0.21,5.95 0.61,5.27 0.98,4.72 C1.44,4.01 2.13,3.09 2.55,2.54 C2.94,2.95 3.54,3.56 3.98,4 C3.47,4.64 2.65,5.82 2.1,6.77 C1.66,7.55 1.28,8.44 0.98,9.26 C0.88,9.55 0.77,9.67 0.59,9.81 C0.44,9.93 0.28,9.99 0,9.99 C-0.28,9.99 -0.48,9.89 -0.68,9.66 C-0.88,9.44 -1.03,9.18 -1.15,8.9 C-1.45,8.07 -1.68,7.43 -2.17,6.65 C-2.67,5.88 -3.27,4.87 -4.15,3.79 C-4.88,2.75 -5.67,1.6 -6.21,0.62 C-6.74,-0.36 -7,-1.76 -7,-3.01 C-7,-4.08 -6.77,-5.28 -6.23,-6.23 C-5.75,-5.73 -4.92,-4.92 -4.61,-4.61 C-4.87,-3.95 -5.06,-2.93 -5.05,-2.1c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3.98 4.01 C3.98,4.01 2.56,2.54 2.56,2.54 C3.34,1.49 4.41,0.01 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -4.01,-6.61 -4.61,-4.61 C-4.61,-4.61 -6.24,-6.22 -6.24,-6.22 C-5.27,-8.11 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.86,-0.99 6.49,0.02 C6.12,1.04 4.57,3.23 3.98,4.01c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_1"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-0.96 -0.67 C0.37,-0.16 1.79,-0.77 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-2.08,-4.88 -2.84,-3.58 -2.35,-2.11 C-2.16,-1.55 -1.58,-0.91 -0.96,-0.67c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M-9.89 -7.81 C-9.89,-7.81 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 -8.49,-9.21 -8.49,-9.21 C-8.49,-9.21 -9.89,-7.81 -9.89,-7.81c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_location_icon_on.xml b/packages/SystemUI/res/drawable/qs_location_icon_on.xml
new file mode 100644
index 000000000000..c56b6508eb49
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_location_icon_on.xml
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5.18 2.33 C5.18,2.33 3.75,0.88 3.75,0.88 C4.13,0.29 4.45,-0.23 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.16,-6.94 -3.61,-6.46 C-3.61,-6.46 -5.07,-7.86 -5.07,-7.86 C-4.42,-8.54 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.84,-0.81 6.49,0.02 C6.14,0.86 5.68,1.58 5.18,2.33c "
+ android:valueTo="M4.62 3.11 C4.62,3.11 3.2,1.65 3.2,1.65 C3.77,0.85 4.43,-0.12 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.55,-6.78 -4.16,-5.65 C-4.16,-5.65 -5.67,-7.13 -5.67,-7.13 C-4.81,-8.35 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.85,-0.89 6.49,0.02 C6.13,0.94 5.16,2.35 4.62,3.11c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="233"
+ android:propertyName="pathData"
+ android:startOffset="100"
+ android:valueFrom="M4.62 3.11 C4.62,3.11 3.2,1.65 3.2,1.65 C3.77,0.85 4.43,-0.12 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.55,-6.78 -4.16,-5.65 C-4.16,-5.65 -5.67,-7.13 -5.67,-7.13 C-4.81,-8.35 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.85,-0.89 6.49,0.02 C6.13,0.94 5.16,2.35 4.62,3.11c "
+ android:valueTo="M3.98 4.01 C3.98,4.01 2.56,2.54 2.56,2.54 C3.34,1.49 4.41,0.01 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -4.01,-6.61 -4.61,-4.61 C-4.61,-4.61 -6.24,-6.22 -6.24,-6.22 C-5.27,-8.11 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.86,-0.99 6.49,0.02 C6.12,1.04 4.57,3.23 3.98,4.01c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_1">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M1.8 -1.24 C2.03,-1.47 2.21,-1.76 2.33,-2.05 C2.44,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.25,-5.21 -1.57,-5.02 -1.77,-4.78 C-1.77,-4.78 1.8,-1.24 1.8,-1.24c "
+ android:valueTo="M0.87 -0.65 C1.56,-0.93 2.02,-1.3 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.63,-5.06 -2.14,-4.44 -2.33,-3.91 C-2.11,-3.68 0.66,-0.85 0.87,-0.65c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="pathData"
+ android:startOffset="100"
+ android:valueFrom="M0.87 -0.65 C1.56,-0.93 2.02,-1.3 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.63,-5.06 -2.14,-4.44 -2.33,-3.91 C-2.11,-3.68 0.66,-0.85 0.87,-0.65c "
+ android:valueTo="M-0.29 -0.52 C0.81,-0.39 1.86,-0.95 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.93,-4.94 -2.6,-3.89 -2.49,-2.82 C-2.18,-2.46 -0.67,-0.84 -0.29,-0.52c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="167"
+ android:valueFrom="M-0.29 -0.52 C0.81,-0.39 1.86,-0.95 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.93,-4.94 -2.6,-3.89 -2.49,-2.82 C-2.18,-2.46 -0.67,-0.84 -0.29,-0.52c "
+ android:valueTo="M-0.96 -0.67 C0.37,-0.16 1.79,-0.77 2.33,-2.05 C2.45,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-2.08,-4.88 -2.84,-3.58 -2.35,-2.11 C-2.16,-1.55 -1.58,-0.91 -0.96,-0.67c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M8.5 10.6 C8.5,10.6 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c "
+ android:valueTo="M8.5 10.6 C8.5,10.6 8.51,10.6 8.51,10.6 C8.51,10.6 9.91,9.2 9.91,9.2 C9.91,9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="350"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-5.05 -2.1 C-5.05,-1.06 -4.64,-0.4 -4.23,0.3 C-3.81,1 -3.28,1.75 -2.65,2.55 C-2.22,3.1 -1.78,3.71 -1.35,4.39 C-0.92,5.06 -0.39,5.6 0.01,6.43 C0.21,5.95 0.61,5.27 0.98,4.72 C1.44,4.01 2.13,3.09 2.55,2.54 C2.94,2.95 3.54,3.56 3.98,4 C3.47,4.64 2.65,5.82 2.1,6.77 C1.66,7.55 1.28,8.44 0.98,9.26 C0.88,9.55 0.77,9.67 0.59,9.81 C0.44,9.93 0.28,9.99 0,9.99 C-0.28,9.99 -0.48,9.89 -0.68,9.66 C-0.88,9.44 -1.03,9.18 -1.15,8.9 C-1.45,8.07 -1.68,7.43 -2.17,6.65 C-2.67,5.88 -3.27,4.87 -4.15,3.79 C-4.88,2.75 -5.67,1.6 -6.21,0.62 C-6.74,-0.36 -7,-1.76 -7,-3.01 C-7,-4.08 -6.77,-5.28 -6.23,-6.23 C-5.75,-5.73 -4.92,-4.92 -4.61,-4.61 C-4.87,-3.95 -5.06,-2.93 -5.05,-2.1c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M5.18 2.33 C5.18,2.33 3.75,0.88 3.75,0.88 C4.13,0.29 4.45,-0.23 4.68,-0.84 C4.9,-1.45 5,-2.2 5,-3 C5,-4.38 4.51,-5.56 3.54,-6.54 C2.56,-7.51 1.38,-8 0,-8 C-0.7,-8 -1.36,-7.89 -1.99,-7.61 C-2.61,-7.34 -3.16,-6.94 -3.61,-6.46 C-3.61,-6.46 -5.07,-7.86 -5.07,-7.86 C-4.42,-8.54 -3.65,-9.09 -2.79,-9.46 C-1.94,-9.84 -1,-10 0,-10 C1.93,-10 3.6,-9.32 4.97,-7.96 C6.33,-6.59 7,-4.93 7,-3 C7,-1.85 6.84,-0.81 6.49,0.02 C6.14,0.86 5.68,1.58 5.18,2.33c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_1"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M1.8 -1.24 C2.03,-1.47 2.21,-1.76 2.33,-2.05 C2.44,-2.34 2.5,-2.67 2.5,-3.02 C2.5,-3.72 2.24,-4.3 1.76,-4.78 C1.27,-5.27 0.7,-5.5 0,-5.5 C-0.33,-5.5 -0.65,-5.44 -0.95,-5.32 C-1.25,-5.21 -1.57,-5.02 -1.77,-4.78 C-1.77,-4.78 1.8,-1.24 1.8,-1.24c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M8.5 10.6 C8.5,10.6 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_mic_access_off.xml b/packages/SystemUI/res/drawable/qs_mic_access_off.xml
new file mode 100644
index 000000000000..63a94443c255
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_mic_access_off.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5.05 4.43 C3.98,5.25 3.08,5.53 2.53,5.73 C2.26,5.81 1.98,5.87 1.7,5.9 C1.7,5.9 1.7,9 1.7,9 C1.7,9 -0.3,9 -0.3,9 C-0.3,9 -0.3,5.9 -0.3,5.9 C-2.02,5.65 -3.44,4.88 -4.59,3.59 C-5.73,2.3 -6.3,0.77 -6.3,-1 C-6.3,-1 -4.3,-1 -4.3,-1 C-4.3,0.38 -3.81,1.56 -2.84,2.54 C-1.86,3.51 -0.68,4 0.7,4 C0.88,4 1.06,3.99 1.23,3.96 C1.39,3.94 2.58,3.77 3.61,2.99 C3.61,2.99 5.05,4.43 5.05,4.43c "
+ android:valueTo="M5.02 4.5 C3.59,5.47 2.79,5.64 2.53,5.73 C2.26,5.81 1.98,5.87 1.7,5.9 C1.7,5.9 1.7,9 1.7,9 C1.7,9 -0.3,9 -0.3,9 C-0.3,9 -0.3,5.9 -0.3,5.9 C-2.02,5.65 -3.44,4.88 -4.59,3.59 C-5.73,2.3 -6.3,0.77 -6.3,-1 C-6.3,-1 -4.3,-1 -4.3,-1 C-4.3,0.38 -3.81,1.56 -2.84,2.54 C-1.86,3.51 -0.68,4 0.7,4 C0.88,4 1.06,3.99 1.23,3.96 C1.39,3.94 2.53,3.83 3.58,3.07 C3.58,3.07 5.02,4.5 5.02,4.5c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="44"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M2.31 1.86 C2.31,1.86 1.01,0.55 0.75,0.31 C1.7,0.27 1.7,-0.7 1.7,-0.7 C1.7,-0.7 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-0.66 -0.29,-0.66 C-0.29,-0.66 -2.31,-2.67 -2.31,-2.67 C-2.31,-2.67 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.7,-0.33 3.64,-0.03 C3.51,0.52 3.23,1.19 2.31,1.86c "
+ android:valueTo="M2.86 1.48 C2.86,1.48 1.58,0.21 1.39,0.03 C1.77,-0.26 1.7,-1.03 1.7,-1.03 C1.7,-1.03 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-1.51 -0.29,-1.51 C-0.29,-1.51 -2.31,-3.5 -2.31,-3.5 C-2.31,-3.5 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.44 3.65,-0.17 C3.61,0.19 3.36,1.05 2.86,1.48c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="39"
+ android:propertyName="pathData"
+ android:startOffset="44"
+ android:valueFrom="M2.86 1.48 C2.86,1.48 1.58,0.21 1.39,0.03 C1.77,-0.26 1.7,-1.03 1.7,-1.03 C1.7,-1.03 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-1.51 -0.29,-1.51 C-0.29,-1.51 -2.31,-3.5 -2.31,-3.5 C-2.31,-3.5 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.44 3.65,-0.17 C3.61,0.19 3.36,1.05 2.86,1.48c "
+ android:valueTo="M3.12 0.99 C3.12,0.99 1.83,-0.26 1.71,-0.38 C1.72,-0.81 1.7,-1.31 1.7,-1.31 C1.7,-1.31 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-2.26 -0.29,-2.26 C-0.29,-2.26 -2.31,-4.23 -2.31,-4.23 C-2.31,-4.23 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.67,-0.54 3.66,-0.3 C3.63,0.12 3.38,0.7 3.12,0.99c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="83"
+ android:propertyName="pathData"
+ android:startOffset="83"
+ android:valueFrom="M3.12 0.99 C3.12,0.99 1.83,-0.26 1.71,-0.38 C1.72,-0.81 1.7,-1.31 1.7,-1.31 C1.7,-1.31 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-2.26 -0.29,-2.26 C-0.29,-2.26 -2.31,-4.23 -2.31,-4.23 C-2.31,-4.23 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.67,-0.54 3.66,-0.3 C3.63,0.12 3.38,0.7 3.12,0.99c "
+ android:valueTo="M3.5 -0.05 C3.5,-0.05 1.7,-1.85 1.7,-1.85 C1.7,-1.85 1.7,-1.92 1.7,-1.92 C1.7,-1.92 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.3,-3.85 -0.3,-3.85 C-0.3,-3.85 -2.3,-5.85 -2.3,-5.85 C-2.3,-5.85 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.65 3.64,-0.5 C3.6,-0.35 3.55,-0.2 3.5,-0.05c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5.04 4.45 C5.04,4.45 3.59,3.02 3.59,3.02 C4.82,1.94 5.31,0.96 5.53,0.3 C5.64,-0.12 5.7,-0.55 5.7,-1 C5.7,-1 7.7,-1 7.7,-1 C7.7,-0.27 7.59,0.43 7.38,1.09 C7.16,1.75 6.51,3.17 5.04,4.45c "
+ android:valueTo="M6.45 2.95 C6.45,2.95 5,1.5 5,1.5 C5.23,1.12 5.41,0.72 5.53,0.3 C5.64,-0.12 5.7,-0.55 5.7,-1 C5.7,-1 7.7,-1 7.7,-1 C7.7,-0.27 7.59,0.43 7.38,1.09 C7.16,1.75 6.85,2.37 6.45,2.95c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C-0.4,2.17 -0.94,1.89 -1.41,1.42 C-1.89,0.94 -2.18,0.37 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c "
+ android:valueTo="M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C-0.4,2.17 -0.94,1.89 -1.41,1.42 C-1.89,0.94 -2.18,0.37 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="100"
+ android:propertyName="pathData"
+ android:startOffset="67"
+ android:valueFrom="M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C-0.4,2.17 -0.94,1.89 -1.41,1.42 C-1.89,0.94 -2.18,0.37 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c "
+ android:valueTo="M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C0.09,2.2 -0.55,1.45 -1.02,0.98 C-1.5,0.5 -2.23,-0.14 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_4_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_4_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-9.89 -7.81 C-9.89,-7.81 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 -8.49,-9.21 -8.49,-9.21 C-8.49,-9.21 -9.89,-7.81 -9.89,-7.81c "
+ android:valueTo="M8.5 10.6 C8.5,10.6 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.2,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="267"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M5.05 4.43 C3.98,5.25 3.08,5.53 2.53,5.73 C2.26,5.81 1.98,5.87 1.7,5.9 C1.7,5.9 1.7,9 1.7,9 C1.7,9 -0.3,9 -0.3,9 C-0.3,9 -0.3,5.9 -0.3,5.9 C-2.02,5.65 -3.44,4.88 -4.59,3.59 C-5.73,2.3 -6.3,0.77 -6.3,-1 C-6.3,-1 -4.3,-1 -4.3,-1 C-4.3,0.38 -3.81,1.56 -2.84,2.54 C-1.86,3.51 -0.68,4 0.7,4 C0.88,4 1.06,3.99 1.23,3.96 C1.39,3.94 2.58,3.77 3.61,2.99 C3.61,2.99 5.05,4.43 5.05,4.43c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M2.31 1.86 C2.31,1.86 1.01,0.55 0.75,0.31 C1.7,0.27 1.7,-0.7 1.7,-0.7 C1.7,-0.7 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-0.66 -0.29,-0.66 C-0.29,-0.66 -2.31,-2.67 -2.31,-2.67 C-2.31,-2.67 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.7,-0.33 3.64,-0.03 C3.51,0.52 3.23,1.19 2.31,1.86c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M5.04 4.45 C5.04,4.45 3.59,3.02 3.59,3.02 C4.82,1.94 5.31,0.96 5.53,0.3 C5.64,-0.12 5.7,-0.55 5.7,-1 C5.7,-1 7.7,-1 7.7,-1 C7.7,-0.27 7.59,0.43 7.38,1.09 C7.16,1.75 6.51,3.17 5.04,4.45c " />
+ <path
+ android:name="_R_G_L_0_G_D_3_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C-0.4,2.17 -0.94,1.89 -1.41,1.42 C-1.89,0.94 -2.18,0.37 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c " />
+ <path
+ android:name="_R_G_L_0_G_D_4_P_0"
+ android:fillAlpha="0"
+ android:fillColor="#edf2eb"
+ android:fillType="nonZero"
+ android:pathData=" M-9.89 -7.81 C-9.89,-7.81 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 -8.49,-9.21 -8.49,-9.21 C-8.49,-9.21 -9.89,-7.81 -9.89,-7.81c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/qs_mic_access_on.xml b/packages/SystemUI/res/drawable/qs_mic_access_on.xml
new file mode 100644
index 000000000000..b485f06d1bf2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_mic_access_on.xml
@@ -0,0 +1,212 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:aapt="http://schemas.android.com/aapt">
+ <target android:name="_R_G_L_0_G_D_0_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M5.02 4.5 C3.59,5.47 2.79,5.64 2.53,5.73 C2.26,5.81 1.98,5.87 1.7,5.9 C1.7,5.9 1.7,9 1.7,9 C1.7,9 -0.3,9 -0.3,9 C-0.3,9 -0.3,5.9 -0.3,5.9 C-2.02,5.65 -3.44,4.88 -4.59,3.59 C-5.73,2.3 -6.3,0.77 -6.3,-1 C-6.3,-1 -4.3,-1 -4.3,-1 C-4.3,0.38 -3.81,1.56 -2.84,2.54 C-1.86,3.51 -0.68,4 0.7,4 C0.88,4 1.06,3.99 1.23,3.96 C1.39,3.94 2.53,3.83 3.58,3.07 C3.58,3.07 5.02,4.5 5.02,4.5c "
+ android:valueTo="M5.05 4.43 C3.98,5.25 3.08,5.53 2.53,5.73 C2.26,5.81 1.98,5.87 1.7,5.9 C1.7,5.9 1.7,9 1.7,9 C1.7,9 -0.3,9 -0.3,9 C-0.3,9 -0.3,5.9 -0.3,5.9 C-2.02,5.65 -3.44,4.88 -4.59,3.59 C-5.73,2.3 -6.3,0.77 -6.3,-1 C-6.3,-1 -4.3,-1 -4.3,-1 C-4.3,0.38 -3.81,1.56 -2.84,2.54 C-1.86,3.51 -0.68,4 0.7,4 C0.88,4 1.06,3.99 1.23,3.96 C1.39,3.94 2.58,3.77 3.61,2.99 C3.61,2.99 5.05,4.43 5.05,4.43c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_1_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="125"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M3.5 -0.05 C3.5,-0.05 1.7,-1.85 1.7,-1.85 C1.7,-1.85 1.7,-1.92 1.7,-1.92 C1.7,-1.92 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.3,-3.85 -0.3,-3.85 C-0.3,-3.85 -2.3,-5.85 -2.3,-5.85 C-2.3,-5.85 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.65 3.64,-0.5 C3.6,-0.35 3.55,-0.2 3.5,-0.05c "
+ android:valueTo="M3.12 0.99 C3.12,0.99 1.83,-0.26 1.71,-0.38 C1.72,-0.81 1.7,-1.31 1.7,-1.31 C1.7,-1.31 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-2.26 -0.29,-2.26 C-0.29,-2.26 -2.31,-4.23 -2.31,-4.23 C-2.31,-4.23 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.67,-0.54 3.66,-0.3 C3.63,0.12 3.38,0.7 3.12,0.99c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="58"
+ android:propertyName="pathData"
+ android:startOffset="125"
+ android:valueFrom="M3.12 0.99 C3.12,0.99 1.83,-0.26 1.71,-0.38 C1.72,-0.81 1.7,-1.31 1.7,-1.31 C1.7,-1.31 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-2.26 -0.29,-2.26 C-0.29,-2.26 -2.31,-4.23 -2.31,-4.23 C-2.31,-4.23 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.67,-0.54 3.66,-0.3 C3.63,0.12 3.38,0.7 3.12,0.99c "
+ android:valueTo="M2.86 1.48 C2.86,1.48 1.58,0.21 1.39,0.03 C1.77,-0.26 1.7,-1.03 1.7,-1.03 C1.7,-1.03 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-1.51 -0.29,-1.51 C-0.29,-1.51 -2.31,-3.5 -2.31,-3.5 C-2.31,-3.5 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.44 3.65,-0.17 C3.61,0.19 3.36,1.05 2.86,1.48c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="67"
+ android:propertyName="pathData"
+ android:startOffset="183"
+ android:valueFrom="M2.86 1.48 C2.86,1.48 1.58,0.21 1.39,0.03 C1.77,-0.26 1.7,-1.03 1.7,-1.03 C1.7,-1.03 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-1.51 -0.29,-1.51 C-0.29,-1.51 -2.31,-3.5 -2.31,-3.5 C-2.31,-3.5 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.44 3.65,-0.17 C3.61,0.19 3.36,1.05 2.86,1.48c "
+ android:valueTo="M2.42 1.81 C2.42,1.81 1.16,0.52 0.9,0.28 C1.7,0.14 1.7,-0.7 1.7,-0.7 C1.7,-0.7 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.29,-0.66 -0.29,-0.66 C-0.29,-0.66 -2.31,-2.67 -2.31,-2.67 C-2.31,-2.67 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.7,-0.33 3.64,-0.03 C3.58,0.27 3.22,1.2 2.42,1.81c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_2_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M6.45 2.95 C6.45,2.95 5,1.5 5,1.5 C5.23,1.12 5.41,0.72 5.53,0.3 C5.64,-0.12 5.7,-0.55 5.7,-1 C5.7,-1 7.7,-1 7.7,-1 C7.7,-0.27 7.59,0.43 7.38,1.09 C7.16,1.75 6.85,2.37 6.45,2.95c "
+ android:valueTo="M5.04 4.43 C5.04,4.43 3.59,2.98 3.59,2.98 C4.61,2.09 5.41,0.72 5.53,0.3 C5.64,-0.12 5.7,-0.55 5.7,-1 C5.7,-1 7.7,-1 7.7,-1 C7.7,-0.27 7.59,0.43 7.38,1.09 C7.16,1.75 6.19,3.44 5.04,4.43c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_3_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="250"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C0.09,2.2 -0.55,1.45 -1.02,0.98 C-1.5,0.5 -2.23,-0.14 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c "
+ android:valueTo="M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C-0.4,2.17 -0.94,1.89 -1.41,1.42 C-1.89,0.94 -2.18,0.37 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.4,0 0.1,1 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_4_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="400"
+ android:propertyName="fillAlpha"
+ android:startOffset="0"
+ android:valueFrom="1"
+ android:valueTo="1"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ <objectAnimator
+ android:duration="17"
+ android:propertyName="fillAlpha"
+ android:startOffset="400"
+ android:valueFrom="1"
+ android:valueTo="0"
+ android:valueType="floatType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.167,0.167 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="_R_G_L_0_G_D_4_P_0">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="167"
+ android:propertyName="pathData"
+ android:startOffset="0"
+ android:valueFrom="M8.5 10.6 C8.5,10.6 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c "
+ android:valueTo="M8.5 10.6 C8.5,10.6 8.51,10.6 8.51,10.6 C8.51,10.6 9.91,9.2 9.91,9.2 C9.91,9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c "
+ android:valueType="pathType">
+ <aapt:attr name="android:interpolator">
+ <pathInterpolator android:pathData="M 0.0,0.0 c0.5,0 0.833,0.833 1.0,1.0" />
+ </aapt:attr>
+ </objectAnimator>
+ </set>
+ </aapt:attr>
+ </target>
+ <target android:name="time_group">
+ <aapt:attr name="android:animation">
+ <set android:ordering="together">
+ <objectAnimator
+ android:duration="433"
+ android:propertyName="translateX"
+ android:startOffset="0"
+ android:valueFrom="0"
+ android:valueTo="1"
+ android:valueType="floatType" />
+ </set>
+ </aapt:attr>
+ </target>
+ <aapt:attr name="android:drawable">
+ <vector
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+ <group android:name="_R_G">
+ <group
+ android:name="_R_G_L_0_G"
+ android:translateX="12"
+ android:translateY="12">
+ <path
+ android:name="_R_G_L_0_G_D_0_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M5.02 4.5 C3.59,5.47 2.79,5.64 2.53,5.73 C2.26,5.81 1.98,5.87 1.7,5.9 C1.7,5.9 1.7,9 1.7,9 C1.7,9 -0.3,9 -0.3,9 C-0.3,9 -0.3,5.9 -0.3,5.9 C-2.02,5.65 -3.44,4.88 -4.59,3.59 C-5.73,2.3 -6.3,0.77 -6.3,-1 C-6.3,-1 -4.3,-1 -4.3,-1 C-4.3,0.38 -3.81,1.56 -2.84,2.54 C-1.86,3.51 -0.68,4 0.7,4 C0.88,4 1.06,3.99 1.23,3.96 C1.39,3.94 2.53,3.83 3.58,3.07 C3.58,3.07 5.02,4.5 5.02,4.5c " />
+ <path
+ android:name="_R_G_L_0_G_D_1_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M3.5 -0.05 C3.5,-0.05 1.7,-1.85 1.7,-1.85 C1.7,-1.85 1.7,-1.92 1.7,-1.92 C1.7,-1.92 1.7,-7 1.7,-7 C1.7,-7.28 1.61,-7.52 1.41,-7.71 C1.22,-7.9 0.98,-8 0.7,-8 C0.42,-8 0.18,-7.9 -0.01,-7.71 C-0.2,-7.52 -0.3,-7.28 -0.3,-7 C-0.3,-7 -0.3,-3.85 -0.3,-3.85 C-0.3,-3.85 -2.3,-5.85 -2.3,-5.85 C-2.3,-5.85 -2.3,-7 -2.3,-7 C-2.3,-7.83 -2.01,-8.54 -1.42,-9.12 C-0.84,-9.71 -0.13,-10 0.7,-10 C1.53,-10 2.24,-9.71 2.83,-9.12 C3.41,-8.54 3.7,-7.83 3.7,-7 C3.7,-7 3.7,-1 3.7,-1 C3.7,-0.82 3.68,-0.65 3.64,-0.5 C3.6,-0.35 3.55,-0.2 3.5,-0.05c " />
+ <path
+ android:name="_R_G_L_0_G_D_2_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M6.45 2.95 C6.45,2.95 5,1.5 5,1.5 C5.23,1.12 5.41,0.72 5.53,0.3 C5.64,-0.12 5.7,-0.55 5.7,-1 C5.7,-1 7.7,-1 7.7,-1 C7.7,-0.27 7.59,0.43 7.38,1.09 C7.16,1.75 6.85,2.37 6.45,2.95c " />
+ <path
+ android:name="_R_G_L_0_G_D_3_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M-0.29 -0.71 C-0.29,-0.42 -0.19,-0.19 0,0.01 C0.19,0.2 0.43,0.29 0.71,0.29 C0.76,0.31 0.75,0.31 0.78,0.3 C1.14,0.67 1.92,1.43 2.34,1.85 C2.03,2.04 1.55,2.29 0.71,2.29 C0.55,2.29 0.39,2.28 0.23,2.26 C0.09,2.2 -0.55,1.45 -1.02,0.98 C-1.5,0.5 -2.23,-0.14 -2.26,-0.28 C-2.28,-0.42 -2.29,-0.56 -2.29,-0.71 C-2.29,-0.71 -2.31,-2.72 -2.31,-2.72 C-2.31,-2.72 -0.29,-0.71 -0.29,-0.71c " />
+ <path
+ android:name="_R_G_L_0_G_D_4_P_0"
+ android:fillAlpha="1"
+ android:fillColor="#ffffff"
+ android:fillType="nonZero"
+ android:pathData=" M8.5 10.6 C8.5,10.6 -9.9,-7.8 -9.9,-7.8 C-9.9,-7.8 -8.5,-9.2 -8.5,-9.2 C-8.5,-9.2 9.9,9.2 9.9,9.2 C9.9,9.2 8.5,10.6 8.5,10.6c " />
+ </group>
+ </group>
+ <group android:name="time_group" />
+ </vector>
+ </aapt:attr>
+</animated-vector>
diff --git a/packages/SystemUI/res/drawable/screenshot_edit_background.xml b/packages/SystemUI/res/drawable/screenshot_edit_background.xml
index ff5c62e1600b..a1185a2d5479 100644
--- a/packages/SystemUI/res/drawable/screenshot_edit_background.xml
+++ b/packages/SystemUI/res/drawable/screenshot_edit_background.xml
@@ -17,7 +17,7 @@
<!-- Long screenshot edit FAB background -->
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:color="?android:textColorPrimary">
+ android:color="@color/overlay_button_ripple">
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<solid android:color="?androidprv:attr/colorAccentPrimary"/>
diff --git a/packages/SystemUI/res/drawable/settings_input_antenna.xml b/packages/SystemUI/res/drawable/settings_input_antenna.xml
new file mode 100644
index 000000000000..f2adcaf069ef
--- /dev/null
+++ b/packages/SystemUI/res/drawable/settings_input_antenna.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp"
+ android:height="24dp" android:viewportWidth="24" android:viewportHeight="24"
+ android:tint="?android:attr/textColorSecondary">
+ <path android:fillColor="#FF000000"
+ android:pathData="M9,22.4 L7.6,21 11,17.6V14.3Q10.325,14 9.913,13.375Q9.5,12.75 9.5,12Q9.5,10.95 10.225,10.225Q10.95,9.5 12,9.5Q13.05,9.5 13.775,10.225Q14.5,10.95 14.5,12Q14.5,12.75 14.088,13.375Q13.675,14 13,14.3V17.6L16.4,21L15,22.4L12,19.4ZM5,12Q5,9.05 7.05,7.025Q9.1,5 12,5Q14.9,5 16.95,7.025Q19,9.05 19,12H17Q17,9.925 15.538,8.462Q14.075,7 12,7Q9.925,7 8.463,8.462Q7,9.925 7,12ZM1,12Q1,9.7 1.863,7.7Q2.725,5.7 4.225,4.212Q5.725,2.725 7.725,1.862Q9.725,1 12,1Q14.275,1 16.275,1.862Q18.275,2.725 19.775,4.212Q21.275,5.7 22.138,7.7Q23,9.7 23,12H21Q21,10.125 20.288,8.487Q19.575,6.85 18.35,5.625Q17.125,4.4 15.488,3.7Q13.85,3 12,3Q10.15,3 8.512,3.7Q6.875,4.4 5.65,5.625Q4.425,6.85 3.712,8.487Q3,10.125 3,12Z"/>
+</vector> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_container_view.xml b/packages/SystemUI/res/layout/auth_container_view.xml
index 3db01a4e7f3a..2bd2e640127e 100644
--- a/packages/SystemUI/res/layout/auth_container_view.xml
+++ b/packages/SystemUI/res/layout/auth_container_view.xml
@@ -23,7 +23,6 @@
android:id="@+id/background"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="@color/biometric_dialog_dim_color"
android:contentDescription="@string/biometric_dialog_empty_space_description"/>
<View
diff --git a/packages/SystemUI/res/layout/brightness_mirror_container.xml b/packages/SystemUI/res/layout/brightness_mirror_container.xml
index ac90db3e5e19..1bf45aad8906 100644
--- a/packages/SystemUI/res/layout/brightness_mirror_container.xml
+++ b/packages/SystemUI/res/layout/brightness_mirror_container.xml
@@ -23,7 +23,6 @@
android:background="@drawable/brightness_mirror_background"
android:layout_gravity="center_vertical"
android:layout_margin="8dp"
- android:padding="@dimen/rounded_slider_background_padding"
android:gravity="center"
android:visibility="invisible">
diff --git a/packages/SystemUI/res/layout/broadcast_dialog.xml b/packages/SystemUI/res/layout/broadcast_dialog.xml
new file mode 100644
index 000000000000..5ba2afe5b172
--- /dev/null
+++ b/packages/SystemUI/res/layout/broadcast_dialog.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/dialog_bg"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="@dimen/broadcast_dialog_margin"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/dialog_icon"
+ android:layout_width="@dimen/broadcast_dialog_icon_size"
+ android:layout_height="@dimen/broadcast_dialog_icon_size"
+ android:layout_marginTop="@dimen/broadcast_dialog_icon_margin_top"
+ android:layout_marginBottom="@dimen/broadcast_dialog_title_img_margin_top"
+ android:layout_gravity="center"
+ android:src="@drawable/settings_input_antenna"/>
+
+ <TextView
+ style="@style/BroadcastDialogTitleStyle"
+ android:id="@+id/dialog_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="center"/>
+
+ <TextView
+ style="@style/BroadcastDialogBodyStyle"
+ android:id="@+id/dialog_subtitle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:layout_gravity="center"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="@dimen/broadcast_dialog_margin"
+ android:layout_marginBottom="@dimen/broadcast_dialog_margin"
+ android:orientation="vertical">
+
+ <Button
+ android:layout_marginBottom="@dimen/broadcast_dialog_btn_margin_bottom"
+ android:id="@+id/switch_broadcast"
+ style="@style/BroadcastDialogButtonStyle"/>
+
+ <Button
+ android:layout_marginBottom="@dimen/broadcast_dialog_btn_margin_bottom"
+ android:id="@+id/change_output"
+ android:text="@string/bt_le_audio_broadcast_dialog_different_output"
+ style="@style/BroadcastDialogButtonStyle"/>
+
+ <Button
+ android:id="@+id/cancel"
+ android:text="@android:string/cancel"
+ style="@style/BroadcastDialogButtonStyle"/>
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml
index 99a5a2e904f6..1a1fc75a41a1 100644
--- a/packages/SystemUI/res/layout/clipboard_overlay.xml
+++ b/packages/SystemUI/res/layout/clipboard_overlay.xml
@@ -115,6 +115,7 @@
android:autoSizeMinTextSize="@dimen/clipboard_overlay_min_font"
android:autoSizeMaxTextSize="@dimen/clipboard_overlay_max_font"
android:textColor="?attr/overlayButtonTextColor"
+ android:textColorLink="?attr/overlayButtonTextColor"
android:background="?androidprv:attr/colorAccentSecondary"
android:layout_width="@dimen/clipboard_preview_size"
android:layout_height="@dimen/clipboard_preview_size"/>
diff --git a/packages/SystemUI/res/layout/combined_qs_header.xml b/packages/SystemUI/res/layout/combined_qs_header.xml
index ec82ccf2022e..5dc34b9db594 100644
--- a/packages/SystemUI/res/layout/combined_qs_header.xml
+++ b/packages/SystemUI/res/layout/combined_qs_header.xml
@@ -14,7 +14,7 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<androidx.constraintlayout.motion.widget.MotionLayout
+<com.android.systemui.util.NoRemeasureMotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/split_shade_status_bar"
@@ -32,10 +32,37 @@
<androidx.constraintlayout.widget.Guideline
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:id="@+id/center"
- app:layout_constraintGuide_percent="0.5"
+ android:id="@+id/begin_guide"
+ android:orientation="vertical"
+ app:layout_constraintGuide_begin="0dp"/>
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/end_guide"
+ android:orientation="vertical"
+ app:layout_constraintGuide_end="0dp"
+ />
+
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/center_left"
android:orientation="vertical" />
+ <androidx.constraintlayout.widget.Guideline
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/center_right"
+ android:orientation="vertical" />
+
+ <androidx.constraintlayout.widget.Barrier
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/barrier"
+ app:barrierDirection="start"
+ app:constraint_referenced_ids="statusIcons,privacy_container" />
+
<com.android.systemui.statusbar.policy.Clock
android:id="@+id/clock"
android:layout_width="wrap_content"
@@ -44,18 +71,25 @@
android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
android:singleLine="true"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.QS.Status"
+ android:transformPivotX="0sp"
+ android:transformPivotY="20sp"
+ android:scaleX="1"
+ android:scaleY="1"
/>
- <com.android.systemui.statusbar.policy.DateView
+ <com.android.systemui.statusbar.policy.VariableDateView
android:id="@+id/date"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_gravity="start|center_vertical"
android:gravity="center_vertical"
android:singleLine="true"
+ android:textDirection="locale"
android:textAppearance="@style/TextAppearance.QS.Status"
- app:datePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ app:longDatePattern="@string/abbrev_wday_month_day_no_year_alarm"
+ app:shortDatePattern="@string/abbrev_month_day_no_year"
/>
<include
@@ -81,7 +115,7 @@
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
android:paddingEnd="@dimen/signal_cluster_battery_padding"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintStart_toEndOf="@id/carrier_group"
app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
app:layout_constraintTop_toTopOf="@id/clock"
@@ -92,8 +126,9 @@
<com.android.systemui.battery.BatteryMeterView
android:id="@+id/batteryRemainingIcon"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ app:layout_constrainedWidth="true"
app:textAppearance="@style/TextAppearance.QS.Status"
app:layout_constraintStart_toEndOf="@id/statusIcons"
app:layout_constraintEnd_toEndOf="parent"
@@ -104,13 +139,18 @@
<FrameLayout
android:id="@+id/privacy_container"
android:layout_width="wrap_content"
- android:layout_height="48dp"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
android:gravity="center"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
>
<include layout="@layout/ongoing_privacy_chip"/>
</FrameLayout>
-</androidx.constraintlayout.motion.widget.MotionLayout> \ No newline at end of file
+ <Space
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:id="@+id/space"
+ />
+</com.android.systemui.util.NoRemeasureMotionLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml b/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
new file mode 100644
index 000000000000..fcebb8d3f6c6
--- /dev/null
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_aqi.xml
@@ -0,0 +1,26 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/aqi_view"
+ style="@style/clock_subtitle"
+ android:visibility="gone"
+ android:background="@drawable/dream_aqi_badge_bg"
+ android:paddingHorizontal="@dimen/dream_aqi_badge_padding_horizontal"
+ android:paddingVertical="@dimen/dream_aqi_badge_padding_vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
index 3f56bafef134..efbdd1af3644 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_date.xml
@@ -20,5 +20,5 @@
style="@style/clock_subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:format12Hour="EEE, MMM d"
- android:format24Hour="EEE, MMM d"/>
+ android:format12Hour="@string/dream_date_complication_date_format"
+ android:format24Hour="@string/dream_date_complication_date_format"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
index e066d38e446f..7a57293f58bd 100644
--- a/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_complication_clock_time.xml
@@ -22,8 +22,8 @@
android:fontFamily="@font/clock"
android:includeFontPadding="false"
android:textColor="@android:color/white"
- android:format12Hour="h:mm"
- android:format24Hour="kk:mm"
+ android:format12Hour="@string/dream_time_complication_12_hr_time_format"
+ android:format24Hour="@string/dream_time_complication_24_hr_time_format"
android:shadowColor="@color/keyguard_shadow_color"
android:shadowRadius="?attr/shadowRadius"
android:textSize="@dimen/dream_overlay_complication_clock_time_text_size"/>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index d0f4903a3421..70a770912c7f 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -70,15 +70,32 @@
android:visibility="gone"
android:contentDescription="@string/dream_overlay_status_bar_wifi_off" />
- <com.android.systemui.dreams.DreamOverlayDotImageView
+ <ImageView
+ android:id="@+id/dream_overlay_mic_off"
+ android:layout_width="@dimen/dream_overlay_grey_chip_width"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/dream_overlay_mic_off"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_mic_off" />
+
+ <ImageView
+ android:id="@+id/dream_overlay_camera_off"
+ android:layout_width="@dimen/dream_overlay_grey_chip_width"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/dream_overlay_camera_off"
+ android:visibility="gone"
+ android:contentDescription="@string/dream_overlay_status_bar_camera_off" />
+
+ <ImageView
android:id="@+id/dream_overlay_camera_mic_off"
- android:layout_width="@dimen/dream_overlay_camera_mic_off_indicator_size"
- android:layout_height="@dimen/dream_overlay_camera_mic_off_indicator_size"
- android:layout_gravity="center_vertical"
+ android:layout_width="@dimen/dream_overlay_grey_chip_width"
+ android:layout_height="match_parent"
android:layout_marginEnd="@dimen/dream_overlay_status_icon_margin"
+ android:src="@drawable/dream_overlay_mic_and_camera_off"
android:visibility="gone"
- android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off"
- app:dotColor="@color/dream_overlay_camera_mic_off_dot_color" />
+ android:contentDescription="@string/dream_overlay_status_bar_camera_mic_off" />
</LinearLayout>
</com.android.systemui.dreams.DreamOverlayStatusBarView>
diff --git a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
index 43b16618d615..a313833e2a66 100644
--- a/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_conversation_notification.xml
@@ -57,7 +57,8 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
<TextView
@@ -65,6 +66,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
</com.android.systemui.statusbar.notification.row.HybridConversationNotificationView>
diff --git a/packages/SystemUI/res/layout/hybrid_notification.xml b/packages/SystemUI/res/layout/hybrid_notification.xml
index e8d77511e53c..9ea7be50adec 100644
--- a/packages/SystemUI/res/layout/hybrid_notification.xml
+++ b/packages/SystemUI/res/layout/hybrid_notification.xml
@@ -20,19 +20,22 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="bottom|start"
- style="?attr/hybridNotificationStyle">
+ android:paddingStart="@*android:dimen/notification_content_margin_start"
+ android:paddingEnd="12dp">
<TextView
android:id="@+id/notification_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTitleStyle"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:paddingEnd="4dp"
/>
<TextView
android:id="@+id/notification_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
- style="?attr/hybridNotificationTextStyle"
+ android:paddingEnd="4dp"
+ style="@*android:style/Widget.DeviceDefault.Notification.Text"
/>
</com.android.systemui.statusbar.notification.row.HybridNotificationView> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
index 8f8993f3c8d9..12dfa1042dd7 100644
--- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml
+++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml
@@ -59,26 +59,6 @@
</LinearLayout>
- <com.android.systemui.statusbar.KeyguardAffordanceView
- android:id="@+id/camera_button"
- android:layout_height="@dimen/keyguard_affordance_height"
- android:layout_width="@dimen/keyguard_affordance_width"
- android:layout_gravity="bottom|end"
- android:src="@drawable/ic_camera_alt_24dp"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_camera_button"
- android:tint="?attr/wallpaperTextColor" />
-
- <com.android.systemui.statusbar.KeyguardAffordanceView
- android:id="@+id/left_button"
- android:layout_height="@dimen/keyguard_affordance_height"
- android:layout_width="@dimen/keyguard_affordance_width"
- android:layout_gravity="bottom|start"
- android:src="@*android:drawable/ic_phone"
- android:scaleType="center"
- android:contentDescription="@string/accessibility_phone_button"
- android:tint="?attr/wallpaperTextColor" />
-
<ImageView
android:id="@+id/wallet_button"
android:layout_height="@dimen/keyguard_affordance_fixed_height"
diff --git a/packages/SystemUI/res/layout/keyguard_status_bar.xml b/packages/SystemUI/res/layout/keyguard_status_bar.xml
index e47eed9ea04a..d27fa192e741 100644
--- a/packages/SystemUI/res/layout/keyguard_status_bar.xml
+++ b/packages/SystemUI/res/layout/keyguard_status_bar.xml
@@ -60,9 +60,8 @@
</com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
<FrameLayout android:id="@+id/system_icons_container"
- android:layout_width="0dp"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:layout_weight="1"
android:layout_marginEnd="@dimen/status_bar_padding_end"
android:gravity="center_vertical|end">
<include layout="@layout/system_icons" />
diff --git a/packages/SystemUI/res/layout/large_screen_shade_header.xml b/packages/SystemUI/res/layout/large_screen_shade_header.xml
index 250eabd42d50..3029a2777fd9 100644
--- a/packages/SystemUI/res/layout/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/layout/large_screen_shade_header.xml
@@ -22,7 +22,7 @@
android:minHeight="@dimen/large_screen_shade_header_min_height"
android:clickable="false"
android:focusable="true"
- android:paddingLeft="@dimen/qs_panel_padding"
+ android:paddingLeft="@dimen/large_screen_shade_header_left_padding"
android:paddingRight="@dimen/qs_panel_padding"
android:visibility="gone"
android:theme="@style/Theme.SystemUI.QuickSettings.Header">
diff --git a/packages/SystemUI/res/layout/media_output_dialog.xml b/packages/SystemUI/res/layout/media_output_dialog.xml
index 1efb4796b5b7..93c16e4e119d 100644
--- a/packages/SystemUI/res/layout/media_output_dialog.xml
+++ b/packages/SystemUI/res/layout/media_output_dialog.xml
@@ -42,12 +42,35 @@
android:layout_height="wrap_content"
android:paddingStart="12dp"
android:orientation="vertical">
- <ImageView
- android:id="@+id/app_source_icon"
- android:layout_width="20dp"
- android:layout_height="20dp"
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
android:gravity="center_vertical"
- android:importantForAccessibility="no"/>
+ android:orientation="horizontal">
+ <ImageView
+ android:id="@+id/app_source_icon"
+ android:layout_width="20dp"
+ android:layout_height="20dp"
+ android:gravity="center_vertical"
+ android:importantForAccessibility="no"/>
+
+ <Space
+ android:layout_weight="1"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"/>
+
+ <ImageView
+ android:id="@+id/broadcast_icon"
+ android:src="@drawable/settings_input_antenna"
+ android:contentDescription="@string/broadcasting_description_is_broadcasting"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:padding="12dp"
+ android:gravity="center_vertical"
+ android:clickable="true"
+ android:focusable="true"
+ android:visibility="gone"/>
+ </LinearLayout>
<TextView
android:id="@+id/header_title"
android:layout_width="wrap_content"
@@ -89,8 +112,7 @@
android:id="@+id/list_result"
android:scrollbars="vertical"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:overScrollMode="never"/>
+ android:layout_height="wrap_content"/>
</LinearLayout>
<LinearLayout
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index 0e20fa3f46b5..c526d9cc8dd3 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -42,7 +42,6 @@
android:adjustViewBounds="true"
android:clipToOutline="true"
android:background="@drawable/qs_media_outline_album_bg"
- android:foreground="@drawable/qs_media_scrim"
/>
<!-- Guideline for output switcher -->
@@ -151,7 +150,7 @@
<!-- See comment in media_session_collapsed.xml for how these barriers are used -->
<androidx.constraintlayout.widget.Barrier
- android:id="@+id/media_action_barrier"
+ android:id="@+id/media_action_barrier_start"
android:layout_width="0dp"
android:layout_height="0dp"
android:orientation="vertical"
@@ -173,6 +172,7 @@
app:layout_constraintStart_toStartOf="parent"
/>
+ <!-- This barrier is used in expanded view to constrain the bottom row of actions -->
<androidx.constraintlayout.widget.Barrier
android:id="@+id/media_action_barrier_top"
android:layout_width="match_parent"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip.xml b/packages/SystemUI/res/layout/media_ttt_chip.xml
index 4d24140abbf4..d88680669fe0 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip.xml
@@ -31,6 +31,8 @@
android:padding="@dimen/media_ttt_chip_outer_padding"
android:background="@drawable/media_ttt_chip_background"
android:layout_marginTop="20dp"
+ android:layout_marginStart="@dimen/notification_side_paddings"
+ android:layout_marginEnd="@dimen/notification_side_paddings"
android:clipToPadding="false"
android:gravity="center_vertical"
android:alpha="0.0"
@@ -46,8 +48,9 @@
<TextView
android:id="@+id/text"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
+ android:layout_weight="1"
android:textSize="@dimen/media_ttt_text_size"
android:textColor="?android:attr/textColorPrimary"
android:alpha="0.0"
diff --git a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
index 5e8b892018eb..e079fd3c5e8f 100644
--- a/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
+++ b/packages/SystemUI/res/layout/media_ttt_chip_receiver.xml
@@ -14,20 +14,25 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<!-- TODO(b/203800646): layout_marginTop doesn't seem to work on some large screens. -->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/media_ttt_receiver_chip"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:background="@drawable/media_ttt_chip_background_receiver"
>
+ <com.android.systemui.media.taptotransfer.receiver.ReceiverChipRippleView
+ android:id="@+id/ripple"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ />
+
<com.android.internal.widget.CachingIconView
android:id="@+id/app_icon"
android:layout_width="@dimen/media_ttt_icon_size_receiver"
android:layout_height="@dimen/media_ttt_icon_size_receiver"
- android:layout_gravity="center"
+ android:layout_gravity="center|bottom"
+ android:alpha="0.0"
/>
</FrameLayout>
diff --git a/packages/SystemUI/res/layout/notification_icon_area.xml b/packages/SystemUI/res/layout/notification_icon_area.xml
index fa696cc1f54c..aadfae8c5aed 100644
--- a/packages/SystemUI/res/layout/notification_icon_area.xml
+++ b/packages/SystemUI/res/layout/notification_icon_area.xml
@@ -14,18 +14,9 @@
~ See the License for the specific language governing permissions and
~ limitations under the License
-->
-<com.android.keyguard.AlphaOptimizedLinearLayout
+<com.android.systemui.statusbar.phone.NotificationIconContainer
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/notification_icon_area_inner"
- android:layout_width="match_parent"
+ android:id="@+id/notificationIcons"
+ android:layout_width="wrap_content"
android:layout_height="match_parent"
- android:clipChildren="false">
- <com.android.systemui.statusbar.phone.NotificationIconContainer
- android:id="@+id/notificationIcons"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_alignParentStart="true"
- android:gravity="center_vertical"
- android:orientation="horizontal"
- android:clipChildren="false"/>
-</com.android.keyguard.AlphaOptimizedLinearLayout> \ No newline at end of file
+ android:clipChildren="false"/> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_activity.xml b/packages/SystemUI/res/layout/people_space_activity.xml
index 7102375a89bf..f45cc7c464d5 100644
--- a/packages/SystemUI/res/layout/people_space_activity.xml
+++ b/packages/SystemUI/res/layout/people_space_activity.xml
@@ -13,103 +13,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<LinearLayout
+<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/top_level"
+ android:id="@+id/container"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="8dp">
- <TextView
- android:id="@+id/select_conversation_title"
- android:text="@string/select_conversation_title"
- android:gravity="center"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="24sp"/>
-
- <TextView
- android:id="@+id/select_conversation"
- android:text="@string/select_conversation_text"
- android:gravity="center"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
- android:textColor="?android:attr/textColorPrimary"
- android:textSize="16sp"
- android:paddingVertical="24dp"
- android:paddingHorizontal="48dp"/>
-
- <androidx.core.widget.NestedScrollView
- android:id="@+id/scroll_view"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <LinearLayout
- android:id="@+id/scroll_layout"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="16dp"
- android:orientation="vertical">
-
- <LinearLayout
- android:id="@+id/priority"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="35dp">
- <TextView
- android:id="@+id/priority_header"
- android:text="@string/priority_conversations"
- android:layout_width="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:textSize="14sp"
- android:paddingStart="16dp"
- android:layout_height="wrap_content"/>
-
- <LinearLayout
- android:id="@+id/priority_tiles"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:clipToOutline="true">
- </LinearLayout>
- </LinearLayout>
-
- <LinearLayout
- android:id="@+id/recent"
- android:orientation="vertical"
- android:layout_width="match_parent"
- android:layout_height="wrap_content">
- <TextView
- android:id="@+id/recent_header"
- android:gravity="start"
- android:text="@string/recent_conversations"
- android:layout_width="wrap_content"
- android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
- android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
- android:textSize="14sp"
- android:paddingStart="16dp"
- android:layout_height="wrap_content"/>
-
- <LinearLayout
- android:id="@+id/recent_tiles"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:orientation="vertical"
- android:background="@drawable/rounded_bg_full_large_radius"
- android:clipToOutline="true">
- </LinearLayout>
- </LinearLayout>
- </LinearLayout>
- </androidx.core.widget.NestedScrollView>
-</LinearLayout> \ No newline at end of file
+ android:layout_height="match_parent">
+ <!-- The content of people_space_activity_(no|with)_conversations.xml will be added here at
+ runtime depending on the number of conversations to show. -->
+</FrameLayout>
diff --git a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
index 2e9ff07caed9..a97c90c5e8ac 100644
--- a/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
+++ b/packages/SystemUI/res/layout/people_space_activity_no_conversations.xml
@@ -16,7 +16,7 @@
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
- android:id="@+id/top_level"
+ android:id="@+id/top_level_no_conversations"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="24dp"
@@ -54,7 +54,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/rounded_bg_full_large_radius"
- android:onClick="dismissActivity"
android:text="@string/got_it"
android:textColor="?androidprv:attr/textColorOnAccent"
android:layout_marginBottom="60dp"
diff --git a/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
new file mode 100644
index 000000000000..2384963c44db
--- /dev/null
+++ b/packages/SystemUI/res/layout/people_space_activity_with_conversations.xml
@@ -0,0 +1,115 @@
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+ android:id="@+id/top_level_with_conversations"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:padding="8dp">
+ <TextView
+ android:id="@+id/select_conversation_title"
+ android:text="@string/select_conversation_title"
+ android:gravity="center"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="24sp"/>
+
+ <TextView
+ android:id="@+id/select_conversation"
+ android:text="@string/select_conversation_text"
+ android:gravity="center"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
+ android:textColor="?android:attr/textColorPrimary"
+ android:textSize="16sp"
+ android:paddingVertical="24dp"
+ android:paddingHorizontal="48dp"/>
+
+ <androidx.core.widget.NestedScrollView
+ android:id="@+id/scroll_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/scroll_layout"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:id="@+id/priority"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="35dp">
+ <TextView
+ android:id="@+id/priority_header"
+ android:text="@string/priority_conversations"
+ android:layout_width="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+ android:textSize="14sp"
+ android:paddingStart="16dp"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/priority_tiles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:orientation="vertical"
+ android:background="@drawable/rounded_bg_full_large_radius"
+ android:clipToOutline="true">
+ </LinearLayout>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/recent"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+ <TextView
+ android:id="@+id/recent_header"
+ android:gravity="start"
+ android:text="@string/recent_conversations"
+ android:layout_width="wrap_content"
+ android:textAppearance="@*android:style/TextAppearance.DeviceDefault.Notification.Title"
+ android:textColor="?androidprv:attr/colorAccentPrimaryVariant"
+ android:textSize="14sp"
+ android:paddingStart="16dp"
+ android:layout_height="wrap_content"/>
+
+ <LinearLayout
+ android:id="@+id/recent_tiles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:orientation="vertical"
+ android:background="@drawable/rounded_bg_full_large_radius"
+ android:clipToOutline="true">
+ </LinearLayout>
+ </LinearLayout>
+ </LinearLayout>
+ </androidx.core.widget.NestedScrollView>
+</LinearLayout> \ No newline at end of file
diff --git a/packages/SystemUI/res/layout/people_space_tile_view.xml b/packages/SystemUI/res/layout/people_space_tile_view.xml
index 2a2c35dde841..b0599caae6df 100644
--- a/packages/SystemUI/res/layout/people_space_tile_view.xml
+++ b/packages/SystemUI/res/layout/people_space_tile_view.xml
@@ -37,8 +37,8 @@
<ImageView
android:id="@+id/tile_view_person_icon"
- android:layout_width="52dp"
- android:layout_height="52dp" />
+ android:layout_width="@dimen/avatar_size_for_medium"
+ android:layout_height="@dimen/avatar_size_for_medium" />
<LinearLayout
android:orientation="horizontal"
diff --git a/packages/SystemUI/res/layout/qs_tile_label.xml b/packages/SystemUI/res/layout/qs_tile_label.xml
index 77523ec9229f..c124aea01afc 100644
--- a/packages/SystemUI/res/layout/qs_tile_label.xml
+++ b/packages/SystemUI/res/layout/qs_tile_label.xml
@@ -28,7 +28,7 @@
android:importantForAccessibility="no"
android:layout_gravity="center_vertical | start">
- <com.android.systemui.util.SafeMarqueeTextView
+ <com.android.systemui.util.DelayableMarqueeTextView
android:id="@+id/tile_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -41,7 +41,7 @@
android:importantForAccessibility="no"
android:textAppearance="@style/TextAppearance.QS.TileLabel"/>
- <com.android.systemui.util.SafeMarqueeTextView
+ <com.android.systemui.util.DelayableMarqueeTextView
android:id="@+id/app_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
diff --git a/packages/SystemUI/res/layout/status_bar.xml b/packages/SystemUI/res/layout/status_bar.xml
index deab1ebd6507..e281511140c7 100644
--- a/packages/SystemUI/res/layout/status_bar.xml
+++ b/packages/SystemUI/res/layout/status_bar.xml
@@ -47,52 +47,63 @@
android:paddingStart="@dimen/status_bar_padding_start"
android:paddingEnd="@dimen/status_bar_padding_end"
android:paddingTop="@dimen/status_bar_padding_top"
- android:orientation="horizontal"
- >
+ android:orientation="horizontal">
+
+ <!-- Container for the entire start half of the status bar. It will always use the same
+ width, independent of the number of visible children and sub-children. -->
<FrameLayout
+ android:id="@+id/status_bar_start_side_container"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_weight="1">
- <include layout="@layout/heads_up_status_bar_layout" />
+ <!-- Container that is wrapped around the views on the start half of the status bar.
+ Its width will change with the number of visible children and sub-children.
+ It is useful when we want to know the visible bounds of the content. -->
+ <FrameLayout
+ android:id="@+id/status_bar_start_side_content"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:clipChildren="false">
- <!-- The alpha of the left side is controlled by PhoneStatusBarTransitions, and the
- individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK and
- DISABLE_NOTIFICATION_ICONS, respectively -->
- <LinearLayout
- android:id="@+id/status_bar_left_side"
- android:layout_height="match_parent"
- android:layout_width="match_parent"
- android:clipChildren="false"
- >
- <ViewStub
- android:id="@+id/operator_name"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout="@layout/operator_name" />
+ <include layout="@layout/heads_up_status_bar_layout" />
- <com.android.systemui.statusbar.policy.Clock
- android:id="@+id/clock"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- android:singleLine="true"
- android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
- android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
- android:gravity="center_vertical|start"
- />
-
- <include layout="@layout/ongoing_call_chip" />
-
- <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
- android:id="@+id/notification_icon_area"
- android:layout_width="0dp"
- android:layout_height="match_parent"
- android:layout_weight="1"
- android:orientation="horizontal"
- android:clipChildren="false"/>
+ <!-- The alpha of the start side is controlled by PhoneStatusBarTransitions, and the
+ individual views are controlled by StatusBarManager disable flags DISABLE_CLOCK
+ and DISABLE_NOTIFICATION_ICONS, respectively -->
+ <LinearLayout
+ android:id="@+id/status_bar_start_side_except_heads_up"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:clipChildren="false">
+ <ViewStub
+ android:id="@+id/operator_name"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout="@layout/operator_name" />
+
+ <com.android.systemui.statusbar.policy.Clock
+ android:id="@+id/clock"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ android:singleLine="true"
+ android:paddingStart="@dimen/status_bar_left_clock_starting_padding"
+ android:paddingEnd="@dimen/status_bar_left_clock_end_padding"
+ android:gravity="center_vertical|start"
+ />
+
+ <include layout="@layout/ongoing_call_chip" />
- </LinearLayout>
+ <com.android.systemui.statusbar.AlphaOptimizedFrameLayout
+ android:id="@+id/notification_icon_area"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="horizontal"
+ android:clipChildren="false"/>
+
+ </LinearLayout>
+ </FrameLayout>
</FrameLayout>
<!-- Space should cover the notch (if it exists) and let other views lay out around it -->
@@ -103,42 +114,57 @@
android:gravity="center_horizontal|center_vertical"
/>
- <com.android.keyguard.AlphaOptimizedLinearLayout android:id="@+id/system_icon_area"
+ <!-- Container for the entire end half of the status bar. It will always use the same
+ width, independent of the number of visible children and sub-children. -->
+ <FrameLayout
+ android:id="@+id/status_bar_end_side_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
- android:orientation="horizontal"
- android:gravity="center_vertical|end"
- >
-
- <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
- android:id="@+id/user_switcher_container"
+ android:clipChildren="false">
+
+ <!-- Container that is wrapped around the views on the end half of the
+ status bar. Its width will change with the number of visible children and
+ sub-children.
+ It is useful when we want know the visible bounds of the content.-->
+ <com.android.keyguard.AlphaOptimizedLinearLayout
+ android:id="@+id/status_bar_end_side_content"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:gravity="center"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
android:orientation="horizontal"
- android:paddingTop="4dp"
- android:paddingBottom="4dp"
- android:paddingStart="8dp"
- android:paddingEnd="8dp"
- android:layout_marginEnd="16dp"
- android:background="@drawable/status_bar_user_chip_bg"
- android:visibility="visible" >
- <ImageView android:id="@+id/current_user_avatar"
- android:layout_width="@dimen/multi_user_avatar_keyguard_size"
- android:layout_height="@dimen/multi_user_avatar_keyguard_size"
- android:scaleType="centerInside"
- android:paddingEnd="4dp" />
-
- <TextView android:id="@+id/current_user_name"
+ android:gravity="center_vertical|end"
+ android:clipChildren="false">
+
+ <com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer
+ android:id="@+id/user_switcher_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.StatusBar.Clock"
- />
- </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
-
- <include layout="@layout/system_icons" />
- </com.android.keyguard.AlphaOptimizedLinearLayout>
+ android:gravity="center"
+ android:orientation="horizontal"
+ android:paddingTop="4dp"
+ android:paddingBottom="4dp"
+ android:paddingStart="8dp"
+ android:paddingEnd="8dp"
+ android:layout_marginEnd="16dp"
+ android:background="@drawable/status_bar_user_chip_bg"
+ android:visibility="visible" >
+ <ImageView android:id="@+id/current_user_avatar"
+ android:layout_width="@dimen/multi_user_avatar_keyguard_size"
+ android:layout_height="@dimen/multi_user_avatar_keyguard_size"
+ android:scaleType="centerInside"
+ android:paddingEnd="4dp" />
+
+ <TextView android:id="@+id/current_user_name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textAppearance="@style/TextAppearance.StatusBar.Clock"
+ />
+ </com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer>
+
+ <include layout="@layout/system_icons" />
+ </com.android.keyguard.AlphaOptimizedLinearLayout>
+ </FrameLayout>
</LinearLayout>
<ViewStub
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 4d5bf53eb64a..6423a50fc107 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -18,7 +18,7 @@
-->
-<com.android.systemui.statusbar.phone.NotificationPanelView
+<com.android.systemui.shade.NotificationPanelView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:systemui="http://schemas.android.com/apk/res-auto"
android:id="@+id/notification_panel"
@@ -67,7 +67,7 @@
</com.android.keyguard.LockIconView>
- <com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer
+ <com.android.systemui.shade.NotificationsQuickSettingsContainer
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="@integer/notification_panel_layout_gravity"
@@ -150,11 +150,11 @@
android:text="@string/tap_again"
android:visibility="gone"
/>
- </com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer>
+ </com.android.systemui.shade.NotificationsQuickSettingsContainer>
<FrameLayout
android:id="@+id/preview_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
-</com.android.systemui.statusbar.phone.NotificationPanelView>
+</com.android.systemui.shade.NotificationPanelView>
diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml
index 60860bad9c64..86f8ce26ccab 100644
--- a/packages/SystemUI/res/layout/super_notification_shade.xml
+++ b/packages/SystemUI/res/layout/super_notification_shade.xml
@@ -18,7 +18,7 @@
-->
<!-- This is the notification shade window. -->
-<com.android.systemui.statusbar.phone.NotificationShadeWindowView
+<com.android.systemui.shade.NotificationShadeWindowView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:sysui="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
@@ -114,4 +114,4 @@
android:importantForAccessibility="no"
sysui:ignoreRightInset="true"
/>
-</com.android.systemui.statusbar.phone.NotificationShadeWindowView>
+</com.android.systemui.shade.NotificationShadeWindowView>
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index 0f2d372f7158..c2c79cb0f34b 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -14,7 +14,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<androidx.constraintlayout.widget.ConstraintLayout
+<com.android.systemui.user.UserSwitcherRootView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
@@ -68,4 +68,4 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="48dp" />
-</androidx.constraintlayout.widget.ConstraintLayout>
+</com.android.systemui.user.UserSwitcherRootView>
diff --git a/packages/SystemUI/res/layout/wireless_charging_layout.xml b/packages/SystemUI/res/layout/wireless_charging_layout.xml
index 1312b413f106..887e3e715369 100644
--- a/packages/SystemUI/res/layout/wireless_charging_layout.xml
+++ b/packages/SystemUI/res/layout/wireless_charging_layout.xml
@@ -22,7 +22,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
- <com.android.systemui.statusbar.charging.ChargingRippleView
+ <com.android.systemui.ripple.RippleView
android:id="@+id/wireless_charging_ripple"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
diff --git a/packages/SystemUI/res/values-night/styles.xml b/packages/SystemUI/res/values-night/styles.xml
index cf4972805e25..6f871695c491 100644
--- a/packages/SystemUI/res/values-night/styles.xml
+++ b/packages/SystemUI/res/values-night/styles.xml
@@ -44,7 +44,6 @@
<item name="android:windowLightStatusBar">false</item>
<item name="android:windowLightNavigationBar">false</item>
<item name="android:navigationBarColor">?android:attr/colorBackgroundFloating</item>
- <item name="android:textColorSecondary">?android:attr/textColorPrimaryInverse</item>
</style>
<style name="FloatingOverlay" parent="@android:style/Theme.DeviceDefault.DayNight">
diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
index 6c42073b8805..d638c9d5742e 100644
--- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml
@@ -47,6 +47,8 @@
<dimen name="lockscreen_shade_max_over_scroll_amount">32dp</dimen>
+ <dimen name="status_view_margin_horizontal">8dp</dimen>
+
<!-- Distance that the full shade transition takes in order to complete by tapping on a button
like "expand". -->
<dimen name="lockscreen_shade_transition_by_tap_distance">200dp</dimen>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 589d12f95f45..347cf2965c77 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -21,5 +21,10 @@
<dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
+ <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
+ pages is margin * 2, and that makes tiles page not appear immediately after user swipes to
+ the side -->
+ <dimen name="qs_tiles_page_horizontal_margin">32dp</dimen>
+
<dimen name="qqs_layout_padding_bottom">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
index f45f106d391d..868c003d99a5 100644
--- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml
@@ -25,6 +25,8 @@
<dimen name="status_bar_header_height_keyguard">56dp</dimen>
+ <dimen name="status_view_margin_horizontal">24dp</dimen>
+
<dimen name="qs_media_session_height_expanded">251dp</dimen>
<dimen name="qs_content_horizontal_padding">40dp</dimen>
<dimen name="qs_horizontal_margin">40dp</dimen>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 2abc9e3d6119..a0bf072ed27c 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -25,9 +25,16 @@
<dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
<dimen name="bouncer_user_switcher_y_trans">90dp</dimen>
+ <dimen name="large_screen_shade_header_left_padding">24dp</dimen>
<dimen name="qqs_layout_padding_bottom">40dp</dimen>
<dimen name="notification_panel_margin_horizontal">80dp</dimen>
<dimen name="notification_side_paddings">40dp</dimen>
+
+ <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
+ pages is margin * 2, and that makes tiles page not appear immediately after user swipes to the
+ side -->
+ <dimen name="qs_tiles_page_horizontal_margin">60dp</dimen>
+
<dimen name="notification_section_divider_height">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values-television/config.xml b/packages/SystemUI/res/values-television/config.xml
index 94f6c3952e06..375bd394bb4c 100644
--- a/packages/SystemUI/res/values-television/config.xml
+++ b/packages/SystemUI/res/values-television/config.xml
@@ -22,7 +22,7 @@
<resources>
<!-- SystemUIFactory component -->
<string name="config_systemUIFactoryComponent" translatable="false">
- com.android.systemui.tv.TvSystemUIFactory
+ com.android.systemui.tv.TvSystemUIInitializer
</string>
<!-- Svelte specific logic, see RecentsConfiguration.SVELTE_* constants. -->
diff --git a/packages/SystemUI/res/values/attrs.xml b/packages/SystemUI/res/values/attrs.xml
index 3228c3c9d37f..9a71995383ac 100644
--- a/packages/SystemUI/res/values/attrs.xml
+++ b/packages/SystemUI/res/values/attrs.xml
@@ -108,12 +108,6 @@
<attr name="android:layout" />
</declare-styleable>
- <declare-styleable name="HybridNotificationTheme">
- <attr name="hybridNotificationStyle" format="reference" />
- <attr name="hybridNotificationTitleStyle" format="reference" />
- <attr name="hybridNotificationTextStyle" format="reference" />
- </declare-styleable>
-
<declare-styleable name="PluginInflateContainer">
<attr name="viewType" format="string" />
</declare-styleable>
@@ -193,5 +187,9 @@
<declare-styleable name="DreamOverlayDotImageView">
<attr name="dotColor" format="color" />
</declare-styleable>
+
+ <declare-styleable name="DelayableMarqueeTextView">
+ <attr name="marqueeDelay" format="integer" />
+ </declare-styleable>
</resources>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 02ba271ee9a4..1eece4cee179 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -129,7 +129,6 @@
<color name="smart_reply_button_stroke">@*android:color/accent_device_default</color>
<!-- Biometric dialog colors -->
- <color name="biometric_dialog_dim_color">#80000000</color> <!-- 50% black -->
<color name="biometric_dialog_gray">#ff757575</color>
<color name="biometric_dialog_accent">@color/material_dynamic_primary40</color>
<color name="biometric_dialog_error">#ffd93025</color> <!-- red 600 -->
@@ -233,4 +232,13 @@
<color name="connected_network_secondary_color">#41493D</color>
<color name="dream_overlay_camera_mic_off_dot_color">#FCBE03</color>
+
+ <!-- Air Quality -->
+ <color name="dream_overlay_aqi_good">#689F38</color>
+ <color name="dream_overlay_aqi_moderate">#FBC02D</color>
+ <color name="dream_overlay_aqi_unhealthy_sensitive">#F57C00</color>
+ <color name="dream_overlay_aqi_unhealthy">#C53929</color>
+ <color name="dream_overlay_aqi_very_unhealthy">#AD1457</color>
+ <color name="dream_overlay_aqi_hazardous">#880E4F</color>
+ <color name="dream_overlay_aqi_unknown">#BDC1C6</color>
</resources>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 597e88093de8..82a3b58a5155 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -37,12 +37,6 @@
<item>400</item>
</integer-array>
- <!-- Show mic or phone affordance on Keyguard -->
- <bool name="config_keyguardShowLeftAffordance">false</bool>
-
- <!-- Show camera affordance on Keyguard -->
- <bool name="config_keyguardShowCameraAffordance">false</bool>
-
<!-- decay duration (from size_max -> size), in ms -->
<integer name="navigation_bar_deadzone_hold">333</integer>
<integer name="navigation_bar_deadzone_decay">333</integer>
@@ -87,7 +81,7 @@
<!-- Tiles native to System UI. Order should match "quick_settings_tiles_default" -->
<string name="quick_settings_tiles_stock" translatable="false">
- internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction
+ internet,bt,flashlight,dnd,alarm,airplane,controls,wallet,rotation,battery,cast,screenrecord,mictoggle,cameratoggle,location,hotspot,inversion,saver,dark,work,night,reverse,reduce_brightness,qr_code_scanner,onehanded,color_correction,dream
</string>
<!-- The tiles to display in QuickSettings -->
@@ -292,7 +286,7 @@
<bool name="config_enableFullscreenUserSwitcher">false</bool>
<!-- SystemUIFactory component -->
- <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIFactory</string>
+ <string name="config_systemUIFactoryComponent" translatable="false">com.android.systemui.SystemUIInitializerImpl</string>
<!-- QS tile shape store width. negative implies fill configuration instead of stroke-->
<dimen name="config_qsTileStrokeWidthActive">-1dp</dimen>
@@ -438,6 +432,9 @@
they were added. -->
<integer name="config_smart_replies_in_notifications_onclick_init_delay">200</integer>
+ <!-- Smartspace trampoline activity that is used when the user taps smartspace. -->
+ <string name="config_smartspaceTrampolineActivityComponent" translatable="false">com.google.android.apps.gsa.staticplugins.opa.smartspace.ExportedSmartspaceTrampolineActivity</string>
+
<!-- Screenshot editing default activity. Must handle ACTION_EDIT image/png intents.
Blank sends the user to the Chooser first.
This name is in the ComponentName flattened format (package/class) -->
@@ -629,11 +626,11 @@
<!-- This value is used when calculating whether the device is in ambient light mode. It is
light mode when the light sensor sample value exceeds above this value. -->
- <integer name="config_ambientLightModeThreshold">5</integer>
+ <integer name="config_ambientLightModeThreshold">10</integer>
<!-- This value is used when calculating whether the device is in ambient dark mode. It is
dark mode when the light sensor sample value drops below this value. -->
- <integer name="config_ambientDarkModeThreshold">2</integer>
+ <integer name="config_ambientDarkModeThreshold">5</integer>
<!-- This value is used when calculating whether the device is in ambient light mode. Each
sample contains light sensor events from this span of time duration. -->
@@ -690,14 +687,11 @@
<!-- Flag to enable privacy dot views, it shall be true for normal case -->
<bool name="config_enablePrivacyDot">true</bool>
- <!-- Flag to enable dream overlay service and its registration -->
- <bool name="config_dreamOverlayServiceEnabled">false</bool>
-
<!-- Class for the communal source connector to be used -->
<string name="config_communalSourceConnector" translatable="false"></string>
<!-- How often in milliseconds to jitter the dream overlay in order to avoid burn-in. -->
- <integer name="config_dreamOverlayBurnInProtectionUpdateIntervalMillis">500</integer>
+ <integer name="config_dreamOverlayBurnInProtectionUpdateIntervalMillis">1000</integer>
<!-- How long in milliseconds before full burn-in protection is achieved. -->
<integer name="config_dreamOverlayMillisUntilFullJitter">240000</integer>
@@ -727,4 +721,25 @@
<item>com.android.keyguard</item>
<item>com.android.systemui</item>
</string-array>
+
+ <!-- The thresholds which determine the color used by the AQI dream overlay.
+ NOTE: This must always be kept sorted from low to high -->
+ <integer-array name="config_dreamAqiThresholds">
+ <item>-1</item>
+ <item>50</item>
+ <item>100</item>
+ <item>150</item>
+ <item>200</item>
+ <item>300</item>
+ </integer-array>
+
+ <!-- The color values which correspond to the thresholds above -->
+ <integer-array name="config_dreamAqiColorValues">
+ <item>@color/dream_overlay_aqi_good</item>
+ <item>@color/dream_overlay_aqi_moderate</item>
+ <item>@color/dream_overlay_aqi_unhealthy_sensitive</item>
+ <item>@color/dream_overlay_aqi_unhealthy</item>
+ <item>@color/dream_overlay_aqi_very_unhealthy</item>
+ <item>@color/dream_overlay_aqi_hazardous</item>
+ </integer-array>
</resources>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 160dcd0fbccc..3fb00a33e6b6 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -44,11 +44,53 @@
<!-- The threshold to drag to trigger the edge action -->
<dimen name="navigation_edge_action_drag_threshold">16dp</dimen>
<!-- The threshold to progress back animation for edge swipe -->
- <dimen name="navigation_edge_action_progress_threshold">400dp</dimen>
+ <dimen name="navigation_edge_action_progress_threshold">412dp</dimen>
<!-- The minimum display position of the arrow on the screen -->
<dimen name="navigation_edge_arrow_min_y">64dp</dimen>
<!-- The amount by which the arrow is shifted to avoid the finger-->
- <dimen name="navigation_edge_finger_offset">48dp</dimen>
+ <dimen name="navigation_edge_finger_offset">64dp</dimen>
+
+ <!-- The thickness of the arrow -->
+ <dimen name="navigation_edge_arrow_thickness">4dp</dimen>
+ <!-- The minimum delta needed to change direction / stop triggering back -->
+ <dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen>
+
+ <!-- entry state -->
+ <dimen name="navigation_edge_entry_margin">4dp</dimen>
+ <dimen name="navigation_edge_entry_background_width">8dp</dimen>
+ <dimen name="navigation_edge_entry_background_height">60dp</dimen>
+ <dimen name="navigation_edge_entry_edge_corners">30dp</dimen>
+ <dimen name="navigation_edge_entry_far_corners">30dp</dimen>
+ <dimen name="navigation_edge_entry_arrow_length">10dp</dimen>
+ <dimen name="navigation_edge_entry_arrow_height">7dp</dimen>
+
+ <!-- pre-threshold -->
+ <dimen name="navigation_edge_pre_threshold_margin">4dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_background_width">64dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_background_height">60dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_edge_corners">22dp</dimen>
+ <dimen name="navigation_edge_pre_threshold_far_corners">26dp</dimen>
+
+ <!-- post-threshold / active -->
+ <dimen name="navigation_edge_active_margin">14dp</dimen>
+ <dimen name="navigation_edge_active_background_width">60dp</dimen>
+ <dimen name="navigation_edge_active_background_height">60dp</dimen>
+ <dimen name="navigation_edge_active_edge_corners">30dp</dimen>
+ <dimen name="navigation_edge_active_far_corners">30dp</dimen>
+ <dimen name="navigation_edge_active_arrow_length">8dp</dimen>
+ <dimen name="navigation_edge_active_arrow_height">9dp</dimen>
+
+ <dimen name="navigation_edge_stretch_margin">18dp</dimen>
+ <dimen name="navigation_edge_stretch_background_width">74dp</dimen>
+ <dimen name="navigation_edge_stretch_background_height">60dp</dimen>
+ <dimen name="navigation_edge_stretch_edge_corners">30dp</dimen>
+ <dimen name="navigation_edge_stretch_far_corners">30dp</dimen>
+ <dimen name="navigation_edge_stretched_arrow_length">7dp</dimen>
+ <dimen name="navigation_edge_stretched_arrow_height">10dp</dimen>
+
+ <dimen name="navigation_edge_cancelled_arrow_length">12dp</dimen>
+ <dimen name="navigation_edge_cancelled_arrow_height">0dp</dimen>
+ <dimen name="navigation_edge_cancelled_edge_corners">6dp</dimen>
<!-- Height of notification icons in the status bar -->
<dimen name="status_bar_icon_size">@*android:dimen/status_bar_icon_size</dimen>
@@ -366,6 +408,7 @@
<!-- Height of status bar in split shade mode - visible only on large screens -->
<dimen name="large_screen_shade_header_height">@*android:dimen/quick_qs_offset_height</dimen>
<dimen name="large_screen_shade_header_min_height">@dimen/qs_header_row_min_height</dimen>
+ <dimen name="large_screen_shade_header_left_padding">@dimen/qs_horizontal_margin</dimen>
<!-- The top margin of the panel that holds the list of notifications.
On phones it's always 0dp but it's overridden in Car UI
@@ -452,7 +495,7 @@
<dimen name="navigation_key_padding">0dp</dimen>
<!-- Floating rotation button -->
- <dimen name="floating_rotation_button_diameter">40dp</dimen>
+ <dimen name="floating_rotation_button_diameter">52dp</dimen>
<dimen name="floating_rotation_button_min_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_left_margin">20dp</dimen>
<dimen name="floating_rotation_button_taskbar_bottom_margin">10dp</dimen>
@@ -511,7 +554,7 @@
<dimen name="qs_dual_tile_padding_horizontal">6dp</dimen>
<dimen name="qs_panel_elevation">4dp</dimen>
<dimen name="qs_panel_padding_bottom">@dimen/footer_actions_height</dimen>
- <dimen name="qs_panel_padding_top">48dp</dimen>
+ <dimen name="qs_panel_padding_top">80dp</dimen>
<dimen name="qs_data_usage_text_size">14sp</dimen>
<dimen name="qs_data_usage_usage_text_size">36sp</dimen>
@@ -522,6 +565,8 @@
<dimen name="qs_footer_icon_size">20dp</dimen>
<dimen name="qs_header_row_min_height">48dp</dimen>
+ <dimen name="qs_header_non_clickable_element_height">24dp</dimen>
+
<dimen name="qs_footer_padding">20dp</dimen>
<dimen name="qs_security_footer_height">88dp</dimen>
<dimen name="qs_security_footer_single_line_height">48dp</dimen>
@@ -621,6 +666,7 @@
<!-- With the large clock, move up slightly from the center -->
<dimen name="keyguard_large_clock_top_margin">-60dp</dimen>
+ <!-- TODO: Remove during migration -->
<!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
<item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
<!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
@@ -634,9 +680,6 @@
<!-- The minimum background radius when swiping to a side for the camera / phone affordances. -->
<dimen name="keyguard_affordance_min_background_radius">30dp</dimen>
- <!-- The size of the touch targets on the keyguard for the affordances. -->
- <dimen name="keyguard_affordance_touch_target_size">120dp</dimen>
-
<!-- The grow amount for the camera and phone circles when hinting -->
<dimen name="hint_grow_amount_sideways">60dp</dimen>
@@ -848,6 +891,7 @@
<dimen name="burn_in_prevention_offset_y_clock">42dp</dimen>
<!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <!-- TODO: Remove when clock migration complete -->
<dimen name="large_clock_text_size">150dp</dimen>
<dimen name="clock_text_size">86dp</dimen>
@@ -1018,6 +1062,7 @@
<!-- Since the generic icon isn't circular, we need to scale it down so it still fits within
the circular chip. -->
<dimen name="media_ttt_generic_icon_size_receiver">70dp</dimen>
+ <dimen name="media_ttt_receiver_vert_translation">20dp</dimen>
<!-- Window magnification -->
<dimen name="magnification_border_drag_size">35dp</dimen>
@@ -1204,6 +1249,15 @@
<!-- Maximum overshoot for the pulse expansion -->
<dimen name="pulse_expansion_max_top_overshoot">32dp</dimen>
+ <!-- The drag amount required for the split shade to fully expand. -->
+ <dimen name="split_shade_full_transition_distance">200dp</dimen>
+ <!--
+ The drag amount required for the scrim to fully fade in when expanding the split shade.
+ Currently setting it a little longer than the full shade transition distance, to avoid
+ having a state where the screen is fully black without any content showing.
+ -->
+ <dimen name="split_shade_scrim_transition_distance">300dp</dimen>
+
<!-- Alpha in duration in ms for the auth ripple to become fully vislble. If set to 0,
it is immediately visible. -->
<integer name="auth_ripple_alpha_in_duration">100</integer>
@@ -1400,6 +1454,7 @@
@*android:dimen/status_bar_system_icon_size</dimen>
<dimen name="dream_overlay_camera_mic_off_indicator_size">8dp</dimen>
<dimen name="dream_overlay_notification_indicator_size">6dp</dimen>
+ <dimen name="dream_overlay_grey_chip_width">56dp</dimen>
<!-- Dream overlay complications related dimensions -->
<dimen name="dream_overlay_complication_clock_time_text_size">100sp</dimen>
@@ -1452,6 +1507,10 @@
<dimen name="dream_overlay_y_offset">80dp</dimen>
+ <dimen name="dream_aqi_badge_corner_radius">28dp</dimen>
+ <dimen name="dream_aqi_badge_padding_vertical">6dp</dimen>
+ <dimen name="dream_aqi_badge_padding_horizontal">16dp</dimen>
+
<dimen name="status_view_margin_horizontal">0dp</dimen>
<!-- Media output broadcast dialog QR code picture size -->
@@ -1464,4 +1523,18 @@
<dimen name="media_output_broadcast_info_title_height">24dp</dimen>
<dimen name="media_output_broadcast_info_summary_height">20dp</dimen>
<dimen name="media_output_broadcast_info_edit">18dp</dimen>
+
+ <!-- Broadcast dialog -->
+ <dimen name="broadcast_dialog_title_img_margin_top">18dp</dimen>
+ <dimen name="broadcast_dialog_title_text_size">24sp</dimen>
+ <dimen name="broadcast_dialog_title_text_margin">16dp</dimen>
+ <dimen name="broadcast_dialog_title_text_margin_top">18dp</dimen>
+ <dimen name="broadcast_dialog_subtitle_text_size">14sp</dimen>
+ <dimen name="broadcast_dialog_icon_size">24dp</dimen>
+ <dimen name="broadcast_dialog_icon_margin_top">25dp</dimen>
+ <dimen name="broadcast_dialog_btn_radius">100dp</dimen>
+ <dimen name="broadcast_dialog_btn_margin_bottom">4dp</dimen>
+ <dimen name="broadcast_dialog_btn_text_size">16sp</dimen>
+ <dimen name="broadcast_dialog_btn_minHeight">44dp</dimen>
+ <dimen name="broadcast_dialog_margin">16dp</dimen>
</resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index e144b43294c6..9c2542cbd05f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -313,13 +313,6 @@
<string name="accessibility_scanning_face">Scanning face</string>
<!-- Click action label for accessibility for the smart reply buttons (not shown on-screen).". [CHAR LIMIT=NONE] -->
<string name="accessibility_send_smart_reply">Send</string>
- <!-- Content description of the manage notification button for accessibility (not shown on the screen). [CHAR LIMIT=NONE] -->
- <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
- <string name="phone_label">open phone</string>
- <!-- Click action label for accessibility for the voice assist button. This is not shown on-screen and is an accessibility label for the icon which launches the voice assist from the lock screen.[CHAR LIMIT=NONE] -->
- <string name="voice_assist_label">open voice assist</string>
- <!-- Click action label for accessibility for the phone button. [CHAR LIMIT=NONE] -->
- <string name="camera_label">open camera</string>
<!-- Button name for "Cancel". [CHAR LIMIT=NONE] -->
<string name="cancel">Cancel</string>
@@ -505,10 +498,10 @@
<string name="notification_group_overflow_indicator">+ <xliff:g id="number" example="3">%s</xliff:g></string>
<!-- Content description describing how many more notifications are in a group [CHAR LIMIT=NONE] -->
- <plurals name="notification_group_overflow_description">
- <item quantity="one"><xliff:g id="number" example="1">%s</xliff:g> more notification inside.</item>
- <item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> more notifications inside.</item>
- </plurals>
+ <string name="notification_group_overflow_description">{count, plural,
+ =1 {# more notification inside.}
+ other {# more notifications inside.}
+ }</string>
<!-- Format to use to summarize a message from a contact in a single line of text. For example: "Julia: How's it going?". [CHAR LIMIT=NONE] -->
@@ -578,6 +571,8 @@
<!-- QuickSettings: Location [CHAR LIMIT=NONE] -->
<string name="quick_settings_location_label">Location</string>
<!-- QuickSettings: Location (Off) [CHAR LIMIT=NONE] -->
+ <!-- QuickSettings: Screen saver [CHAR LIMIT=NONE] -->
+ <string name="quick_settings_screensaver_label">Screen saver</string>
<!-- QuickSettings: Camera [CHAR LIMIT=NONE] -->
<string name="quick_settings_camera_label">Camera access</string>
<!-- QuickSettings: Microphone [CHAR LIMIT=NONE] -->
@@ -652,10 +647,10 @@
the user why they can't toggle the hotspot tile. [CHAR LIMIT=20] -->
<string name="quick_settings_hotspot_secondary_label_data_saver_enabled">Data Saver is on</string>
<!-- QuickSettings: Hotspot: Secondary label for how many devices are connected to the hotspot [CHAR LIMIT=NONE] -->
- <plurals name="quick_settings_hotspot_secondary_label_num_devices">
- <item quantity="one">%d device</item>
- <item quantity="other">%d devices</item>
- </plurals>
+ <string name="quick_settings_hotspot_secondary_label_num_devices">{count, plural,
+ =1 {# device}
+ other {# devices}
+ }</string>
<!-- QuickSettings: Notifications [CHAR LIMIT=NONE] -->
<!-- QuickSettings: Flashlight [CHAR LIMIT=NONE] -->
<string name="quick_settings_flashlight_label">Flashlight</string>
@@ -807,6 +802,8 @@
<!-- Message shown when lock screen is unlocked (ie: by trust agent) and the user taps the empty space on the lock screen and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
<string name="keyguard_unlock_press">Press the unlock icon to open</string>
+ <!-- Message shown when non-bypass face authentication succeeds. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
+ <string name="keyguard_face_successful_unlock_swipe">Unlocked by face. Swipe up to open.</string>
<!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
<string name="keyguard_face_successful_unlock_press">Unlocked by face. Press the unlock icon to open.</string>
<!-- Message shown when non-bypass face authentication succeeds and UDFPS is supported. Provides extra instructions for how the user can enter their device [CHAR LIMIT=60] -->
@@ -911,14 +908,25 @@
<!-- Notification when resuming an existing guest session: Action that continues with the current session [CHAR LIMIT=35] -->
<string name="guest_wipe_session_dontwipe">Yes, continue</string>
+ <!-- App name of the notification when guest mode is entered [CHAR LIMIT=35] -->
+ <string name="guest_notification_app_name">Guest mode</string>
+ <!-- Title of the notification when guest mode is entered [CHAR LIMIT=35] -->
+ <string name="guest_notification_session_active">You are in guest mode</string>
+
+ <!-- Additional message for add user confirmation dialog that is appended when current user is
+ guest and guest is ephemeral. This is to warn users that current guest session
+ would get removed after a new user is added and switched to [CHAR LIMIT=none] -->
+ <string name="user_add_user_message_guest_remove">\n\nAdding a new user will exit guest mode
+ and delete all apps and data from the current guest session.</string>
+
<!-- Title for the dialog that lets users know that the maximum allowed number of users on the device has been reached. [CHAR LIMIT=35]-->
<string name="user_limit_reached_title">User limit reached</string>
<!-- Message that tells people what's the maximum number of uses allowed on the device. [CHAR_LIMIT=NONE]-->
- <plurals name="user_limit_reached_message">
- <item quantity="one">Only one user can be created.</item>
- <item quantity="other">You can add up to <xliff:g id="count" example="3">%d</xliff:g> users.</item>
- </plurals>
+ <string name="user_limit_reached_message">{count, plural,
+ =1 {Only one user can be created.}
+ other {You can add up to # users.}
+ }</string>
<!-- Title of the confirmation dialog for deleting a user [CHAR LIMIT=NONE] -->
<string name="user_remove_user_title">Remove user?</string>
@@ -1473,20 +1481,20 @@
<!-- Notification: Snooze panel: message indicating how long the notification was snoozed for. [CHAR LIMIT=100]-->
<string name="snoozed_for_time">Snoozed for <xliff:g id="time_amount" example="15 minutes">%1$s</xliff:g></string>
- <!-- Notification:Snooze panel: Snooze options for hours -->
- <plurals name="snoozeHourOptions">
- <item quantity="one">%d hour</item>
- <item quantity="two">%d hours</item>
- <item quantity="few">%d hours</item>
- <item quantity="other">%d hours</item>
- </plurals>
-
- <!-- Notification: Snooze panel: Snooze options for minutes -->
- <plurals name="snoozeMinuteOptions">
- <item quantity="one">%d minute</item>
- <item quantity="few">%d minutes</item>
- <item quantity="other">%d minutes</item>
- </plurals>
+ <!-- Notification:Snooze panel: Snooze options for hours [CHAR LIMIT=NONE]-->
+ <string name="snoozeHourOptions">{count, plural,
+ =1 {# hour}
+ =2 {# hours}
+ few {# hours}
+ other {# hours}
+ }</string>
+
+ <!-- Notification: Snooze panel: Snooze options for minutes [CHAR LIMIT=NONE]-->
+ <string name="snoozeMinuteOptions">{count, plural,
+ =1 {# minute}
+ few {# minutes}
+ other {# minutes}
+ }</string>
<!-- Summary of battery saver not available [CHAR LIMIT=NONE] -->
@@ -1896,8 +1904,10 @@
<string name="notification_channel_battery">Battery</string>
<!-- Title for the notification channel dedicated to screenshot progress. [CHAR LIMIT=NONE] -->
<string name="notification_channel_screenshot">Screenshots</string>
- <!-- Title for the notification channel for miscellaneous notices. [CHAR LIMIT=NONE] -->
- <string name="notification_channel_general">General Messages</string>
+ <!-- Title for the notification channel for instant app notices. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_instant">Instant Apps</string>
+ <!-- Title for the notification channel for setup notices. [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_setup">Setup</string>
<!-- Title for the notification channel for problems with storage (i.e. low disk). [CHAR LIMIT=NONE] -->
<string name="notification_channel_storage">Storage</string>
<!-- Title for the notification channel for hints and suggestions. [CHAR LIMIT=NONE] -->
@@ -2121,10 +2131,10 @@
<!-- Controls management providers screen title [CHAR LIMIT=60]-->
<string name="controls_providers_title">Choose app to add controls</string>
<!-- Number of favorites for controls management screen [CHAR LIMIT=NONE]-->
- <plurals name="controls_number_of_favorites">
- <item quantity="one"><xliff:g id="number" example="1">%s</xliff:g> control added.</item>
- <item quantity="other"><xliff:g id="number" example="3">%s</xliff:g> controls added.</item>
- </plurals>
+ <string name="controls_number_of_favorites">{count, plural,
+ =1 {# control added.}
+ other {# controls added.}
+ }</string>
<!-- Removed control in management screen [CHAR LIMIT=20] -->
<string name="controls_removed">Removed</string>
@@ -2287,8 +2297,8 @@
<string name="media_output_dialog_disconnected">(disconnected)</string>
<!-- Summary for connecting error message [CHAR LIMIT=NONE] -->
<string name="media_output_dialog_connect_failed">Can\'t switch. Tap to try again.</string>
- <!-- Title for pairing item [CHAR LIMIT=60] -->
- <string name="media_output_dialog_pairing_new">Pair new device</string>
+ <!-- Title for connecting item [CHAR LIMIT=60] -->
+ <string name="media_output_dialog_pairing_new">Connect a device</string>
<!-- Title for launch app [CHAR LIMIT=60] -->
<string name="media_output_dialog_launch_app_text">To cast this session, please open the app.</string>
<!-- App name when can't get app name [CHAR LIMIT=60] -->
@@ -2480,10 +2490,10 @@
<string name="qs_user_switch_dialog_title">Select user</string>
<!-- Label for the entry point to open the dialog which shows currently running applications [CHAR LIMIT=NONE]-->
- <plurals name="fgs_manager_footer_label">
- <item quantity="one"><xliff:g id="count" example="1">%s</xliff:g> app is active</item>
- <item quantity="other"><xliff:g id="count" example="2">%s</xliff:g> apps are active</item>
- </plurals>
+ <string name="fgs_manager_footer_label">{count, plural,
+ =1 {# app is active}
+ other {# apps are active}
+ }</string>
<!-- Content description for a dot indicator in the running application indicating that there
is new information [CHAR LIMIT=NONE] -->
<string name="fgs_dot_content_description">New information</string>
@@ -2545,6 +2555,10 @@
<string name="dream_overlay_status_bar_priority_mode">Priority mode</string>
<!-- Content description for the alarm set icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_alarm_set">Alarm set</string>
+ <!-- Content description for the camera off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_camera_off">Camera is off</string>
+ <!-- Content description for the mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
+ <string name="dream_overlay_status_bar_mic_off">Mic is off</string>
<!-- Content description for the camera and mic off icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
<string name="dream_overlay_status_bar_camera_mic_off">Camera and mic are off</string>
<!-- Content description for the notifications indicator icon in the dream overlay status bar [CHAR LIMIT=NONE] -->
@@ -2552,4 +2566,30 @@
=1 {# notification}
other {# notifications}
}</string>
+ <!-- Accessibility label for weather complication on dreams with weather condition and temperature [CHAR_LIMIT=200] -->
+ <string name="dream_overlay_weather_complication_desc">
+ <xliff:g id="weather_condition" example="Partly cloudy">%1$s</xliff:g>, <xliff:g id="temperature" example="7°C">%2$s</xliff:g>
+ </string>
+
+ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is broadcasting -->
+ <string name="broadcasting_description_is_broadcasting">Broadcasting</string>
+ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, title -->
+ <string name="bt_le_audio_broadcast_dialog_title">Stop broadcasting <xliff:g id="app_name" example="App Name 1">%1$s</xliff:g>?</string>
+ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, sub-title -->
+ <string name="bt_le_audio_broadcast_dialog_sub_title">If you broadcast <xliff:g id="switchApp" example="App Name 2">%1$s</xliff:g> or change the output, your current broadcast will stop</string>
+ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, switch to others app. -->
+ <string name="bt_le_audio_broadcast_dialog_switch_app">Broadcast <xliff:g id="switchApp" example="App Name 2">%1$s</xliff:g></string>
+ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, different output. -->
+ <string name="bt_le_audio_broadcast_dialog_different_output">Change output</string>
+ <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, media app is unknown -->
+ <string name="bt_le_audio_broadcast_dialog_unknown_name">Unknown</string>
+
+ <!-- Date format for the Dream Date Complication [CHAR LIMIT=NONE] -->
+ <string name="dream_date_complication_date_format">EEE, MMM d</string>
+
+ <!-- Time format for the Dream Time Complication for 12-hour time format [CHAR LIMIT=NONE] -->
+ <string name="dream_time_complication_12_hr_time_format">h:mm</string>
+
+ <!-- Time format for the Dream Time Complication for 24-hour time format [CHAR LIMIT=NONE] -->
+ <string name="dream_time_complication_24_hr_time_format">kk:mm</string>
</resources>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 0c25f5473416..112d903c609c 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -17,30 +17,6 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android">
- <!-- HybridNotification themes and styles -->
-
- <style name="HybridNotification">
- <item name="hybridNotificationStyle">@style/hybrid_notification</item>
- <item name="hybridNotificationTitleStyle">@style/hybrid_notification_title</item>
- <item name="hybridNotificationTextStyle">@style/hybrid_notification_text</item>
- </style>
-
- <style name="hybrid_notification">
- <item name="android:paddingStart">@*android:dimen/notification_content_margin_start</item>
- <item name="android:paddingEnd">12dp</item>
- </style>
-
- <style name="hybrid_notification_title">
- <item name="android:paddingEnd">4dp</item>
- <item name="android:textAppearance">@*android:style/TextAppearance.DeviceDefault.Notification.Title</item>
- </style>
-
- <style name="hybrid_notification_text"
- parent="@*android:style/Widget.DeviceDefault.Notification.Text">
- <item name="android:paddingEnd">4dp</item>
- </style>
-
-
<style name="TextAppearance.StatusBar.Clock" parent="@*android:style/TextAppearance.StatusBar.Icon">
<item name="android:textSize">@dimen/status_bar_clock_size</item>
<item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item>
@@ -289,6 +265,10 @@
<style name="Animation.ShutdownUi" parent="@android:style/Animation.Toast">
</style>
+ <style name="Theme.SystemUI.MediaProjectionAppSelector"
+ parent="@*android:style/Theme.DeviceDefault.Chooser">
+ </style>
+
<!-- Standard animations for hiding and showing the status bar. -->
<style name="Theme.SystemUI" parent="@*android:style/Theme.DeviceDefault.SystemUI">
@@ -1196,4 +1176,48 @@
<item name="android:shadowColor">@color/keyguard_shadow_color</item>
<item name="android:shadowRadius">?attr/shadowRadius</item>
</style>
+
+ <style name="BroadcastDialogTitleStyle">
+ <item name="android:textAppearance">@style/TextAppearanceBroadcastDialogTitle</item>
+ <item name="android:layout_marginStart">@dimen/broadcast_dialog_title_text_margin</item>
+ <item name="android:layout_marginEnd">@dimen/broadcast_dialog_title_text_margin</item>
+ <item name="android:layout_marginTop">@dimen/broadcast_dialog_title_text_margin_top</item>
+ <item name="android:layout_marginBottom">18dp</item>
+ </style>
+
+ <style name="TextAppearanceBroadcastDialogTitle" parent="@android:style/TextAppearance.DeviceDefault.Headline">
+ <item name="android:textSize">@dimen/broadcast_dialog_title_text_size</item>
+ <item name="android:textColor">?android:attr/textColorPrimary</item>
+ <item name="android:textDirection">locale</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="BroadcastDialogBodyStyle">
+ <item name="android:textAppearance">@style/TextAppearanceBroadcastDialogSubTitle</item>
+ <item name="android:layout_margin">@dimen/broadcast_dialog_title_text_margin</item>
+ </style>
+
+ <style name="TextAppearanceBroadcastDialogSubTitle" parent="@android:style/TextAppearance.DeviceDefault.Headline">
+ <item name="android:textSize">@dimen/broadcast_dialog_subtitle_text_size</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ <item name="android:textDirection">locale</item>
+ <item name="android:ellipsize">end</item>
+ </style>
+
+ <style name="BroadcastDialogButtonStyle">
+ <item name="android:textAppearance">@style/TextAppearanceBroadcastDialogButton</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_gravity">center</item>
+ <item name="android:gravity">center</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:elevation">0dp</item>
+ <item name="android:minHeight">@dimen/broadcast_dialog_btn_minHeight</item>
+ <item name="android:background">@drawable/broadcast_dialog_btn_bg</item>
+ </style>
+
+ <style name="TextAppearanceBroadcastDialogButton" parent="@android:style/TextAppearance.DeviceDefault.Headline">
+ <item name="android:textColor">?androidprv:attr/textColorOnAccent</item>
+ <item name="android:textSize">@dimen/broadcast_dialog_btn_text_size</item>
+ </style>
</resources>
diff --git a/packages/SystemUI/res/values/tiles_states_strings.xml b/packages/SystemUI/res/values/tiles_states_strings.xml
index e2734164161f..c8095510e2c2 100644
--- a/packages/SystemUI/res/values/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values/tiles_states_strings.xml
@@ -308,4 +308,14 @@
<item>Off</item>
<item>On</item>
</string-array>
+
+ <!-- State names for dream (screensaver) tile: unavailable, off, on.
+ This subtitle is shown when the tile is in that particular state but does not set its own
+ subtitle, so some of these may never appear on screen. They should still be translated as
+ if they could appear. [CHAR LIMIT=32] -->
+ <string-array name="tile_states_dream">
+ <item>Unavailable</item>
+ <item>Off</item>
+ <item>On</item>
+ </string-array>
</resources> \ No newline at end of file
diff --git a/packages/SystemUI/res/xml/combined_qs_header_scene.xml b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
index 0e833265c15f..0fac76d11fbc 100644
--- a/packages/SystemUI/res/xml/combined_qs_header_scene.xml
+++ b/packages/SystemUI/res/xml/combined_qs_header_scene.xml
@@ -25,20 +25,109 @@
<KeyFrameSet>
<!-- These positions are to prevent visual movement of @id/date -->
<KeyPosition
- app:keyPositionType="pathRelative"
+ app:keyPositionType="deltaRelative"
app:percentX="0"
+ app:percentY="0"
app:framePosition="49"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyPosition
- app:keyPositionType="pathRelative"
+ app:keyPositionType="deltaRelative"
app:percentX="1"
+ app:percentY="0.51"
app:framePosition="51"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
app:motionTarget="@id/date" />
<KeyAttribute
app:motionTarget="@id/date"
+ app:framePosition="30"
+ android:alpha="0"
+ />
+ <KeyAttribute
+ app:motionTarget="@id/date"
+ app:framePosition="70"
+ android:alpha="0"
+ />
+ <KeyPosition
+ app:keyPositionType="pathRelative"
+ app:percentX="0"
+ app:percentY="0"
+ app:framePosition="0"
+ app:curveFit="linear"
+ app:motionTarget="@id/statusIcons" />
+ <KeyPosition
+ app:keyPositionType="pathRelative"
+ app:percentX="0"
+ app:percentY="0"
+ app:framePosition="50"
+ app:curveFit="linear"
+ app:motionTarget="@id/statusIcons" />
+ <KeyPosition
+ app:keyPositionType="deltaRelative"
+ app:percentX="1"
+ app:percentY="0.51"
+ app:framePosition="51"
+ app:curveFit="linear"
+ app:motionTarget="@id/statusIcons" />
+ <KeyAttribute
+ app:motionTarget="@id/statusIcons"
+ app:framePosition="30"
+ android:alpha="0"
+ />
+ <KeyAttribute
+ app:motionTarget="@id/statusIcons"
+ app:framePosition="70"
+ android:alpha="0"
+ />
+ <KeyPosition
+ app:keyPositionType="deltaRelative"
+ app:percentX="0"
+ app:percentY="0"
app:framePosition="50"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
+ app:motionTarget="@id/batteryRemainingIcon" />
+ <KeyPosition
+ app:keyPositionType="deltaRelative"
+ app:percentX="1"
+ app:percentY="0.51"
+ app:framePosition="51"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
+ app:motionTarget="@id/batteryRemainingIcon" />
+ <KeyAttribute
+ app:motionTarget="@id/batteryRemainingIcon"
+ app:framePosition="30"
android:alpha="0"
/>
+ <KeyAttribute
+ app:motionTarget="@id/batteryRemainingIcon"
+ app:framePosition="70"
+ android:alpha="0"
+ />
+ <KeyPosition
+ app:motionTarget="@id/carrier_group"
+ app:percentX="1"
+ app:percentY="0.51"
+ app:framePosition="51"
+ app:percentWidth="1"
+ app:percentHeight="1"
+ app:curveFit="linear"
+ app:keyPositionType="deltaRelative" />
+ <KeyAttribute
+ app:motionTarget="@id/carrier_group"
+ app:framePosition="0"
+ android:alpha="0" />
+ <KeyAttribute
+ app:motionTarget="@id/carrier_group"
+ app:framePosition="70"
+ android:alpha="0" />
</KeyFrameSet>
</Transition>
diff --git a/packages/SystemUI/res/xml/large_screen_shade_header.xml b/packages/SystemUI/res/xml/large_screen_shade_header.xml
index 89090513ea37..cdbf8ab0be41 100644
--- a/packages/SystemUI/res/xml/large_screen_shade_header.xml
+++ b/packages/SystemUI/res/xml/large_screen_shade_header.xml
@@ -29,7 +29,12 @@
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date"
+ app:layout_constraintHorizontal_bias="0"
/>
+ <Transform
+ android:scaleX="1"
+ android:scaleY="1"
+ />
</Constraint>
<Constraint
@@ -47,9 +52,38 @@
<Constraint
android:id="@+id/carrier_group">
+ <Layout
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constrainedWidth="true"
+ android:layout_gravity="end|center_vertical"
+ android:layout_marginStart="8dp"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ app:layout_constraintTop_toTopOf="@id/clock"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ <PropertySet
+ android:alpha="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/statusIcons">
+ <Layout
+ app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintStart_toEndOf="@id/carrier_group"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/clock"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
<PropertySet
android:alpha="1"
- app:customFloatValue="1"
/>
</Constraint>
@@ -64,6 +98,9 @@
app:layout_constraintTop_toTopOf="@id/clock"
app:layout_constraintBottom_toBottomOf="parent"
/>
+ <PropertySet
+ android:alpha="1"
+ />
</Constraint>
<Constraint
@@ -75,6 +112,7 @@
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
app:layout_constraintStart_toEndOf="@id/batteryRemainingIcon"
+ app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index eab7defe1e52..9115d42fc42d 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -19,7 +19,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto">
<Constraint
- android:id="@+id/media_action_barrier"
+ android:id="@+id/media_action_barrier_start"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
@@ -91,12 +91,16 @@
app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/media_seamless"
- app:layout_constraintLeft_toRightOf="@id/media_action_barrier" />
+ app:layout_constraintLeft_toRightOf="@id/media_action_barrier_start" />
<!-- Showing time while scrubbing isn't available in collapsed mode. -->
<Constraint
android:id="@+id/media_scrubbing_elapsed_time"
- android:visibility="gone" />
+ android:visibility="gone"
+ app:layout_constraintRight_toLeftOf="@id/media_progress_bar"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless"
+ app:layout_constraintLeft_toRightOf="@id/media_action_barrier_start" />
<Constraint
android:id="@+id/media_progress_bar"
@@ -124,7 +128,12 @@
<!-- Showing time while scrubbing isn't available in collapsed mode. -->
<Constraint
android:id="@+id/media_scrubbing_total_time"
- android:visibility="gone" />
+ android:visibility="gone"
+ app:layout_constraintVertical_bias="1"
+ app:layout_constraintLeft_toRightOf="@id/media_progress_bar"
+ app:layout_constraintRight_toLeftOf="@id/action0"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/media_seamless" />
<Constraint
android:id="@+id/action0"
diff --git a/packages/SystemUI/res/xml/qqs_header.xml b/packages/SystemUI/res/xml/qqs_header.xml
index c5b4c5d776b9..ee0c4fb6bab8 100644
--- a/packages/SystemUI/res/xml/qqs_header.xml
+++ b/packages/SystemUI/res/xml/qqs_header.xml
@@ -26,22 +26,27 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
- app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintStart_toStartOf="@id/begin_guide"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/date"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
/>
+ <Transform
+ android:scaleX="1"
+ android:scaleY="1"
+ />
</Constraint>
<Constraint
android:id="@+id/date">
<Layout
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="0dp"
+ app:layout_constrainedWidth="true"
app:layout_constraintStart_toEndOf="@id/clock"
- app:layout_constraintEnd_toStartOf="@id/carrier_group"
+ app:layout_constraintEnd_toStartOf="@id/barrier"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0"
@@ -50,17 +55,41 @@
<Constraint
android:id="@+id/statusIcons">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
</Constraint>
<Constraint
- android:id="@+id/batteryRemainingIcon" >
+ android:id="@+id/batteryRemainingIcon">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/statusIcons"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
</Constraint>
<Constraint
android:id="@+id/carrier_group">
- <CustomAttribute
- app:attributeName="alpha"
- app:customFloatValue="0"
+ <Layout
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ />
+ <PropertySet
+ android:alpha="0"
/>
</Constraint>
@@ -69,9 +98,11 @@
<Layout
android:layout_width="wrap_content"
android:layout_height="0dp"
- app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
app:layout_constraintTop_toTopOf="@id/date"
app:layout_constraintBottom_toBottomOf="@id/date"
+ app:layout_constraintHorizontal_bias="1"
/>
</Constraint>
diff --git a/packages/SystemUI/res/xml/qs_header_new.xml b/packages/SystemUI/res/xml/qs_header_new.xml
new file mode 100644
index 000000000000..f39e6bd65b86
--- /dev/null
+++ b/packages/SystemUI/res/xml/qs_header_new.xml
@@ -0,0 +1,124 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2021 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<ConstraintSet
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/qs_header_constraint"
+>
+
+ <Constraint
+ android:id="@+id/privacy_container">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintEnd_toEndOf="@id/end_guide"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintBottom_toTopOf="@id/carrier_group"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/clock">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintBottom_toTopOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/carrier_group"
+ app:layout_constraintHorizontal_bias="0"
+ />
+ <Transform
+ android:scaleX="2.4"
+ android:scaleY="2.4"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/date">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/space"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintTop_toBottomOf="@id/clock"
+ app:layout_constraintHorizontal_bias="0"
+ app:layout_constraintHorizontal_chainStyle="spread_inside"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/carrier_group">
+ <Layout
+ app:layout_constraintHeight_min="@dimen/large_screen_shade_header_min_height"
+ android:minHeight="@dimen/large_screen_shade_header_min_height"
+ app:layout_constraintWidth_min="48dp"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toEndOf="@id/clock"
+ app:layout_constraintTop_toBottomOf="@id/privacy_container"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ app:layout_constraintBottom_toTopOf="@id/batteryRemainingIcon"
+ />
+ <PropertySet
+ android:alpha="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/statusIcons">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constrainedWidth="true"
+ app:layout_constraintStart_toEndOf="@id/space"
+ app:layout_constraintEnd_toStartOf="@id/batteryRemainingIcon"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+ <Constraint
+ android:id="@+id/batteryRemainingIcon">
+ <Layout
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintHeight_min="@dimen/qs_header_non_clickable_element_height"
+ app:layout_constraintStart_toEndOf="@id/statusIcons"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintTop_toTopOf="@id/date"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintHorizontal_bias="1"
+ />
+ </Constraint>
+
+
+ <Constraint
+ android:id="@id/space">
+ <Layout
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ app:layout_constraintStart_toEndOf="@id/date"
+ app:layout_constraintEnd_toStartOf="@id/statusIcons"
+ />
+ </Constraint>
+</ConstraintSet> \ No newline at end of file
diff --git a/packages/SystemUI/screenshot/Android.bp b/packages/SystemUI/screenshot/Android.bp
new file mode 100644
index 000000000000..f449398fc9f8
--- /dev/null
+++ b/packages/SystemUI/screenshot/Android.bp
@@ -0,0 +1,45 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIScreenshotLib",
+ manifest: "AndroidManifest.xml",
+
+ srcs: [
+ "src/**/*.kt",
+ ],
+
+ resource_dirs: [
+ "res",
+ ],
+
+ static_libs: [
+ "SystemUI-core",
+ "androidx.test.espresso.core",
+ "androidx.appcompat_appcompat",
+ "platform-screenshot-diff-core",
+ "guava",
+ ],
+
+ kotlincflags: ["-Xjvm-default=all"],
+}
diff --git a/packages/SystemUI/screenshot/AndroidManifest.xml b/packages/SystemUI/screenshot/AndroidManifest.xml
new file mode 100644
index 000000000000..a405836bd77f
--- /dev/null
+++ b/packages/SystemUI/screenshot/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.testing.screenshot">
+ <application>
+ <activity
+ android:name="com.android.systemui.testing.screenshot.ScreenshotActivity"
+ android:exported="true"
+ android:theme="@style/Theme.SystemUI.Screenshot" />
+ </application>
+</manifest>
diff --git a/packages/SystemUI/screenshot/res/values/themes.xml b/packages/SystemUI/screenshot/res/values/themes.xml
new file mode 100644
index 000000000000..a7f8a264e892
--- /dev/null
+++ b/packages/SystemUI/screenshot/res/values/themes.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="Theme.SystemUI.Screenshot" parent="Theme.SystemUI">
+ <item name="android:windowActionBar">false</item>
+ <item name="android:windowNoTitle">true</item>
+
+ <!-- We make the status and navigation bars transparent so that the screenshotted content is
+ not clipped by the status bar height when drawn into the Bitmap (which is what happens
+ given that we draw the view into the Bitmap using hardware acceleration). -->
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ <item name="android:navigationBarColor">@android:color/transparent</item>
+
+ <!-- Make sure that device specific cutouts don't impact the outcome of screenshot tests -->
+ <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
new file mode 100644
index 000000000000..a4a70a49fce3
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/Bitmap.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.os.Build
+import android.view.View
+import platform.test.screenshot.matchers.MSSIMMatcher
+import platform.test.screenshot.matchers.PixelPerfectMatcher
+
+/** Draw this [View] into a [Bitmap]. */
+// TODO(b/195673633): Remove this once Compose screenshot tests use hardware rendering for their
+// tests.
+fun View.drawIntoBitmap(): Bitmap {
+ val bitmap =
+ Bitmap.createBitmap(
+ measuredWidth,
+ measuredHeight,
+ Bitmap.Config.ARGB_8888,
+ )
+ val canvas = Canvas(bitmap)
+ draw(canvas)
+ return bitmap
+}
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ */
+val UnitTestBitmapMatcher =
+ if (Build.CPU_ABI == "x86_64") {
+ // Different CPU architectures can sometimes end up rendering differently, so we can't do
+ // pixel-perfect matching on different architectures using the same golden. Given that our
+ // presubmits are run on cf_x86_64_phone, our goldens should be perfectly matched on the
+ // x86_64 architecture and use the Structural Similarity Index on others.
+ // TODO(b/237511747): Run our screenshot presubmit tests on arm64 instead so that we can
+ // do pixel perfect matching both at presubmit time and at development time with actual
+ // devices.
+ PixelPerfectMatcher()
+ } else {
+ MSSIMMatcher()
+ }
+
+/**
+ * The [BitmapMatcher][platform.test.screenshot.matchers.BitmapMatcher] that should be used for
+ * screenshot *unit* tests.
+ *
+ * We use the Structural Similarity Index for integration tests because they usually contain
+ * additional information and noise that shouldn't break the test.
+ */
+val IntegrationTestBitmapMatcher = MSSIMMatcher()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
new file mode 100644
index 000000000000..2a55a80eb7f4
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ScreenshotActivity.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import androidx.activity.ComponentActivity
+
+/** The Activity that is launched and whose content is set for screenshot tests. */
+class ScreenshotActivity : ComponentActivity()
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
new file mode 100644
index 000000000000..cbab0a75061e
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/SystemUIGoldenImagePathManager.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import androidx.test.platform.app.InstrumentationRegistry
+import platform.test.screenshot.GoldenImagePathManager
+import platform.test.screenshot.PathConfig
+
+/** A [GoldenImagePathManager] that should be used for all SystemUI screenshot tests. */
+class SystemUIGoldenImagePathManager(
+ pathConfig: PathConfig,
+) :
+ GoldenImagePathManager(
+ appContext = InstrumentationRegistry.getInstrumentation().context,
+ deviceLocalPath =
+ InstrumentationRegistry.getInstrumentation()
+ .targetContext
+ .filesDir
+ .absolutePath
+ .toString() + "/sysui_screenshots",
+ pathConfig = pathConfig,
+ ) {
+ override fun toString(): String {
+ // This string is appended to all actual/expected screenshots on the device, so make sure
+ // it is a static value.
+ return "SystemUIGoldenImagePathManager"
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
new file mode 100644
index 000000000000..c609e6f8b4bf
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewCapture.kt
@@ -0,0 +1,180 @@
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.content.Context
+import android.content.ContextWrapper
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Rect
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import android.view.PixelCopy
+import android.view.SurfaceView
+import android.view.View
+import android.view.ViewTreeObserver
+import android.view.Window
+import androidx.annotation.RequiresApi
+import androidx.concurrent.futures.ResolvableFuture
+import androidx.test.annotation.ExperimentalTestApi
+import androidx.test.core.internal.os.HandlerExecutor
+import androidx.test.platform.graphics.HardwareRendererCompat
+import com.google.common.util.concurrent.ListenableFuture
+
+/*
+ * This file was forked from androidx/test/core/view/ViewCapture.kt to add [Window] parameter to
+ * [View.captureToBitmap].
+ * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
+ */
+
+/**
+ * Asynchronously captures an image of the underlying view into a [Bitmap].
+ *
+ * For devices below [Build.VERSION_CODES#O] (or if the view's window cannot be determined), the
+ * image is obtained using [View#draw]. Otherwise, [PixelCopy] is used.
+ *
+ * This method will also enable [HardwareRendererCompat#setDrawingEnabled(boolean)] if required.
+ *
+ * This API is primarily intended for use in lower layer libraries or frameworks. For test authors,
+ * its recommended to use espresso or compose's captureToImage.
+ *
+ * This API is currently experimental and subject to change or removal.
+ */
+@ExperimentalTestApi
+@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+fun View.captureToBitmap(window: Window? = null): ListenableFuture<Bitmap> {
+ val bitmapFuture: ResolvableFuture<Bitmap> = ResolvableFuture.create()
+ val mainExecutor = HandlerExecutor(Handler(Looper.getMainLooper()))
+
+ // disable drawing again if necessary once work is complete
+ if (!HardwareRendererCompat.isDrawingEnabled()) {
+ HardwareRendererCompat.setDrawingEnabled(true)
+ bitmapFuture.addListener({ HardwareRendererCompat.setDrawingEnabled(false) }, mainExecutor)
+ }
+
+ mainExecutor.execute {
+ val forceRedrawFuture = forceRedraw()
+ forceRedrawFuture.addListener({ generateBitmap(bitmapFuture, window) }, mainExecutor)
+ }
+
+ return bitmapFuture
+}
+
+/**
+ * Trigger a redraw of the given view.
+ *
+ * Should only be called on UI thread.
+ *
+ * @return a [ListenableFuture] that will be complete once ui drawing is complete
+ */
+// NoClassDefFoundError occurs on API 15
+@RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+// @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@ExperimentalTestApi
+fun View.forceRedraw(): ListenableFuture<Void> {
+ val future: ResolvableFuture<Void> = ResolvableFuture.create()
+
+ if (Build.VERSION.SDK_INT >= 29 && isHardwareAccelerated) {
+ viewTreeObserver.registerFrameCommitCallback() { future.set(null) }
+ } else {
+ viewTreeObserver.addOnDrawListener(
+ object : ViewTreeObserver.OnDrawListener {
+ var handled = false
+ override fun onDraw() {
+ if (!handled) {
+ handled = true
+ future.set(null)
+ // cannot remove on draw listener inside of onDraw
+ Handler(Looper.getMainLooper()).post {
+ viewTreeObserver.removeOnDrawListener(this)
+ }
+ }
+ }
+ }
+ )
+ }
+ invalidate()
+ return future
+}
+
+private fun View.generateBitmap(
+ bitmapFuture: ResolvableFuture<Bitmap>,
+ window: Window? = null,
+) {
+ if (bitmapFuture.isCancelled) {
+ return
+ }
+ val destBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ when {
+ Build.VERSION.SDK_INT < 26 -> generateBitmapFromDraw(destBitmap, bitmapFuture)
+ this is SurfaceView -> generateBitmapFromSurfaceViewPixelCopy(destBitmap, bitmapFuture)
+ else -> {
+ val window = window ?: getActivity()?.window
+ if (window != null) {
+ generateBitmapFromPixelCopy(window, destBitmap, bitmapFuture)
+ } else {
+ Log.i(
+ "View.captureToImage",
+ "Could not find window for view. Falling back to View#draw instead of PixelCopy"
+ )
+ generateBitmapFromDraw(destBitmap, bitmapFuture)
+ }
+ }
+ }
+}
+
+@SuppressWarnings("NewApi")
+private fun SurfaceView.generateBitmapFromSurfaceViewPixelCopy(
+ destBitmap: Bitmap,
+ bitmapFuture: ResolvableFuture<Bitmap>
+) {
+ val onCopyFinished =
+ PixelCopy.OnPixelCopyFinishedListener { result ->
+ if (result == PixelCopy.SUCCESS) {
+ bitmapFuture.set(destBitmap)
+ } else {
+ bitmapFuture.setException(
+ RuntimeException(String.format("PixelCopy failed: %d", result))
+ )
+ }
+ }
+ PixelCopy.request(this, null, destBitmap, onCopyFinished, handler)
+}
+
+internal fun View.generateBitmapFromDraw(
+ destBitmap: Bitmap,
+ bitmapFuture: ResolvableFuture<Bitmap>
+) {
+ destBitmap.density = resources.displayMetrics.densityDpi
+ computeScroll()
+ val canvas = Canvas(destBitmap)
+ canvas.translate((-scrollX).toFloat(), (-scrollY).toFloat())
+ draw(canvas)
+ bitmapFuture.set(destBitmap)
+}
+
+private fun View.getActivity(): Activity? {
+ fun Context.getActivity(): Activity? {
+ return when (this) {
+ is Activity -> this
+ is ContextWrapper -> this.baseContext.getActivity()
+ else -> null
+ }
+ }
+ return context.getActivity()
+}
+
+private fun View.generateBitmapFromPixelCopy(
+ window: Window,
+ destBitmap: Bitmap,
+ bitmapFuture: ResolvableFuture<Bitmap>
+) {
+ val locationInWindow = intArrayOf(0, 0)
+ getLocationInWindow(locationInWindow)
+ val x = locationInWindow[0]
+ val y = locationInWindow[1]
+ val boundsInWindow = Rect(x, y, x + width, y + height)
+
+ return window.generateBitmapFromPixelCopy(boundsInWindow, destBitmap, bitmapFuture)
+}
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
new file mode 100644
index 000000000000..60130e1086ef
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/ViewScreenshotTestRule.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.testing.screenshot
+
+import android.app.Activity
+import android.app.Dialog
+import android.graphics.Bitmap
+import android.graphics.HardwareRenderer
+import android.os.Looper
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.Window
+import androidx.activity.ComponentActivity
+import androidx.test.espresso.Espresso
+import androidx.test.ext.junit.rules.ActivityScenarioRule
+import com.google.common.util.concurrent.FutureCallback
+import com.google.common.util.concurrent.Futures
+import kotlin.coroutines.suspendCoroutine
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.rules.RuleChain
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import platform.test.screenshot.DeviceEmulationRule
+import platform.test.screenshot.DeviceEmulationSpec
+import platform.test.screenshot.MaterialYouColorsRule
+import platform.test.screenshot.ScreenshotTestRule
+import platform.test.screenshot.getEmulatedDevicePathConfig
+
+/** A rule for View screenshot diff unit tests. */
+class ViewScreenshotTestRule(emulationSpec: DeviceEmulationSpec) : TestRule {
+ private val colorsRule = MaterialYouColorsRule()
+ private val deviceEmulationRule = DeviceEmulationRule(emulationSpec)
+ private val screenshotRule =
+ ScreenshotTestRule(
+ SystemUIGoldenImagePathManager(getEmulatedDevicePathConfig(emulationSpec))
+ )
+ private val activityRule = ActivityScenarioRule(ScreenshotActivity::class.java)
+ private val delegateRule =
+ RuleChain.outerRule(colorsRule)
+ .around(deviceEmulationRule)
+ .around(screenshotRule)
+ .around(activityRule)
+ private val matcher = UnitTestBitmapMatcher
+
+ override fun apply(base: Statement, description: Description): Statement {
+ return delegateRule.apply(base, description)
+ }
+
+ /**
+ * Compare the content of the view provided by [viewProvider] with the golden image identified
+ * by [goldenIdentifier] in the context of [emulationSpec].
+ */
+ fun screenshotTest(
+ goldenIdentifier: String,
+ mode: Mode = Mode.WrapContent,
+ viewProvider: (ComponentActivity) -> View,
+ ) {
+ activityRule.scenario.onActivity { activity ->
+ // Make sure that the activity draws full screen and fits the whole display instead of
+ // the system bars.
+ val window = activity.window
+ window.setDecorFitsSystemWindows(false)
+
+ // Set the content.
+ activity.setContentView(viewProvider(activity), mode.layoutParams)
+
+ // Elevation/shadows is not deterministic when doing hardware rendering, so we disable
+ // it for any view in the hierarchy.
+ window.decorView.removeElevationRecursively()
+ }
+
+ // We call onActivity again because it will make sure that our Activity is done measuring,
+ // laying out and drawing its content (that we set in the previous onActivity lambda).
+ var contentView: View? = null
+ activityRule.scenario.onActivity { activity ->
+ // Check that the content is what we expected.
+ val content = activity.requireViewById<ViewGroup>(android.R.id.content)
+ assertEquals(1, content.childCount)
+ contentView = content.getChildAt(0)
+ }
+
+ val bitmap = contentView?.toBitmap() ?: error("contentView is null")
+ screenshotRule.assertBitmapAgainstGolden(
+ bitmap,
+ goldenIdentifier,
+ matcher,
+ )
+ }
+
+ /**
+ * Compare the content of the dialog provided by [dialogProvider] with the golden image
+ * identified by [goldenIdentifier] in the context of [emulationSpec].
+ */
+ fun dialogScreenshotTest(
+ goldenIdentifier: String,
+ dialogProvider: (Activity) -> Dialog,
+ ) {
+ var dialog: Dialog? = null
+ activityRule.scenario.onActivity { activity ->
+ dialog =
+ dialogProvider(activity).apply {
+ // Make sure that the dialog draws full screen and fits the whole display
+ // instead of the system bars.
+ window.setDecorFitsSystemWindows(false)
+
+ // Disable enter/exit animations.
+ create()
+ window.setWindowAnimations(0)
+
+ // Elevation/shadows is not deterministic when doing hardware rendering, so we
+ // disable it for any view in the hierarchy.
+ window.decorView.removeElevationRecursively()
+
+ // Show the dialog.
+ show()
+ }
+ }
+
+ try {
+ val bitmap = dialog?.toBitmap() ?: error("dialog is null")
+ screenshotRule.assertBitmapAgainstGolden(
+ bitmap,
+ goldenIdentifier,
+ matcher,
+ )
+ } finally {
+ dialog?.dismiss()
+ }
+ }
+
+ private fun View.removeElevationRecursively() {
+ this.elevation = 0f
+
+ if (this is ViewGroup) {
+ repeat(childCount) { i -> getChildAt(i).removeElevationRecursively() }
+ }
+ }
+
+ private fun Dialog.toBitmap(): Bitmap {
+ val window = window
+ return window.decorView.toBitmap(window)
+ }
+
+ private fun View.toBitmap(window: Window? = null): Bitmap {
+ if (Looper.getMainLooper() == Looper.myLooper()) {
+ error("toBitmap() can't be called from the main thread")
+ }
+
+ if (!HardwareRenderer.isDrawingEnabled()) {
+ error("Hardware rendering is not enabled")
+ }
+
+ // Make sure we are idle.
+ Espresso.onIdle()
+
+ val mainExecutor = context.mainExecutor
+ return runBlocking {
+ suspendCoroutine { continuation ->
+ Futures.addCallback(
+ captureToBitmap(window),
+ object : FutureCallback<Bitmap> {
+ override fun onSuccess(result: Bitmap?) {
+ continuation.resumeWith(Result.success(result!!))
+ }
+
+ override fun onFailure(t: Throwable) {
+ continuation.resumeWith(Result.failure(t))
+ }
+ },
+ // We know that we are not on the main thread, so we can block the current
+ // thread and wait for the result in the main thread.
+ mainExecutor,
+ )
+ }
+ }
+ }
+
+ enum class Mode(val layoutParams: LayoutParams) {
+ WrapContent(LayoutParams(WRAP_CONTENT, WRAP_CONTENT)),
+ MatchSize(LayoutParams(MATCH_PARENT, MATCH_PARENT)),
+ MatchWidth(LayoutParams(MATCH_PARENT, WRAP_CONTENT)),
+ MatchHeight(LayoutParams(WRAP_CONTENT, MATCH_PARENT)),
+ }
+}
diff --git a/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
new file mode 100644
index 000000000000..d34f46bf48a6
--- /dev/null
+++ b/packages/SystemUI/screenshot/src/com/android/systemui/testing/screenshot/WindowCapture.kt
@@ -0,0 +1,37 @@
+package com.android.systemui.testing.screenshot
+
+import android.graphics.Bitmap
+import android.graphics.Rect
+import android.os.Handler
+import android.os.Looper
+import android.view.PixelCopy
+import android.view.Window
+import androidx.concurrent.futures.ResolvableFuture
+
+/*
+ * This file was forked from androidx/test/core/view/WindowCapture.kt.
+ * TODO(b/195673633): Remove this fork and use the AndroidX version instead.
+ */
+fun Window.generateBitmapFromPixelCopy(
+ boundsInWindow: Rect? = null,
+ destBitmap: Bitmap,
+ bitmapFuture: ResolvableFuture<Bitmap>
+) {
+ val onCopyFinished =
+ PixelCopy.OnPixelCopyFinishedListener { result ->
+ if (result == PixelCopy.SUCCESS) {
+ bitmapFuture.set(destBitmap)
+ } else {
+ bitmapFuture.setException(
+ RuntimeException(String.format("PixelCopy failed: %d", result))
+ )
+ }
+ }
+ PixelCopy.request(
+ this,
+ boundsInWindow,
+ destBitmap,
+ onCopyFinished,
+ Handler(Looper.getMainLooper())
+ )
+}
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index 3cf5bc1bf13a..18bd6b42386a 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -47,11 +47,18 @@ android_library {
],
static_libs: [
"PluginCoreLib",
+ "SystemUIAnimationLib",
+ "SystemUIPluginLib",
+ "SystemUIUnfoldLib",
"androidx.dynamicanimation_dynamicanimation",
"androidx.concurrent_concurrent-futures",
+ "gson-prebuilt-jar",
"dagger2",
"jsr330",
],
+ resource_dirs: [
+ "res",
+ ],
java_version: "1.8",
min_sdk_version: "current",
plugins: ["dagger2-compiler"],
diff --git a/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml b/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
new file mode 100644
index 000000000000..be72d0b82dbb
--- /dev/null
+++ b/packages/SystemUI/shared/res/drawable/clock_default_thumbnail.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="#FFFF00FF" />
+</shape>
diff --git a/packages/SystemUI/shared/res/layout/clock_default_large.xml b/packages/SystemUI/shared/res/layout/clock_default_large.xml
new file mode 100644
index 000000000000..8510a0a8b550
--- /dev/null
+++ b/packages/SystemUI/shared/res/layout/clock_default_large.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.shared.clocks.AnimatableClockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animatable_clock_view_large"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:gravity="center_horizontal"
+ android:textSize="@dimen/large_clock_text_size"
+ android:fontFamily="@*android:string/config_clockFontFamily"
+ android:typeface="monospace"
+ android:elegantTextHeight="false"
+ chargeAnimationDelay="200"
+ dozeWeight="200"
+ lockScreenWeight="400" />
diff --git a/packages/SystemUI/shared/res/layout/clock_default_small.xml b/packages/SystemUI/shared/res/layout/clock_default_small.xml
new file mode 100644
index 000000000000..ec0e427e6a4d
--- /dev/null
+++ b/packages/SystemUI/shared/res/layout/clock_default_small.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2022, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<com.android.systemui.shared.clocks.AnimatableClockView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/animatable_clock_view"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:gravity="start"
+ android:textSize="@dimen/small_clock_text_size"
+ android:fontFamily="@*android:string/config_clockFontFamily"
+ android:elegantTextHeight="false"
+ android:singleLine="true"
+ android:fontFeatureSettings="pnum"
+ chargeAnimationDelay="350"
+ dozeWeight="200"
+ lockScreenWeight="400" />
diff --git a/packages/SystemUI/shared/res/values/attrs.xml b/packages/SystemUI/shared/res/values/attrs.xml
new file mode 100644
index 000000000000..f9d66ee583da
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/attrs.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Formatting note: terminate all comments with a period, to avoid breaking
+ the documentation output. To suppress comment lines from the documentation
+ output, insert an eat-comment element after the comment lines.
+-->
+
+<resources>
+ <declare-styleable name="AnimatableClockView">
+ <attr name="dozeWeight" format="integer" />
+ <attr name="lockScreenWeight" format="integer" />
+ <attr name="chargeAnimationDelay" format="integer" />
+ </declare-styleable>
+</resources>
diff --git a/packages/SystemUI/shared/res/values/dimens.xml b/packages/SystemUI/shared/res/values/dimens.xml
new file mode 100644
index 000000000000..8f90f0fefdb5
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/dimens.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (c) 2022, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+<resources>
+ <!-- Clock maximum font size (dp is intentional, to prevent any further scaling) -->
+ <dimen name="large_clock_text_size">150sp</dimen>
+ <dimen name="small_clock_text_size">86sp</dimen>
+
+ <!-- Default line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale" type="dimen" format="float">.7</item>
+ <!-- Burmese line spacing multiplier between hours and minutes of the keyguard clock -->
+ <item name="keyguard_clock_line_spacing_scale_burmese" type="dimen" format="float">1</item>
+</resources> \ No newline at end of file
diff --git a/packages/SystemUI/shared/res/values/donottranslate.xml b/packages/SystemUI/shared/res/values/donottranslate.xml
new file mode 100644
index 000000000000..383d5521f156
--- /dev/null
+++ b/packages/SystemUI/shared/res/values/donottranslate.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Skeleton string format for displaying the time in 12-hour format. -->
+ <string name="clock_12hr_format">hm</string>
+
+ <!-- Skeleton string format for displaying the time in 24-hour format. -->
+ <string name="clock_24hr_format">Hm</string>
+</resources>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
index 19a53091b35c..a8a526a33229 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt
@@ -29,6 +29,7 @@ interface Flag<T> {
interface ParcelableFlag<T> : Flag<T>, Parcelable {
val default: T
+ val overridden: Boolean
override fun describeContents() = 0
}
@@ -36,6 +37,12 @@ interface ResourceFlag<T> : Flag<T> {
val resourceId: Int
}
+interface DeviceConfigFlag<T> : Flag<T> {
+ val name: String
+ val namespace: String
+ val default: T
+}
+
interface SysPropFlag<T> : Flag<T> {
val name: String
val default: T
@@ -46,7 +53,8 @@ interface SysPropFlag<T> : Flag<T> {
data class BooleanFlag @JvmOverloads constructor(
override val id: Int,
override val default: Boolean = false,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Boolean> {
companion object {
@@ -59,12 +67,16 @@ data class BooleanFlag @JvmOverloads constructor(
private constructor(parcel: Parcel) : this(
id = parcel.readInt(),
- default = parcel.readBoolean()
+ default = parcel.readBoolean(),
+ teamfood = parcel.readBoolean(),
+ overridden = parcel.readBoolean()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(id)
parcel.writeBoolean(default)
+ parcel.writeBoolean(teamfood)
+ parcel.writeBoolean(overridden)
}
}
@@ -74,6 +86,14 @@ data class ResourceBooleanFlag @JvmOverloads constructor(
override val teamfood: Boolean = false
) : ResourceFlag<Boolean>
+data class DeviceConfigBooleanFlag @JvmOverloads constructor(
+ override val id: Int,
+ override val name: String,
+ override val namespace: String,
+ override val default: Boolean = false,
+ override val teamfood: Boolean = false
+) : DeviceConfigFlag<Boolean>
+
data class SysPropBooleanFlag @JvmOverloads constructor(
override val id: Int,
override val name: String,
@@ -86,7 +106,8 @@ data class SysPropBooleanFlag @JvmOverloads constructor(
data class StringFlag @JvmOverloads constructor(
override val id: Int,
override val default: String = "",
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<String> {
companion object {
@JvmField
@@ -116,7 +137,8 @@ data class ResourceStringFlag @JvmOverloads constructor(
data class IntFlag @JvmOverloads constructor(
override val id: Int,
override val default: Int = 0,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Int> {
companion object {
@@ -147,7 +169,8 @@ data class ResourceIntFlag @JvmOverloads constructor(
data class LongFlag @JvmOverloads constructor(
override val id: Int,
override val default: Long = 0,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Long> {
companion object {
@@ -172,7 +195,8 @@ data class LongFlag @JvmOverloads constructor(
data class FloatFlag @JvmOverloads constructor(
override val id: Int,
override val default: Float = 0f,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Float> {
companion object {
@@ -203,7 +227,8 @@ data class ResourceFloatFlag @JvmOverloads constructor(
data class DoubleFlag @JvmOverloads constructor(
override val id: Int,
override val default: Double = 0.0,
- override val teamfood: Boolean = false
+ override val teamfood: Boolean = false,
+ override val overridden: Boolean = false
) : ParcelableFlag<Double> {
companion object {
@@ -223,4 +248,4 @@ data class DoubleFlag @JvmOverloads constructor(
parcel.writeInt(id)
parcel.writeDouble(default)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
index 26e40e1ecad3..d172690e2488 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/flags/FlagManager.kt
@@ -64,20 +64,29 @@ class FlagManager constructor(
intent.setPackage(RECEIVING_PACKAGE)
return CallbackToFutureAdapter.getFuture {
- completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
- context.sendOrderedBroadcast(intent, null,
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- val extras: Bundle? = getResultExtras(false)
- val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
- extras?.getParcelableArrayList(EXTRA_FLAGS)
- if (listOfFlags != null) {
- completer.set(listOfFlags)
- } else {
- completer.setException(NoFlagResultsException())
- }
+ completer: CallbackToFutureAdapter.Completer<Collection<Flag<*>>> ->
+ context.sendOrderedBroadcast(
+ intent,
+ null,
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val extras: Bundle? = getResultExtras(false)
+ val listOfFlags: java.util.ArrayList<ParcelableFlag<*>>? =
+ extras?.getParcelableArrayList(
+ EXTRA_FLAGS, ParcelableFlag::class.java
+ )
+ if (listOfFlags != null) {
+ completer.set(listOfFlags)
+ } else {
+ completer.setException(NoFlagResultsException())
}
- }, null, Activity.RESULT_OK, "extra data", null)
+ }
+ },
+ null,
+ Activity.RESULT_OK,
+ "extra data",
+ null
+ )
"QueryingFlags"
}
}
@@ -152,7 +161,11 @@ class FlagManager constructor(
}
val parts = uri.pathSegments
val idStr = parts[parts.size - 1]
- val id = try { idStr.toInt() } catch (e: NumberFormatException) { return }
+ val id = try {
+ idStr.toInt()
+ } catch (e: NumberFormatException) {
+ return
+ }
clearCacheAction?.accept(id)
dispatchListenersAndMaybeRestart(id, onSettingsChangedAction)
}
@@ -188,4 +201,5 @@ class FlagManager constructor(
}
class NoFlagResultsException : Exception(
- "SystemUI failed to communicate its flags back successfully") \ No newline at end of file
+ "SystemUI failed to communicate its flags back successfully"
+)
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index e22386e78359..2739d59dbf00 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockView.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.shared.clocks
import android.animation.TimeInterpolator
import android.annotation.ColorInt
@@ -26,9 +26,10 @@ import android.text.TextUtils
import android.text.format.DateFormat
import android.util.AttributeSet
import android.widget.TextView
-import com.android.systemui.R
+import com.android.systemui.animation.GlyphCallback
import com.android.systemui.animation.Interpolators
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.animation.TextAnimator
+import com.android.systemui.shared.R
import java.io.PrintWriter
import java.util.Calendar
import java.util.Locale
@@ -38,13 +39,13 @@ import java.util.TimeZone
* Displays the time with the hour positioned above the minutes. (ie: 09 above 30 is 9:30)
* The time's text color is a gradient that changes its colors based on its controller.
*/
+@SuppressLint("AppCompatCustomView")
class AnimatableClockView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
defStyleRes: Int = 0
) : TextView(context, attrs, defStyleAttr, defStyleRes) {
- private val tag = "AnimatableClockView"
private var lastMeasureCall: CharSequence = ""
@@ -193,7 +194,7 @@ class AnimatableClockView @JvmOverloads constructor(
)
}
- fun animateFoldAppear() {
+ fun animateFoldAppear(animate: Boolean = true) {
if (textAnimator == null) {
return
}
@@ -210,22 +211,22 @@ class AnimatableClockView @JvmOverloads constructor(
weight = dozingWeightInternal,
textSize = -1f,
color = dozingColor,
- animate = true,
+ animate = animate,
interpolator = Interpolators.EMPHASIZED_DECELERATE,
- duration = StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
+ duration = ANIMATION_DURATION_FOLD_TO_AOD.toLong(),
delay = 0,
onAnimationEnd = null
)
}
- fun animateCharge(dozeStateGetter: DozeStateGetter) {
+ fun animateCharge(isDozing: () -> Boolean) {
if (textAnimator == null || textAnimator!!.isRunning()) {
// Skip charge animation if dozing animation is already playing.
return
}
val startAnimPhase2 = Runnable {
setTextStyle(
- weight = if (dozeStateGetter.isDozing) dozingWeight else lockScreenWeight,
+ weight = if (isDozing()) dozingWeight else lockScreenWeight,
textSize = -1f,
color = null,
animate = true,
@@ -235,7 +236,7 @@ class AnimatableClockView @JvmOverloads constructor(
)
}
setTextStyle(
- weight = if (dozeStateGetter.isDozing) lockScreenWeight else dozingWeight,
+ weight = if (isDozing()) lockScreenWeight else dozingWeight,
textSize = -1f,
color = null,
animate = true,
@@ -257,6 +258,8 @@ class AnimatableClockView @JvmOverloads constructor(
)
}
+ private val glyphFilter: GlyphCallback? = null // Add text animation tweak here.
+
/**
* Set text style with an optional animation.
*
@@ -288,6 +291,7 @@ class AnimatableClockView @JvmOverloads constructor(
delay = delay,
onAnimationEnd = onAnimationEnd
)
+ textAnimator?.glyphFilter = glyphFilter
} else {
// when the text animator is set, update its start values
onTextAnimatorInitialized = Runnable {
@@ -301,6 +305,7 @@ class AnimatableClockView @JvmOverloads constructor(
delay = delay,
onAnimationEnd = onAnimationEnd
)
+ textAnimator?.glyphFilter = glyphFilter
}
}
}
@@ -326,9 +331,9 @@ class AnimatableClockView @JvmOverloads constructor(
)
}
- fun refreshFormat() {
+ fun refreshFormat() = refreshFormat(DateFormat.is24HourFormat(context))
+ fun refreshFormat(use24HourFormat: Boolean) {
Patterns.update(context)
- val use24HourFormat = DateFormat.is24HourFormat(context)
format = when {
isSingleLineInternal && use24HourFormat -> Patterns.sClockView24
@@ -381,14 +386,14 @@ class AnimatableClockView @JvmOverloads constructor(
}
}
- interface DozeStateGetter {
- val isDozing: Boolean
+ companion object {
+ private val TAG = AnimatableClockView::class.simpleName
+ const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
+ private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
+ private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
+ private const val DOZE_ANIM_DURATION: Long = 300
+ private const val APPEAR_ANIM_DURATION: Long = 350
+ private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
+ private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
}
}
-
-private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
-private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
-private const val DOZE_ANIM_DURATION: Long = 300
-private const val APPEAR_ANIM_DURATION: Long = 350
-private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
-private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
new file mode 100644
index 000000000000..a4c03b0b57c8
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.shared.clocks
+
+import android.content.Context
+import android.database.ContentObserver
+import android.graphics.drawable.Drawable
+import android.net.Uri
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings
+import android.util.Log
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.google.gson.Gson
+import javax.inject.Inject
+
+private val TAG = ClockRegistry::class.simpleName
+private val DEBUG = true
+
+typealias ClockChangeListener = () -> Unit
+
+/** ClockRegistry aggregates providers and plugins */
+open class ClockRegistry(
+ val context: Context,
+ val pluginManager: PluginManager,
+ val handler: Handler,
+ defaultClockProvider: ClockProvider
+) {
+ @Inject constructor(
+ context: Context,
+ pluginManager: PluginManager,
+ @Main handler: Handler,
+ defaultClockProvider: DefaultClockProvider
+ ) : this(context, pluginManager, handler, defaultClockProvider as ClockProvider) { }
+
+ private val gson = Gson()
+ private val availableClocks = mutableMapOf<ClockId, ClockInfo>()
+ private val clockChangeListeners = mutableListOf<ClockChangeListener>()
+ private val settingObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean, uris: Collection<Uri>, flags: Int, userId: Int) =
+ clockChangeListeners.forEach { it() }
+ }
+
+ private val pluginListener = object : PluginListener<ClockProviderPlugin> {
+ override fun onPluginConnected(plugin: ClockProviderPlugin, context: Context) =
+ connectClocks(plugin)
+
+ override fun onPluginDisconnected(plugin: ClockProviderPlugin) =
+ disconnectClocks(plugin)
+ }
+
+ open var currentClockId: ClockId
+ get() {
+ val json = Settings.Secure.getString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE
+ )
+ return gson.fromJson(json, ClockSetting::class.java).clockId
+ }
+ set(value) {
+ val json = gson.toJson(ClockSetting(value, System.currentTimeMillis()))
+ Settings.Secure.putString(
+ context.contentResolver,
+ Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE, json
+ )
+ }
+
+ init {
+ connectClocks(defaultClockProvider)
+ pluginManager.addPluginListener(pluginListener, ClockProviderPlugin::class.java)
+ context.contentResolver.registerContentObserver(
+ Settings.Secure.getUriFor(Settings.Secure.LOCK_SCREEN_CUSTOM_CLOCK_FACE),
+ false,
+ settingObserver,
+ UserHandle.USER_ALL
+ )
+ }
+
+ private fun connectClocks(provider: ClockProvider) {
+ val currentId = currentClockId
+ for (clock in provider.getClocks()) {
+ val id = clock.clockId
+ val current = availableClocks[id]
+ if (current != null) {
+ Log.e(TAG, "Clock Id conflict: $id is registered by both " +
+ "${provider::class.simpleName} and ${current.provider::class.simpleName}")
+ return
+ }
+
+ availableClocks[id] = ClockInfo(clock, provider)
+ if (currentId == id) {
+ if (DEBUG) {
+ Log.i(TAG, "Current clock ($currentId) was connected")
+ }
+ clockChangeListeners.forEach { it() }
+ }
+ }
+ }
+
+ private fun disconnectClocks(provider: ClockProvider) {
+ val currentId = currentClockId
+ for (clock in provider.getClocks()) {
+ availableClocks.remove(clock.clockId)
+
+ if (currentId == clock.clockId) {
+ Log.w(TAG, "Current clock ($currentId) was disconnected")
+ clockChangeListeners.forEach { it() }
+ }
+ }
+ }
+
+ fun getClocks(): List<ClockMetadata> = availableClocks.map { (_, clock) -> clock.metadata }
+
+ fun getClockThumbnail(clockId: ClockId): Drawable? =
+ availableClocks[clockId]?.provider?.getClockThumbnail(clockId)
+
+ fun createExampleClock(clockId: ClockId): Clock? = createClock(clockId)
+
+ fun registerClockChangeListener(listener: ClockChangeListener) =
+ clockChangeListeners.add(listener)
+
+ fun unregisterClockChangeListener(listener: ClockChangeListener) =
+ clockChangeListeners.remove(listener)
+
+ fun createCurrentClock(): Clock {
+ val clockId = currentClockId
+ if (!clockId.isNullOrEmpty()) {
+ val clock = createClock(clockId)
+ if (clock != null) {
+ return clock
+ } else {
+ Log.e(TAG, "Clock $clockId not found; using default")
+ }
+ }
+
+ return createClock(DEFAULT_CLOCK_ID)!!
+ }
+
+ private fun createClock(clockId: ClockId): Clock? =
+ availableClocks[clockId]?.provider?.createClock(clockId)
+
+ private data class ClockInfo(
+ val metadata: ClockMetadata,
+ val provider: ClockProvider
+ )
+
+ private data class ClockSetting(
+ val clockId: ClockId,
+ val _applied_timestamp: Long
+ )
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
new file mode 100644
index 000000000000..5d8da5985768
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.shared.clocks
+
+import android.content.res.Resources
+import android.graphics.Color
+import android.graphics.drawable.Drawable
+import android.icu.text.NumberFormat
+import android.util.TypedValue
+import android.view.LayoutInflater
+import com.android.internal.colorextraction.ColorExtractor
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockAnimation
+import com.android.systemui.plugins.ClockEvents
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProvider
+import com.android.systemui.shared.R
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import javax.inject.Inject
+
+private val TAG = DefaultClockProvider::class.simpleName
+const val DEFAULT_CLOCK_NAME = "Default Clock"
+const val DEFAULT_CLOCK_ID = "DEFAULT"
+
+/** Provides the default system clock */
+class DefaultClockProvider @Inject constructor(
+ val layoutInflater: LayoutInflater,
+ @Main val resources: Resources
+) : ClockProvider {
+ override fun getClocks(): List<ClockMetadata> =
+ listOf(ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME))
+
+ override fun createClock(id: ClockId): Clock {
+ if (id != DEFAULT_CLOCK_ID) {
+ throw IllegalArgumentException("$id is unsupported by $TAG")
+ }
+ return DefaultClock(layoutInflater, resources)
+ }
+
+ override fun getClockThumbnail(id: ClockId): Drawable? {
+ if (id != DEFAULT_CLOCK_ID) {
+ throw IllegalArgumentException("$id is unsupported by $TAG")
+ }
+
+ // TODO: Update placeholder to actual resource
+ return resources.getDrawable(R.drawable.clock_default_thumbnail, null)
+ }
+}
+
+/**
+ * Controls the default clock visuals.
+ *
+ * This serves as an adapter between the clock interface and the
+ * AnimatableClockView used by the existing lockscreen clock.
+ */
+class DefaultClock(
+ private val layoutInflater: LayoutInflater,
+ private val resources: Resources
+) : Clock {
+ override val smallClock =
+ layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
+ override val largeClock =
+ layoutInflater.inflate(R.layout.clock_default_large, null) as AnimatableClockView
+ private val clocks = listOf(smallClock, largeClock)
+
+ private val burmeseNf = NumberFormat.getInstance(Locale.forLanguageTag("my"))
+ private val burmeseNumerals = burmeseNf.format(FORMAT_NUMBER.toLong())
+ private val burmeseLineSpacing =
+ resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale_burmese)
+ private val defaultLineSpacing = resources.getFloat(R.dimen.keyguard_clock_line_spacing_scale)
+
+ override val events = object : ClockEvents {
+ override fun onTimeTick() = clocks.forEach { it.refreshTime() }
+
+ override fun onTimeFormatChanged(is24Hr: Boolean) =
+ clocks.forEach { it.refreshFormat(is24Hr) }
+
+ override fun onTimeZoneChanged(timeZone: TimeZone) =
+ clocks.forEach { it.onTimeZoneChanged(timeZone) }
+
+ override fun onFontSettingChanged() {
+ smallClock.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.small_clock_text_size).toFloat()
+ )
+ largeClock.setTextSize(
+ TypedValue.COMPLEX_UNIT_PX,
+ resources.getDimensionPixelSize(R.dimen.large_clock_text_size).toFloat()
+ )
+ }
+
+ override fun onColorPaletteChanged(palette: ColorExtractor.GradientColors) =
+ clocks.forEach { it.setColors(DOZE_COLOR, palette.mainColor) }
+
+ override fun onLocaleChanged(locale: Locale) {
+ val nf = NumberFormat.getInstance(locale)
+ if (nf.format(FORMAT_NUMBER.toLong()) == burmeseNumerals) {
+ clocks.forEach { it.setLineSpacingScale(burmeseLineSpacing) }
+ } else {
+ clocks.forEach { it.setLineSpacingScale(defaultLineSpacing) }
+ }
+
+ clocks.forEach { it.refreshFormat() }
+ }
+ }
+
+ override val animation = object : ClockAnimation {
+ override fun initialize(dozeFraction: Float, foldFraction: Float) {
+ dozeState = AnimationState(dozeFraction)
+ foldState = AnimationState(foldFraction)
+
+ if (foldState.isActive) {
+ clocks.forEach { it.animateFoldAppear(false) }
+ } else {
+ clocks.forEach { it.animateDoze(dozeState.isActive, false) }
+ }
+ }
+
+ override fun enter() {
+ if (dozeState.isActive) {
+ clocks.forEach { it.animateAppearOnLockscreen() }
+ }
+ }
+
+ override fun charge() = clocks.forEach { it.animateCharge { dozeState.isActive } }
+
+ private var foldState = AnimationState(0f)
+ override fun fold(fraction: Float) {
+ val (hasChanged, hasJumped) = foldState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateFoldAppear(!hasJumped) }
+ }
+ }
+
+ private var dozeState = AnimationState(0f)
+ override fun doze(fraction: Float) {
+ val (hasChanged, hasJumped) = dozeState.update(fraction)
+ if (hasChanged) {
+ clocks.forEach { it.animateDoze(dozeState.isActive, !hasJumped) }
+ }
+ }
+ }
+
+ private class AnimationState(
+ var fraction: Float
+ ) {
+ var isActive: Boolean = fraction < 0.5f
+ fun update(newFraction: Float): Pair<Boolean, Boolean> {
+ val wasActive = isActive
+ val hasJumped = (fraction == 0f && newFraction == 1f) ||
+ (fraction == 1f && newFraction == 0f)
+ isActive = newFraction > fraction
+ fraction = newFraction
+ return Pair(wasActive != isActive, hasJumped)
+ }
+ }
+
+ init {
+ events.onLocaleChanged(Locale.getDefault())
+ }
+
+ override fun dump(pw: PrintWriter) = clocks.forEach { it.dump(pw) }
+
+ companion object {
+ private const val DOZE_COLOR = Color.WHITE
+ private const val FORMAT_NUMBER = 1234567890
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
index 88fe03465405..203b236fcdd6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/pip/PipSurfaceTransactionHelper.java
@@ -80,7 +80,8 @@ public class PipSurfaceTransactionHelper {
public PictureInPictureSurfaceTransaction scaleAndCrop(
SurfaceControl.Transaction tx, SurfaceControl leash,
- Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets) {
+ Rect sourceRectHint, Rect sourceBounds, Rect destinationBounds, Rect insets,
+ float progress) {
mTmpSourceRectF.set(sourceBounds);
mTmpDestinationRect.set(sourceBounds);
mTmpDestinationRect.inset(insets);
@@ -93,9 +94,13 @@ public class PipSurfaceTransactionHelper {
: (float) destinationBounds.height() / sourceBounds.height();
} else {
// scale by sourceRectHint if it's not edge-to-edge
- scale = sourceRectHint.width() <= sourceRectHint.height()
+ final float endScale = sourceRectHint.width() <= sourceRectHint.height()
? (float) destinationBounds.width() / sourceRectHint.width()
: (float) destinationBounds.height() / sourceRectHint.height();
+ final float startScale = sourceRectHint.width() <= sourceRectHint.height()
+ ? (float) destinationBounds.width() / sourceBounds.width()
+ : (float) destinationBounds.height() / sourceBounds.height();
+ scale = (1 - progress) * startScale + progress * endScale;
}
final float left = destinationBounds.left - (insets.left + sourceBounds.left) * scale;
final float top = destinationBounds.top - (insets.top + sourceBounds.top) * scale;
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index e743c4e5a83d..a030bf629507 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -42,12 +42,6 @@ interface ISystemUiProxy {
void onOverviewShown(boolean fromHome) = 6;
/**
- * Get the secondary split screen app's rectangle when not minimized.
- * @deprecated
- */
- Rect getNonMinimizedSplitScreenSecondaryBounds() = 7;
-
- /**
* Control the {@param alpha} of the option nav bar button (back-button in 2 button mode
* and home handle & background in gestural mode). The {@param animate} is currently only
* supported for 2 button mode.
@@ -94,21 +88,6 @@ interface ISystemUiProxy {
*/
void stopScreenPinning() = 17;
- /**
- * Handle the provided image as if it was a screenshot.
- *
- * Deprecated, use handleImageBundleAsScreenshot with image bundle and UserTask
- * @deprecated
- */
- void handleImageAsScreenshot(in Bitmap screenImage, in Rect locationInScreen,
- in Insets visibleInsets, int taskId) = 21;
-
- /**
- * Sets the split-screen divider minimized state
- * @deprecated
- */
- void setSplitScreenMinimized(boolean minimized) = 22;
-
/*
* Notifies that the swipe-to-home (recents animation) is finished.
*/
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index 675dc9b533fb..422274469dcc 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -16,8 +16,12 @@
package com.android.systemui.shared.recents.model;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+
import android.app.ActivityManager;
import android.app.ActivityManager.TaskDescription;
import android.app.TaskInfo;
@@ -31,6 +35,8 @@ import android.view.ViewDebug;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.internal.util.ArrayUtils;
+
import java.io.PrintWriter;
import java.util.Objects;
@@ -240,10 +246,16 @@ public class Task {
*/
public static Task from(TaskKey taskKey, TaskInfo taskInfo, boolean isLocked) {
ActivityManager.TaskDescription td = taskInfo.taskDescription;
+ // Also consider undefined activity type to include tasks in overview right after rebooting
+ // the device.
+ final boolean isDockable = taskInfo.supportsMultiWindow
+ && ArrayUtils.contains(CONTROLLED_WINDOWING_MODES, taskInfo.getWindowingMode())
+ && (taskInfo.getActivityType() == ACTIVITY_TYPE_UNDEFINED
+ || ArrayUtils.contains(CONTROLLED_ACTIVITY_TYPES, taskInfo.getActivityType()));
return new Task(taskKey,
td != null ? td.getPrimaryColor() : 0,
- td != null ? td.getBackgroundColor() : 0,
- taskInfo.supportsSplitScreenMultiWindow, isLocked, td, taskInfo.topActivity);
+ td != null ? td.getBackgroundColor() : 0, isDockable , isLocked, td,
+ taskInfo.topActivity);
}
public Task(TaskKey key) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
index 01f417f147d7..b29dc835a6f4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/rotation/RotationButtonController.java
@@ -20,6 +20,7 @@ import static android.content.pm.PackageManager.FEATURE_PC;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.internal.view.RotationPolicy.NATURAL_ROTATION;
+import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -97,6 +98,7 @@ public class RotationButtonController {
@SuppressLint("InlinedApi")
private @WindowInsetsController.Behavior
int mBehavior = WindowInsetsController.BEHAVIOR_DEFAULT;
+ private int mNavBarMode;
private boolean mSkipOverrideUserLockPrefsOnce;
private final int mLightIconColor;
private final int mDarkIconColor;
@@ -397,6 +399,10 @@ public class RotationButtonController {
if (rotateSuggestionsDisabled) onRotationSuggestionsDisabled();
}
+ public void onNavigationModeChanged(int mode) {
+ mNavBarMode = mode;
+ }
+
public void onBehaviorChanged(int displayId, @WindowInsetsController.Behavior int behavior) {
if (DEFAULT_DISPLAY != displayId) {
return;
@@ -433,7 +439,8 @@ public class RotationButtonController {
*/
@SuppressLint("InlinedApi")
private boolean canShowRotationButton() {
- return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT;
+ return mIsNavigationBarShowing || mBehavior == WindowInsetsController.BEHAVIOR_DEFAULT
+ || isGesturalMode(mNavBarMode);
}
@DrawableRes
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
index b87cf47dd93f..c1429335292f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/CompatUI.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerKt.kt
@@ -14,22 +14,19 @@
* limitations under the License.
*/
-package com.android.wm.shell.compatui;
+package com.android.systemui.shared.system
-import com.android.wm.shell.common.annotations.ExternalThread;
+import android.app.ActivityManager
+
+/** Kotlin extensions for [ActivityManager] */
+object ActivityManagerKt {
-/**
- * Interface to engage compat UI.
- */
-@ExternalThread
-public interface CompatUI {
/**
- * Called when the keyguard showing state changes. Removes all compat UIs if the
- * keyguard is now showing.
- *
- * <p>Note that if the keyguard is occluded it will also be considered showing.
- *
- * @param showing indicates if the keyguard is now showing.
+ * Returns `true` whether the app with the given package name has an activity at the top of the
+ * most recent task; `false` otherwise
*/
- void onKeyguardShowingChanged(boolean showing);
+ fun ActivityManager.isInForeground(packageName: String): Boolean {
+ val tasks: List<ActivityManager.RunningTaskInfo> = getRunningTasks(1)
+ return tasks.isNotEmpty() && packageName == tasks[0].topActivity.packageName
+ }
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
index be3dfdcf05d5..916526d0efac 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -130,19 +130,14 @@ public class ActivityManagerWrapper {
}
/**
- * @return a list of the recents tasks.
- */
- public List<RecentTaskInfo> getRecentTasks(int numTasks, int userId) {
- return mAtm.getRecentTasks(numTasks, RECENT_IGNORE_UNAVAILABLE, userId);
- }
-
- /**
- * @return the task snapshot for the given {@param taskId}.
+ * @return a {@link ThumbnailData} with {@link TaskSnapshot} for the given {@param taskId}.
+ * The snapshot will be triggered if no cached {@link TaskSnapshot} exists.
*/
public @NonNull ThumbnailData getTaskThumbnail(int taskId, boolean isLowResolution) {
TaskSnapshot snapshot = null;
try {
- snapshot = getService().getTaskSnapshot(taskId, isLowResolution);
+ snapshot = getService().getTaskSnapshot(taskId, isLowResolution,
+ true /* takeSnapshotIfNeeded */);
} catch (RemoteException e) {
Log.w(TAG, "Failed to retrieve task snapshot", e);
}
@@ -245,25 +240,6 @@ public class ActivityManagerWrapper {
}
/**
- * Starts a task from Recents.
- *
- * @param resultCallback The result success callback
- * @param resultCallbackHandler The handler to receive the result callback
- */
- public void startActivityFromRecentsAsync(Task.TaskKey taskKey, ActivityOptions options,
- Consumer<Boolean> resultCallback, Handler resultCallbackHandler) {
- final boolean result = startActivityFromRecents(taskKey, options);
- if (resultCallback != null) {
- resultCallbackHandler.post(new Runnable() {
- @Override
- public void run() {
- resultCallback.accept(result);
- }
- });
- }
- }
-
- /**
* Starts a task from Recents synchronously.
*/
public boolean startActivityFromRecents(Task.TaskKey taskKey, ActivityOptions options) {
@@ -284,20 +260,6 @@ public class ActivityManagerWrapper {
}
/**
- * @deprecated use {@link TaskStackChangeListeners#registerTaskStackListener}
- */
- public void registerTaskStackListener(TaskStackChangeListener listener) {
- TaskStackChangeListeners.getInstance().registerTaskStackListener(listener);
- }
-
- /**
- * @deprecated use {@link TaskStackChangeListeners#unregisterTaskStackListener}
- */
- public void unregisterTaskStackListener(TaskStackChangeListener listener) {
- TaskStackChangeListeners.getInstance().unregisterTaskStackListener(listener);
- }
-
- /**
* Requests that the system close any open system windows (including other SystemUI).
*/
public void closeSystemWindows(final String reason) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
index add2d022e893..be99b270c09a 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -28,14 +28,6 @@ import android.os.Handler;
public abstract class ActivityOptionsCompat {
/**
- * @Deprecated
- * @return ActivityOptions for starting a task in split screen as the primary window.
- */
- public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) {
- return ActivityOptions.makeBasic();
- }
-
- /**
* @return ActivityOptions for starting a task in freeform.
*/
public static ActivityOptions makeFreeformOptions() {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index 630fb360cc14..97e024238778 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -204,28 +204,6 @@ public class QuickStepContract {
}
/**
- * Touch slopes and thresholds for quick step operations. Drag slop is the point where the
- * home button press/long press over are ignored and will start to drag when exceeded and the
- * touch slop is when the respected operation will occur when exceeded. Touch slop must be
- * larger than the drag slop.
- */
- public static int getQuickStepDragSlopPx() {
- return convertDpToPixel(10);
- }
-
- public static int getQuickStepTouchSlopPx() {
- return convertDpToPixel(24);
- }
-
- public static int getQuickScrubTouchSlopPx() {
- return convertDpToPixel(24);
- }
-
- private static int convertDpToPixel(float dp) {
- return (int) (dp * Resources.getSystem().getDisplayMetrics().density);
- }
-
- /**
* Returns whether the specified sysui state is such that the assistant gesture should be
* disabled.
*/
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
index 13f1db4a0831..0094820f0dad 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RecentsAnimationControllerCompat.java
@@ -65,14 +65,6 @@ public class RecentsAnimationControllerCompat {
}
}
- public void hideCurrentInputMethod() {
- try {
- mAnimationController.hideCurrentInputMethod();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to set hide input method", e);
- }
- }
-
/**
* Sets the final surface transaction on a Task. This is used by Launcher to notify the system
* that animating Activity to PiP has completed and the associated task surface should be
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
index 06f5372e5cce..9265f07ad284 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
@@ -114,6 +114,8 @@ public class RemoteAnimationAdapterCompat {
private static IRemoteTransition.Stub wrapRemoteTransition(
final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
@Override
public void startAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t,
@@ -137,6 +139,8 @@ public class RemoteAnimationAdapterCompat {
float displayH = 0;
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
+ // skip changes that we didn't wrap
+ if (!leashMap.containsKey(change.getLeash())) continue;
if (change.getTaskInfo() != null
&& change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
isReturnToHome = change.getMode() == TRANSIT_OPEN
@@ -173,6 +177,8 @@ public class RemoteAnimationAdapterCompat {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
final TransitionInfo.Change change = info.getChanges().get(i);
final SurfaceControl leash = leashMap.get(change.getLeash());
+ // skip changes that we didn't wrap
+ if (leash == null) continue;
final int mode = info.getChanges().get(i).getMode();
// Only deal with independent layers
if (!TransitionInfo.isIndependent(change, info)) continue;
@@ -214,9 +220,9 @@ public class RemoteAnimationAdapterCompat {
for (int i = info.getChanges().size() - 1; i >= 0; --i) {
info.getChanges().get(i).getLeash().release();
}
- for (int i = leashMap.size() - 1; i >= 0; --i) {
- leashMap.valueAt(i).release();
- }
+ // Don't release here since launcher might still be using them. Instead
+ // let launcher release them (eg. via RemoteAnimationTargets)
+ leashMap.clear();
try {
finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
} catch (RemoteException e) {
@@ -225,19 +231,32 @@ public class RemoteAnimationAdapterCompat {
}
}
};
+ synchronized (mFinishRunnables) {
+ mFinishRunnables.put(token, animationFinishedCallback);
+ }
// TODO(bc-unlcok): Pass correct transit type.
- remoteAnimationAdapter.onAnimationStart(
- TRANSIT_OLD_NONE,
- appsCompat, wallpapersCompat, nonAppsCompat,
- animationFinishedCallback);
+ remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
+ appsCompat, wallpapersCompat, nonAppsCompat, () -> {
+ synchronized (mFinishRunnables) {
+ if (mFinishRunnables.remove(token) == null) return;
+ }
+ animationFinishedCallback.run();
+ });
}
@Override
public void mergeAnimation(IBinder token, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
- // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, ignore
- // any incoming merges.
+ // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+ // to legacy cancel.
+ final Runnable finishRunnable;
+ synchronized (mFinishRunnables) {
+ finishRunnable = mFinishRunnables.remove(mergeTarget);
+ }
+ if (finishRunnable == null) return;
+ remoteAnimationAdapter.onAnimationCancelled();
+ finishRunnable.run();
}
};
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
index 9cf482fdf790..249133a9a63b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationTargetCompat.java
@@ -17,7 +17,6 @@
package com.android.systemui.shared.system;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
-import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
@@ -32,7 +31,6 @@ import android.app.WindowConfiguration;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.ArrayMap;
-import android.util.IntArray;
import android.util.SparseArray;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
@@ -142,21 +140,12 @@ public class RemoteAnimationTargetCompat {
// changes should be ordered top-to-bottom in z
final int mode = change.getMode();
- // Don't move anything that isn't independent within its parents
- if (!TransitionInfo.isIndependent(change, info)) {
- if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
- t.setPosition(leash, change.getEndRelOffset().x, change.getEndRelOffset().y);
- }
- return;
- }
-
- final boolean hasParent = change.getParent() != null;
+ t.reparent(leash, info.getRootLeash());
+ final Rect absBounds =
+ (mode == TRANSIT_OPEN) ? change.getEndAbsBounds() : change.getStartAbsBounds();
+ t.setPosition(leash, absBounds.left - info.getRootOffset().x,
+ absBounds.top - info.getRootOffset().y);
- if (!hasParent) {
- t.reparent(leash, info.getRootLeash());
- t.setPosition(leash, change.getStartAbsBounds().left - info.getRootOffset().x,
- change.getStartAbsBounds().top - info.getRootOffset().y);
- }
// Put all the OPEN/SHOW on top
if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT) {
if (isOpening) {
@@ -196,8 +185,7 @@ public class RemoteAnimationTargetCompat {
.setContainerLayer()
// Initial the surface visible to respect the visibility of the original surface.
.setHidden(false)
- .setParent(change.getParent() == null ? info.getRootLeash()
- : info.getChange(change.getParent()).getLeash())
+ .setParent(info.getRootLeash())
.build();
// Copied Transitions setup code (which expects bottom-to-top order, so we swap here)
setupLeash(leashSurface, change, info.getChanges().size() - order, info, t);
@@ -269,58 +257,32 @@ public class RemoteAnimationTargetCompat {
public static RemoteAnimationTargetCompat[] wrap(TransitionInfo info, boolean wallpapers,
SurfaceControl.Transaction t, ArrayMap<SurfaceControl, SurfaceControl> leashMap) {
final ArrayList<RemoteAnimationTargetCompat> out = new ArrayList<>();
- final SparseArray<RemoteAnimationTargetCompat> childTaskTargets = new SparseArray<>();
- final IntArray excludedParentTaskIds = new IntArray();
+ final SparseArray<TransitionInfo.Change> childTaskTargets = new SparseArray<>();
for (int i = 0; i < info.getChanges().size(); i++) {
final TransitionInfo.Change change = info.getChanges().get(i);
final boolean changeIsWallpaper =
(change.getFlags() & TransitionInfo.FLAG_IS_WALLPAPER) != 0;
if (wallpapers != changeIsWallpaper) continue;
- final RemoteAnimationTargetCompat targetCompat =
- new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
- if (leashMap != null) {
- leashMap.put(change.getLeash(), targetCompat.leash);
- }
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (taskInfo != null) {
- // Skip wrapping excluded parent task animate target since it will animate its child
- // tasks instead.
- if (excludedParentTaskIds.binarySearch(taskInfo.taskId) != -1) {
- continue;
- }
-
- // Check if there's a matching child task target in cache.
- RemoteAnimationTargetCompat childTaskTarget = childTaskTargets.get(taskInfo.taskId);
- if (childTaskTarget != null) {
- // Launcher monitors leaf task ids to perform animation, override the target
- // with its child task information so Launcher can animate this parent surface
- // directly with leaf task information.
- targetCompat.taskInfo = childTaskTarget.taskInfo;
- targetCompat.taskId = childTaskTarget.taskId;
- childTaskTargets.remove(taskInfo.taskId);
- }
-
- // Check if it has a parent task, cache its information for later use.
- if (taskInfo.parentTaskId != -1
- && excludedParentTaskIds.binarySearch(taskInfo.parentTaskId) == -1) {
- if (!childTaskTargets.contains(taskInfo.parentTaskId)) {
- // Cache the target amd skip wrapping it info the final animation targets.
- // Otherwise, the child task might get transformed multiple-times with the
- // flow like RecentsView#redrawLiveTile.
- childTaskTargets.put(taskInfo.parentTaskId, targetCompat);
+ if (!wallpapers) {
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ // Children always come before parent since changes are in top-to-bottom z-order.
+ if (taskInfo != null) {
+ if (childTaskTargets.contains(taskInfo.taskId)) {
+ // has children, so not a leaf. Skip.
continue;
}
-
- // There is another child task target cached with the same parent task id.
- // Which means the parent having multiple child tasks in transition. Stop
- // propagate child task info.
- childTaskTarget = childTaskTargets.removeReturnOld(taskInfo.parentTaskId);
- out.add(childTaskTarget);
- excludedParentTaskIds.add(taskInfo.parentTaskId);
+ if (taskInfo.hasParentTask()) {
+ childTaskTargets.put(taskInfo.parentTaskId, change);
+ }
}
}
+ final RemoteAnimationTargetCompat targetCompat =
+ new RemoteAnimationTargetCompat(change, info.getChanges().size() - i, info, t);
+ if (leashMap != null) {
+ leashMap.put(change.getLeash(), targetCompat.leash);
+ }
out.add(targetCompat);
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index de35514be2cb..609846e8c729 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -21,12 +21,14 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
+import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
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.window.TransitionFilter.CONTAINER_ORDER_TOP;
import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.ACTIVITY_TYPE_RECENTS;
+import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MODE_CLOSING;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -132,16 +134,17 @@ public class RemoteTransitionCompat implements Parcelable {
// TODO(b/177438007): Move this set-up logic into launcher's animation impl.
mToken = transition;
// This transition is for opening recents, so recents is on-top. We want to draw
- // the current going-away task on top of recents, though, so move it to front
+ // the current going-away tasks on top of recents, though, so move them to front.
+ // Note that we divide up the "layer space" into 3 regions each the size of
+ // the change count. This way we can easily move changes into above/below/between
+ // while maintaining their relative ordering.
final ArrayList<WindowContainerToken> pausingTasks = new ArrayList<>();
WindowContainerToken pipTask = null;
WindowContainerToken recentsTask = null;
- for (int i = info.getChanges().size() - 1; i >= 0; --i) {
- final TransitionInfo.Change change = info.getChanges().get(i);
- final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
- if (change.getMode() == TRANSIT_CLOSE || change.getMode() == TRANSIT_TO_BACK) {
- t.setLayer(leashMap.get(change.getLeash()),
- info.getChanges().size() * 3 - i);
+ for (int i = apps.length - 1; i >= 0; --i) {
+ final ActivityManager.RunningTaskInfo taskInfo = apps[i].taskInfo;
+ if (apps[i].mode == MODE_CLOSING) {
+ t.setLayer(apps[i].leash, info.getChanges().size() * 3 - i);
if (taskInfo == null) {
continue;
}
@@ -154,8 +157,7 @@ public class RemoteTransitionCompat implements Parcelable {
} else if (taskInfo != null
&& taskInfo.topActivityType == ACTIVITY_TYPE_RECENTS) {
// This task is for recents, keep it on top.
- t.setLayer(leashMap.get(change.getLeash()),
- info.getChanges().size() * 3 - i);
+ t.setLayer(apps[i].leash, info.getChanges().size() * 3 - i);
recentsTask = taskInfo.token;
} else if (taskInfo != null && taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
recentsTask = taskInfo.token;
@@ -167,7 +169,8 @@ public class RemoteTransitionCompat implements Parcelable {
}
t.apply();
mRecentsSession.setup(controller, info, finishedCallback, pausingTasks, pipTask,
- recentsTask, leashMap, mToken);
+ recentsTask, leashMap, mToken,
+ (info.getFlags() & TRANSIT_FLAG_KEYGUARD_LOCKED) != 0);
recents.onAnimationStart(mRecentsSession, apps, wallpapers, new Rect(0, 0, 0, 0),
new Rect());
}
@@ -183,6 +186,8 @@ public class RemoteTransitionCompat implements Parcelable {
} catch (RemoteException e) {
Log.e(TAG, "Error merging transition.", e);
}
+ // commit taskAppeared after merge transition finished.
+ mRecentsSession.commitTasksAppearedIfNeeded(recents);
}
};
mTransition = new RemoteTransition(remote, appThread);
@@ -219,15 +224,18 @@ public class RemoteTransitionCompat implements Parcelable {
private WindowContainerToken mRecentsTask = null;
private TransitionInfo mInfo = null;
private ArrayList<SurfaceControl> mOpeningLeashes = null;
+ private boolean mOpeningHome = false;
private ArrayMap<SurfaceControl, SurfaceControl> mLeashMap = null;
private PictureInPictureSurfaceTransaction mPipTransaction = null;
private IBinder mTransition = null;
+ private boolean mKeyguardLocked = false;
+ private RemoteAnimationTargetCompat[] mAppearedTargets;
void setup(RecentsAnimationControllerCompat wrapped, TransitionInfo info,
IRemoteTransitionFinishedCallback finishCB,
ArrayList<WindowContainerToken> pausingTasks, WindowContainerToken pipTask,
WindowContainerToken recentsTask, ArrayMap<SurfaceControl, SurfaceControl> leashMap,
- IBinder transition) {
+ IBinder transition, boolean keyguardLocked) {
if (mInfo != null) {
throw new IllegalStateException("Trying to run a new recents animation while"
+ " recents is already active.");
@@ -240,12 +248,14 @@ public class RemoteTransitionCompat implements Parcelable {
mRecentsTask = recentsTask;
mLeashMap = leashMap;
mTransition = transition;
+ mKeyguardLocked = keyguardLocked;
}
@SuppressLint("NewApi")
boolean merge(TransitionInfo info, SurfaceControl.Transaction t,
RecentsAnimationListener recents) {
SparseArray<TransitionInfo.Change> openingTasks = null;
+ mAppearedTargets = null;
boolean cancelRecents = false;
boolean homeGoingAway = false;
boolean hasChangingApp = false;
@@ -312,6 +322,7 @@ public class RemoteTransitionCompat implements Parcelable {
}
final int layer = mInfo.getChanges().size() * 3;
mOpeningLeashes = new ArrayList<>();
+ mOpeningHome = cancelRecents;
final RemoteAnimationTargetCompat[] targets =
new RemoteAnimationTargetCompat[openingTasks.size()];
for (int i = 0; i < openingTasks.size(); ++i) {
@@ -326,10 +337,17 @@ public class RemoteTransitionCompat implements Parcelable {
targets[i] = target;
}
t.apply();
- recents.onTasksAppeared(targets);
+ mAppearedTargets = targets;
return true;
}
+ private void commitTasksAppearedIfNeeded(RecentsAnimationListener recents) {
+ if (mAppearedTargets != null) {
+ recents.onTasksAppeared(mAppearedTargets);
+ mAppearedTargets = null;
+ }
+ }
+
@Override public ThumbnailData screenshotTask(int taskId) {
try {
final TaskSnapshot snapshot =
@@ -351,10 +369,6 @@ public class RemoteTransitionCompat implements Parcelable {
if (mWrapped != null) mWrapped.setAnimationTargetsBehindSystemBars(behindSystemBars);
}
- @Override public void hideCurrentInputMethod() {
- mWrapped.hideCurrentInputMethod();
- }
-
@Override public void setFinishTaskTransaction(int taskId,
PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) {
mPipTransaction = finishTransaction;
@@ -374,6 +388,10 @@ public class RemoteTransitionCompat implements Parcelable {
final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
final WindowContainerTransaction wct = new WindowContainerTransaction();
+ if (mKeyguardLocked && mRecentsTask != null) {
+ if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
+ else wct.restoreTransientOrder(mRecentsTask);
+ }
if (!toHome && mPausingTasks != null && mOpeningLeashes == null) {
// The gesture went back to opening the app rather than continuing with
// recents, so end the transition by moving the app back to the top (and also
@@ -383,16 +401,41 @@ public class RemoteTransitionCompat implements Parcelable {
wct.reorder(mPausingTasks.get(i), true /* onTop */);
t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
}
- if (mRecentsTask != null) {
+ if (!mKeyguardLocked && mRecentsTask != null) {
+ wct.restoreTransientOrder(mRecentsTask);
+ }
+ } else if (toHome && mOpeningHome && mPausingTasks != null) {
+ // Special situaition where 3p launcher was changed during recents (this happens
+ // during tapltests...). Here we get both "return to home" AND "home opening".
+ // This is basically going home, but we have to restore recents order and also
+ // treat the home "pausing" task properly.
+ for (int i = mPausingTasks.size() - 1; i >= 0; --i) {
+ final TransitionInfo.Change change = mInfo.getChange(mPausingTasks.get(i));
+ final ActivityManager.RunningTaskInfo taskInfo = change.getTaskInfo();
+ if (taskInfo.topActivityType == ACTIVITY_TYPE_HOME) {
+ // Treat as opening (see above)
+ wct.reorder(mPausingTasks.get(i), true /* onTop */);
+ t.show(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+ } else {
+ // Treat as hiding (see below)
+ t.hide(mInfo.getChange(mPausingTasks.get(i)).getLeash());
+ }
+ }
+ if (!mKeyguardLocked && mRecentsTask != null) {
wct.restoreTransientOrder(mRecentsTask);
}
} else {
- if (!sendUserLeaveHint) {
- for (int i = 0; i < mPausingTasks.size(); ++i) {
+ for (int i = 0; i < mPausingTasks.size(); ++i) {
+ if (!sendUserLeaveHint) {
// This means recents is not *actually* finishing, so of course we gotta
// do special stuff in WMCore to accommodate.
wct.setDoNotPip(mPausingTasks.get(i));
}
+ // Since we will reparent out of the leashes, pre-emptively hide the child
+ // surface to match the leash. Otherwise, there will be a flicker before the
+ // visibility gets committed in Core when using split-screen (in splitscreen,
+ // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+ t.hide(mInfo.getChange(mPausingTasks.get(i)).getLeash());
}
if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) {
t.show(mInfo.getChange(mPipTask).getLeash());
@@ -402,18 +445,14 @@ public class RemoteTransitionCompat implements Parcelable {
mPipTransaction = null;
}
}
- // Release surface references now. This is apparently to free GPU
- // memory while doing quick operations (eg. during CTS).
- for (int i = 0; i < mLeashMap.size(); ++i) {
- if (mLeashMap.keyAt(i) == mLeashMap.valueAt(i)) continue;
- t.remove(mLeashMap.valueAt(i));
- }
try {
mFinishCB.onTransitionFinished(wct.isEmpty() ? null : wct, t);
} catch (RemoteException e) {
Log.e("RemoteTransitionCompat", "Failed to call animation finish callback", e);
t.apply();
}
+ // Only release the non-local created surface references. The animator is responsible
+ // for releasing the leashes created by local.
for (int i = 0; i < mInfo.getChanges().size(); ++i) {
mInfo.getChanges().get(i).getLeash().release();
}
@@ -423,6 +462,7 @@ public class RemoteTransitionCompat implements Parcelable {
mPausingTasks = null;
mInfo = null;
mOpeningLeashes = null;
+ mOpeningHome = false;
mLeashMap = null;
mTransition = null;
}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java
deleted file mode 100644
index 359d36939b31..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/UniversalSmartspaceUtils.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.SurfaceView;
-
-/** Utility class that is shared between SysUI and Launcher for Universal Smartspace features. */
-public final class UniversalSmartspaceUtils {
- public static final String ACTION_REQUEST_SMARTSPACE_VIEW =
- "com.android.systemui.REQUEST_SMARTSPACE_VIEW";
- public static final String INTENT_BUNDLE_KEY = "bundle_key";
-
- private static final String SYSUI_PACKAGE = "com.android.systemui";
-
- /** Creates an intent to request that sysui draws the Smartspace to the SurfaceView. */
- public static Intent createRequestSmartspaceIntent(SurfaceView surfaceView) {
- Intent intent = new Intent(ACTION_REQUEST_SMARTSPACE_VIEW);
-
- Bundle bundle = SurfaceViewRequestUtils.createSurfaceBundle(surfaceView);
- return intent
- .putExtra(INTENT_BUNDLE_KEY, bundle)
- .setPackage(SYSUI_PACKAGE)
- .addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
- | Intent.FLAG_RECEIVER_FOREGROUND);
- }
-
- private UniversalSmartspaceUtils() {}
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
index 5bd81a42a814..5577513f4c3c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -28,6 +28,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.view.InsetsController;
+import android.view.InsetsFrameProvider;
import android.view.InsetsState;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -105,30 +106,10 @@ public class WindowManagerWrapper {
*/
public void setProvidesInsetsTypes(WindowManager.LayoutParams params,
int[] providesInsetsTypes) {
- params.providesInsetsTypes = providesInsetsTypes;
- }
-
- /**
- * Sets if app requested fixed orientation should be ignored for given displayId.
- */
- public void setIgnoreOrientationRequest(int displayId, boolean ignoreOrientationRequest) {
- try {
- WindowManagerGlobal.getWindowManagerService().setIgnoreOrientationRequest(
- displayId, ignoreOrientationRequest);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to setIgnoreOrientationRequest()", e);
- }
- }
-
- /**
- * @return the stable insets for the primary display.
- */
- public void getStableInsets(Rect outStableInsets) {
- try {
- WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
- outStableInsets);
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to get stable insets", e);
+ final int length = providesInsetsTypes.length;
+ params.providedInsets = new InsetsFrameProvider[length];
+ for (int i = 0; i < length; i++) {
+ params.providedInsets[i] = new InsetsFrameProvider(providesInsetsTypes[i]);
}
}
@@ -148,16 +129,6 @@ public class WindowManagerWrapper {
}
}
- public void overridePendingAppTransitionRemote(
- RemoteAnimationAdapterCompat remoteAnimationAdapter, int displayId) {
- try {
- WindowManagerGlobal.getWindowManagerService().overridePendingAppTransitionRemote(
- remoteAnimationAdapter.getWrapped(), displayId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override pending app transition (remote): ", e);
- }
- }
-
/**
* Enable or disable haptic feedback on the navigation bar buttons.
*/
@@ -170,19 +141,6 @@ public class WindowManagerWrapper {
}
}
- public void setRecentsVisibility(boolean visible) {
- try {
- WindowManagerGlobal.getWindowManagerService().setRecentsVisibility(visible);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to set recents visibility");
- }
- }
-
- @Deprecated
- public void setPipVisibility(final boolean visible) {
- // To be removed
- }
-
/**
* @param displayId the id of display to check if there is a software navigation bar.
*
@@ -197,22 +155,6 @@ public class WindowManagerWrapper {
}
/**
- * @return The side of the screen where navigation bar is positioned.
- * @see #NAV_BAR_POS_RIGHT
- * @see #NAV_BAR_POS_LEFT
- * @see #NAV_BAR_POS_BOTTOM
- * @see #NAV_BAR_POS_INVALID
- */
- public int getNavBarPosition(int displayId) {
- try {
- return WindowManagerGlobal.getWindowManagerService().getNavBarPosition(displayId);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to get nav bar position");
- }
- return NAV_BAR_POS_INVALID;
- }
-
- /**
* Mirrors a specified display. The SurfaceControl returned is the root of the mirrored
* hierarchy.
*
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
deleted file mode 100644
index d1b06394b818..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.unfold.config
-
-import android.content.Context
-import android.os.SystemProperties
-
-internal class ResourceUnfoldTransitionConfig(private val context: Context) :
- UnfoldTransitionConfig {
-
- override val isEnabled: Boolean
- get() = readIsEnabledResource() && isPropertyEnabled
-
- override val isHingeAngleEnabled: Boolean
- get() = readIsHingeAngleEnabled()
-
- private val isPropertyEnabled: Boolean
- get() =
- SystemProperties.getInt(
- UNFOLD_TRANSITION_MODE_PROPERTY_NAME, UNFOLD_TRANSITION_PROPERTY_ENABLED) ==
- UNFOLD_TRANSITION_PROPERTY_ENABLED
-
- private fun readIsEnabledResource(): Boolean =
- context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled)
-
- private fun readIsHingeAngleEnabled(): Boolean =
- context.resources.getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle)
-}
-
-/**
- * Temporary persistent property to control unfold transition mode.
- *
- * See [com.android.unfold.config.AnimationMode].
- */
-private const val UNFOLD_TRANSITION_MODE_PROPERTY_NAME = "persist.unfold.transition_enabled"
-private const val UNFOLD_TRANSITION_PROPERTY_ENABLED = 1
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
new file mode 100644
index 000000000000..7f2933e44b32
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/ActivityManagerActivityTypeProvider.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.system
+
+import android.app.ActivityManager
+import android.app.WindowConfiguration
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ActivityManagerActivityTypeProvider @Inject constructor(
+ private val activityManager: ActivityManager
+) : CurrentActivityTypeProvider {
+
+ override val isHomeActivity: Boolean?
+ get() {
+ val activityType = activityManager.getRunningTasks(/* maxNum= */ 1)
+ ?.getOrNull(0)?.topActivityType ?: return null
+
+ return activityType == WindowConfiguration.ACTIVITY_TYPE_HOME
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
new file mode 100644
index 000000000000..3b8d318a3a79
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/DeviceStateManagerFoldProvider.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.system
+
+import android.content.Context
+import android.hardware.devicestate.DeviceStateManager
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class DeviceStateManagerFoldProvider @Inject constructor(
+ private val deviceStateManager: DeviceStateManager,
+ private val context: Context
+) : FoldProvider {
+
+ private val callbacks: MutableMap<FoldCallback,
+ DeviceStateManager.DeviceStateCallback> = hashMapOf()
+
+ override fun registerCallback(callback: FoldCallback, executor: Executor) {
+ val listener = FoldStateListener(context, callback)
+ deviceStateManager.registerCallback(executor, listener)
+ callbacks[callback] = listener
+ }
+
+ override fun unregisterCallback(callback: FoldCallback) {
+ val listener = callbacks.remove(callback)
+ listener?.let {
+ deviceStateManager.unregisterCallback(it)
+ }
+ }
+
+ private inner class FoldStateListener(
+ context: Context,
+ listener: FoldCallback
+ ) : DeviceStateManager.FoldStateListener(context, { listener.onFoldUpdated(it) })
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
new file mode 100644
index 000000000000..24ae42ae4db2
--- /dev/null
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/system/SystemUnfoldSharedModule.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.system
+
+import android.os.Handler
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
+import dagger.Binds
+import dagger.Module
+import java.util.concurrent.Executor
+
+/**
+ * Dagger module with system-only dependencies for the unfold animation.
+ * The code that is used to calculate unfold transition progress
+ * depends on some hidden APIs that are not available in normal
+ * apps. In order to re-use this code and use alternative implementations
+ * of these classes in other apps and hidden APIs here.
+ */
+@Module
+abstract class SystemUnfoldSharedModule {
+
+ @Binds
+ abstract fun activityTypeProvider(executor: ActivityManagerActivityTypeProvider):
+ CurrentActivityTypeProvider
+
+ @Binds
+ abstract fun config(config: ResourceUnfoldTransitionConfig): UnfoldTransitionConfig
+
+ @Binds
+ abstract fun foldState(provider: DeviceStateManagerFoldProvider): FoldProvider
+
+ @Binds
+ @UnfoldMain
+ abstract fun mainExecutor(@Main executor: Executor): Executor
+
+ @Binds
+ @UnfoldMain
+ abstract fun mainHandler(@Main handler: Handler): Handler
+
+ @Binds
+ @UnfoldBackground
+ abstract fun backgroundExecutor(@UiBackground executor: Executor): Executor
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
deleted file mode 100644
index b351585de364..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.android.systemui.unfold.updates.hinge
-
-import androidx.core.util.Consumer
-
-internal object EmptyHingeAngleProvider : HingeAngleProvider {
- override fun start() {}
-
- override fun stop() {}
-
- override fun removeCallback(listener: Consumer<Float>) {}
-
- override fun addCallback(listener: Consumer<Float>) {}
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
deleted file mode 100644
index 48a5b12c759a..000000000000
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.android.systemui.unfold.updates.hinge
-
-import androidx.core.util.Consumer
-import com.android.systemui.statusbar.policy.CallbackController
-
-/**
- * Emits device hinge angle values (angle between two integral parts of the device).
- *
- * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
- * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
- * state.
- */
-interface HingeAngleProvider : CallbackController<Consumer<Float>> {
- fun start()
- fun stop()
-}
-
-const val FULLY_OPEN_DEGREES = 180f
-const val FULLY_CLOSED_DEGREES = 0f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
index 53c528ff24a8..ec938b219933 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProvider.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
package com.android.systemui.unfold.util
import android.content.Context
diff --git a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
index 487e1a48044c..c69ff7ee1cd8 100644
--- a/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
+++ b/packages/SystemUI/src/com/android/keyguard/AnimatableClockController.java
@@ -32,6 +32,7 @@ import com.android.systemui.R;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.ViewController;
@@ -134,6 +135,21 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
reset();
}
}
+
+ @Override
+ public void onTimeFormatChanged(String timeFormat) {
+ mView.refreshFormat();
+ }
+
+ @Override
+ public void onTimeZoneChanged(TimeZone timeZone) {
+ mView.onTimeZoneChanged(timeZone);
+ }
+
+ @Override
+ public void onUserSwitchComplete(int userId) {
+ mView.refreshFormat();
+ }
};
@Override
@@ -186,7 +202,7 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
/** Animate the clock appearance when a foldable device goes from fully-open/half-open state to
* fully folded state and it goes to sleep (always on display screen) */
public void animateFoldAppear() {
- mView.animateFoldAppear();
+ mView.animateFoldAppear(true);
}
/**
@@ -197,20 +213,6 @@ public class AnimatableClockController extends ViewController<AnimatableClockVie
}
/**
- * Updates the timezone for the view.
- */
- public void onTimeZoneChanged(TimeZone timeZone) {
- mView.onTimeZoneChanged(timeZone);
- }
-
- /**
- * Trigger a time format update
- */
- public void refreshFormat() {
- mView.refreshFormat();
- }
-
- /**
* Return locallly stored dozing state.
*/
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
index 40edfe50d907..206b8bee323c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitch.java
@@ -22,6 +22,7 @@ import com.android.keyguard.dagger.KeyguardStatusViewScope;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
index 5c9dd5ec26e7..6c32a4910c56 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java
@@ -398,17 +398,6 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS
void updateTimeZone(TimeZone timeZone) {
mView.onTimeZoneChanged(timeZone);
- if (mClockViewController != null) {
- mClockViewController.onTimeZoneChanged(timeZone);
- mLargeClockViewController.onTimeZoneChanged(timeZone);
- }
- }
-
- void refreshFormat() {
- if (mClockViewController != null) {
- mClockViewController.refreshFormat();
- mLargeClockViewController.refreshFormat();
- }
}
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
index 12fa401d7fea..d32219a9817f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardHostViewController.java
@@ -336,6 +336,11 @@ public class KeyguardHostViewController extends ViewController<KeyguardHostView>
mKeyguardSecurityContainerController.onStartingToHide();
}
+ /** Called when bouncer visibility changes. */
+ public void onBouncerVisibilityChanged(@View.Visibility int visibility) {
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(visibility);
+ }
+
public boolean hasDismissActions() {
return mDismissAction != null || mCancelAction != null;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index db2b4ac2c669..58e0fb968204 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -58,7 +58,6 @@ data class KeyguardFaceListenModel(
val keyguardAwake: Boolean,
val keyguardGoingAway: Boolean,
val listeningForFaceAssistant: Boolean,
- val lockIconPressed: Boolean,
val occludingAppRequestingFaceAuth: Boolean,
val primaryUser: Boolean,
val scanningAllowedByStrongAuth: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
index 81cc3a933600..3ec8ce918a4d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardMessageAreaController.java
@@ -18,6 +18,7 @@ package com.android.keyguard;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.text.TextUtils;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -103,6 +104,15 @@ public class KeyguardMessageAreaController extends ViewController<KeyguardMessag
mView.setMessage(resId);
}
+ /**
+ * Set Text if KeyguardMessageArea is empty.
+ */
+ public void setMessageIfEmpty(int resId) {
+ if (TextUtils.isEmpty(mView.getText())) {
+ setMessage(resId);
+ }
+ }
+
public void setNextMessageColor(ColorStateList colorState) {
mView.setNextMessageColor(colorState);
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index bfacc590dc52..29e912fdab32 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -207,6 +207,7 @@ public class KeyguardPasswordViewController
if (reason != KeyguardSecurityView.SCREEN_ON || mShowImeAtScreenOn) {
showInput();
}
+ mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_password);
}
private void showInput() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
index 39c394981193..b97d9671eb25 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java
@@ -304,6 +304,12 @@ public class KeyguardPatternViewController
}
@Override
+ public void onResume(int reason) {
+ super.onResume(reason);
+ mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pattern);
+ }
+
+ @Override
public boolean needsInput() {
return false;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
index f7423ed12e68..59a018ad51df 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputViewController.java
@@ -127,6 +127,7 @@ public abstract class KeyguardPinBasedInputViewController<T extends KeyguardPinB
public void onResume(int reason) {
super.onResume(reason);
mPasswordEntry.requestFocus();
+ mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
}
@Override
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index cce516d981a5..8fb622a8c55e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -50,6 +50,7 @@ import android.util.AttributeSet;
import android.util.Log;
import android.util.MathUtils;
import android.util.TypedValue;
+import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
@@ -93,6 +94,7 @@ import com.android.systemui.util.settings.GlobalSettings;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
public class KeyguardSecurityContainer extends FrameLayout {
static final int USER_TYPE_PRIMARY = 1;
@@ -127,12 +129,12 @@ public class KeyguardSecurityContainer extends FrameLayout {
private static final long IME_DISAPPEAR_DURATION_MS = 125;
- // The duration of the animation to switch bouncer sides.
- private static final long BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS = 500;
+ // The duration of the animation to switch security sides.
+ private static final long SECURITY_SHIFT_ANIMATION_DURATION_MS = 500;
- // How much of the switch sides animation should be dedicated to fading the bouncer out. The
+ // How much of the switch sides animation should be dedicated to fading the security out. The
// remainder will fade it back in again.
- private static final float BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
+ private static final float SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION = 0.2f;
@VisibleForTesting
KeyguardSecurityViewFlipper mSecurityViewFlipper;
@@ -146,6 +148,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
private final SpringAnimation mSpringAnimation;
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
private final List<Gefingerpoken> mMotionEventListeners = new ArrayList<>();
+ private final GestureDetector mDoubleTapDetector;
private float mLastTouchY = -1;
private int mActivePointerId = -1;
@@ -305,6 +308,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
super(context, attrs, defStyle);
mSpringAnimation = new SpringAnimation(this, DynamicAnimation.Y);
mViewConfiguration = ViewConfiguration.get(context);
+ mDoubleTapDetector = new GestureDetector(context, new DoubleTapListener());
}
void onResume(SecurityMode securityMode, boolean faceAuthEnabled) {
@@ -372,10 +376,23 @@ public class KeyguardSecurityContainer extends FrameLayout {
mViewMode.updatePositionByTouchX(x);
}
- /** Returns whether the inner SecurityViewFlipper is left-aligned when in one-handed mode. */
- public boolean isOneHandedModeLeftAligned() {
- return mCurrentMode == MODE_ONE_HANDED
- && ((OneHandedViewMode) mViewMode).isLeftAligned();
+ public boolean isSidedSecurityMode() {
+ return mViewMode instanceof SidedSecurityMode;
+ }
+
+ /** Returns whether the inner SecurityViewFlipper is left-aligned when in sided mode. */
+ public boolean isSecurityLeftAligned() {
+ return mViewMode instanceof SidedSecurityMode
+ && ((SidedSecurityMode) mViewMode).isLeftAligned();
+ }
+
+ /**
+ * Returns whether the touch happened on the other side of security (like bouncer) when in
+ * sided mode.
+ */
+ public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) {
+ return mViewMode instanceof SidedSecurityMode
+ && ((SidedSecurityMode) mViewMode).isTouchOnTheOtherSideOfSecurity(ev);
}
public void onPause() {
@@ -439,6 +456,10 @@ public class KeyguardSecurityContainer extends FrameLayout {
.anyMatch(listener -> listener.onTouchEvent(event))
|| super.onTouchEvent(event);
+ // double tap detector should be called after listeners handle touches as listeners are
+ // helping with ignoring falsing. Otherwise falsing will be activated for some double taps
+ mDoubleTapDetector.onTouchEvent(event);
+
switch (action) {
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
@@ -475,15 +496,26 @@ public class KeyguardSecurityContainer extends FrameLayout {
if (mSwipeListener != null) {
mSwipeListener.onSwipeUp();
}
- } else {
- if (!mIsDragging) {
- mViewMode.handleTap(event);
- }
}
}
return true;
}
+ private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return handleDoubleTap(e);
+ }
+ }
+
+ @VisibleForTesting boolean handleDoubleTap(MotionEvent e) {
+ if (!mIsDragging) {
+ mViewMode.handleDoubleTap(e);
+ return true;
+ }
+ return false;
+ }
+
void addMotionEventListener(Gefingerpoken listener) {
mMotionEventListeners.add(listener);
}
@@ -736,8 +768,8 @@ public class KeyguardSecurityContainer extends FrameLayout {
/** Alter the ViewFlipper position, based upon a touch outside of it */
default void updatePositionByTouchX(float x) {};
- /** A tap on the container, outside of the ViewFlipper */
- default void handleTap(MotionEvent event) {};
+ /** A double tap on the container, outside of the ViewFlipper */
+ default void handleDoubleTap(MotionEvent event) {};
/** Called when the view needs to reset or hides */
default void reset() {};
@@ -761,6 +793,195 @@ public class KeyguardSecurityContainer extends FrameLayout {
}
/**
+ * Base class for modes which support having on left/right side of the screen, used for large
+ * screen devices
+ */
+ abstract static class SidedSecurityMode implements ViewMode {
+ @Nullable private ValueAnimator mRunningSecurityShiftAnimator;
+ private KeyguardSecurityViewFlipper mViewFlipper;
+ private ViewGroup mView;
+ private GlobalSettings mGlobalSettings;
+ private int mDefaultSideSetting;
+
+ public void init(ViewGroup v, KeyguardSecurityViewFlipper viewFlipper,
+ GlobalSettings globalSettings, boolean leftAlignedByDefault) {
+ mView = v;
+ mViewFlipper = viewFlipper;
+ mGlobalSettings = globalSettings;
+ mDefaultSideSetting =
+ leftAlignedByDefault ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+ : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
+ }
+
+ /**
+ * Determine if a double tap on this view is on the other side. If so, will animate
+ * positions and record the preference to always show on this side.
+ */
+ @Override
+ public void handleDoubleTap(MotionEvent event) {
+ boolean currentlyLeftAligned = isLeftAligned();
+ // Did the tap hit the "other" side of the bouncer?
+ if (isTouchOnTheOtherSideOfSecurity(event, currentlyLeftAligned)) {
+ boolean willBeLeftAligned = !currentlyLeftAligned;
+ updateSideSetting(willBeLeftAligned);
+
+ int keyguardState = willBeLeftAligned
+ ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
+ : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
+ SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
+
+ updateSecurityViewLocation(willBeLeftAligned, /* animate= */ true);
+ }
+ }
+
+ private boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev, boolean leftAligned) {
+ float x = ev.getX();
+ return (leftAligned && (x > mView.getWidth() / 2f))
+ || (!leftAligned && (x < mView.getWidth() / 2f));
+ }
+
+ public boolean isTouchOnTheOtherSideOfSecurity(MotionEvent ev) {
+ return isTouchOnTheOtherSideOfSecurity(ev, isLeftAligned());
+ }
+
+ protected abstract void updateSecurityViewLocation(boolean leftAlign, boolean animate);
+
+ protected void translateSecurityViewLocation(boolean leftAlign, boolean animate) {
+ translateSecurityViewLocation(leftAlign, animate, i -> {});
+ }
+
+ /**
+ * Moves the inner security view to the correct location with animation. This is triggered
+ * when the user double taps on the side of the screen that is not currently occupied by
+ * the security view.
+ */
+ protected void translateSecurityViewLocation(boolean leftAlign, boolean animate,
+ Consumer<Float> securityAlphaListener) {
+ if (mRunningSecurityShiftAnimator != null) {
+ mRunningSecurityShiftAnimator.cancel();
+ mRunningSecurityShiftAnimator = null;
+ }
+
+ int targetTranslation = leftAlign
+ ? 0 : mView.getMeasuredWidth() - mViewFlipper.getWidth();
+
+ if (animate) {
+ // This animation is a bit fun to implement. The bouncer needs to move, and fade
+ // in/out at the same time. The issue is, the bouncer should only move a short
+ // amount (120dp or so), but obviously needs to go from one side of the screen to
+ // the other. This needs a pretty custom animation.
+ //
+ // This works as follows. It uses a ValueAnimation to simply drive the animation
+ // progress. This animator is responsible for both the translation of the bouncer,
+ // and the current fade. It will fade the bouncer out while also moving it along the
+ // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
+ // bouncer closer to its destination, then fade it back in again. The effect is that
+ // the bouncer will move from 0 -> X while fading out, then
+ // (destination - X) -> destination while fading back in again.
+ // TODO(b/208250221): Make this animation properly abortable.
+ Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
+ mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
+ Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
+ Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
+
+ mRunningSecurityShiftAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
+ mRunningSecurityShiftAnimator.setDuration(SECURITY_SHIFT_ANIMATION_DURATION_MS);
+ mRunningSecurityShiftAnimator.setInterpolator(Interpolators.LINEAR);
+
+ int initialTranslation = (int) mViewFlipper.getTranslationX();
+ int totalTranslation = (int) mView.getResources().getDimension(
+ R.dimen.security_shift_animation_translation);
+
+ final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
+ && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
+ if (shouldRestoreLayerType) {
+ mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
+ }
+
+ float initialAlpha = mViewFlipper.getAlpha();
+
+ mRunningSecurityShiftAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mRunningSecurityShiftAnimator = null;
+ }
+ });
+ mRunningSecurityShiftAnimator.addUpdateListener(animation -> {
+ float switchPoint = SECURITY_SHIFT_ANIMATION_FADE_OUT_PROPORTION;
+ boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
+
+ int currentTranslation = (int) (positionInterpolator.getInterpolation(
+ animation.getAnimatedFraction()) * totalTranslation);
+ int translationRemaining = totalTranslation - currentTranslation;
+
+ // Flip the sign if we're going from right to left.
+ if (leftAlign) {
+ currentTranslation = -currentTranslation;
+ translationRemaining = -translationRemaining;
+ }
+
+ float opacity;
+ if (isFadingOut) {
+ // The bouncer fades out over the first X%.
+ float fadeOutFraction = MathUtils.constrainedMap(
+ /* rangeMin= */1.0f,
+ /* rangeMax= */0.0f,
+ /* valueMin= */0.0f,
+ /* valueMax= */switchPoint,
+ animation.getAnimatedFraction());
+ opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
+
+ // When fading out, the alpha needs to start from the initial opacity of the
+ // view flipper, otherwise we get a weird bit of jank as it ramps back to
+ // 100%.
+ mViewFlipper.setAlpha(opacity * initialAlpha);
+
+ // Animate away from the source.
+ mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
+ } else {
+ // And in again over the remaining (100-X)%.
+ float fadeInFraction = MathUtils.constrainedMap(
+ /* rangeMin= */0.0f,
+ /* rangeMax= */1.0f,
+ /* valueMin= */switchPoint,
+ /* valueMax= */1.0f,
+ animation.getAnimatedFraction());
+
+ opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
+ mViewFlipper.setAlpha(opacity);
+
+ // Fading back in, animate towards the destination.
+ mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
+ }
+ securityAlphaListener.accept(opacity);
+
+ if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
+ mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
+ }
+ });
+
+ mRunningSecurityShiftAnimator.start();
+ } else {
+ mViewFlipper.setTranslationX(targetTranslation);
+ }
+ }
+
+
+ boolean isLeftAligned() {
+ return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+ mDefaultSideSetting)
+ == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+ }
+
+ protected void updateSideSetting(boolean leftAligned) {
+ mGlobalSettings.putInt(
+ Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
+ leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
+ : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+ }
+ }
+
+ /**
* Default bouncer is centered within the space
*/
static class DefaultViewMode implements ViewMode {
@@ -792,7 +1013,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
* User switcher mode will display both the current user icon as well as
* a user switcher, in both portrait and landscape modes.
*/
- static class UserSwitcherViewMode implements ViewMode {
+ static class UserSwitcherViewMode extends SidedSecurityMode {
private ViewGroup mView;
private ViewGroup mUserSwitcherViewGroup;
private KeyguardSecurityViewFlipper mViewFlipper;
@@ -804,11 +1025,15 @@ public class KeyguardSecurityContainer extends FrameLayout {
private UserSwitcherController.UserSwitchCallback mUserSwitchCallback =
this::setupUserSwitcher;
+ private float mAnimationLastAlpha = 1f;
+ private boolean mAnimationWaitsToShift = true;
+
@Override
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
+ init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false);
mView = v;
mViewFlipper = viewFlipper;
mFalsingManager = falsingManager;
@@ -822,9 +1047,7 @@ public class KeyguardSecurityContainer extends FrameLayout {
true);
mUserSwitcherViewGroup = mView.findViewById(R.id.keyguard_bouncer_user_switcher);
}
-
updateSecurityViewLocation();
-
mUserSwitcher = mView.findViewById(R.id.user_switcher_header);
setupUserSwitcher();
mUserSwitcherController.addUserSwitchCallback(mUserSwitchCallback);
@@ -1020,18 +1243,65 @@ public class KeyguardSecurityContainer extends FrameLayout {
@Override
public void updateSecurityViewLocation() {
- int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+ updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
+ }
+ public void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
+ setYTranslation();
+ setGravity();
+ setXTranslation(leftAlign, animate);
+ }
+
+ private void setXTranslation(boolean leftAlign, boolean animate) {
+ if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
+ mUserSwitcherViewGroup.setTranslationX(0);
+ mViewFlipper.setTranslationX(0);
+ } else {
+ int switcherTargetTranslation = leftAlign
+ ? mView.getMeasuredWidth() - mViewFlipper.getWidth() : 0;
+ if (animate) {
+ mAnimationWaitsToShift = true;
+ mAnimationLastAlpha = 1f;
+ translateSecurityViewLocation(leftAlign, animate, securityAlpha -> {
+ // During the animation security view fades out - alpha goes from 1 to
+ // (almost) 0 - and then fades in - alpha grows back to 1.
+ // If new alpha is bigger than previous one it means we're at inflection
+ // point and alpha is zero or almost zero. That's when we want to do
+ // translation of user switcher, so that it's not visible to the user.
+ boolean fullyFadeOut = securityAlpha == 0.0f
+ || securityAlpha > mAnimationLastAlpha;
+ if (fullyFadeOut && mAnimationWaitsToShift) {
+ mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
+ mAnimationWaitsToShift = false;
+ }
+ mUserSwitcherViewGroup.setAlpha(securityAlpha);
+ mAnimationLastAlpha = securityAlpha;
+ });
+ } else {
+ translateSecurityViewLocation(leftAlign, animate);
+ mUserSwitcherViewGroup.setTranslationX(switcherTargetTranslation);
+ }
+ }
+
+ }
+
+ private void setGravity() {
if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
- updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
updateViewGravity(mUserSwitcherViewGroup, Gravity.CENTER_HORIZONTAL);
+ updateViewGravity(mViewFlipper, Gravity.CENTER_HORIZONTAL);
+ } else {
+ // horizontal gravity is the same because we translate these views anyway
+ updateViewGravity(mViewFlipper, Gravity.LEFT | Gravity.BOTTOM);
+ updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ }
+ }
+ private void setYTranslation() {
+ int yTrans = mResources.getDimensionPixelSize(R.dimen.bouncer_user_switcher_y_trans);
+ if (mResources.getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
mUserSwitcherViewGroup.setTranslationY(yTrans);
mViewFlipper.setTranslationY(-yTrans);
} else {
- updateViewGravity(mViewFlipper, Gravity.RIGHT | Gravity.BOTTOM);
- updateViewGravity(mUserSwitcherViewGroup, Gravity.LEFT | Gravity.CENTER_VERTICAL);
-
// Attempt to reposition a bit higher to make up for this frame being a bit lower
// on the device
mUserSwitcherViewGroup.setTranslationY(-yTrans);
@@ -1050,20 +1320,18 @@ public class KeyguardSecurityContainer extends FrameLayout {
* Logic to enabled one-handed bouncer mode. Supports animating the bouncer
* between alternate sides of the display.
*/
- static class OneHandedViewMode implements ViewMode {
- @Nullable private ValueAnimator mRunningOneHandedAnimator;
+ static class OneHandedViewMode extends SidedSecurityMode {
private ViewGroup mView;
private KeyguardSecurityViewFlipper mViewFlipper;
- private GlobalSettings mGlobalSettings;
@Override
public void init(@NonNull ViewGroup v, @NonNull GlobalSettings globalSettings,
@NonNull KeyguardSecurityViewFlipper viewFlipper,
@NonNull FalsingManager falsingManager,
@NonNull UserSwitcherController userSwitcherController) {
+ init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true);
mView = v;
mViewFlipper = viewFlipper;
- mGlobalSettings = globalSettings;
updateSecurityViewGravity();
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
@@ -1097,159 +1365,13 @@ public class KeyguardSecurityContainer extends FrameLayout {
updateSecurityViewLocation(isTouchOnLeft, /* animate= */false);
}
- boolean isLeftAligned() {
- return mGlobalSettings.getInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT)
- == Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
- }
-
- private void updateSideSetting(boolean leftAligned) {
- mGlobalSettings.putInt(
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- leftAligned ? Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT
- : Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- }
-
- /**
- * Determine if a tap on this view is on the other side. If so, will animate positions
- * and record the preference to always show on this side.
- */
- @Override
- public void handleTap(MotionEvent event) {
- float x = event.getX();
- boolean currentlyLeftAligned = isLeftAligned();
- // Did the tap hit the "other" side of the bouncer?
- if ((currentlyLeftAligned && (x > mView.getWidth() / 2f))
- || (!currentlyLeftAligned && (x < mView.getWidth() / 2f))) {
-
- boolean willBeLeftAligned = !currentlyLeftAligned;
- updateSideSetting(willBeLeftAligned);
-
- int keyguardState = willBeLeftAligned
- ? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_LEFT
- : SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SWITCH_RIGHT;
- SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, keyguardState);
-
- updateSecurityViewLocation(willBeLeftAligned, true /* animate */);
- }
- }
-
@Override
public void updateSecurityViewLocation() {
updateSecurityViewLocation(isLeftAligned(), /* animate= */false);
}
- /**
- * Moves the inner security view to the correct location (in one handed mode) with
- * animation. This is triggered when the user taps on the side of the screen that is not
- * currently occupied by the security view.
- */
- private void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
- if (mRunningOneHandedAnimator != null) {
- mRunningOneHandedAnimator.cancel();
- mRunningOneHandedAnimator = null;
- }
-
- int targetTranslation = leftAlign
- ? 0 : (int) (mView.getMeasuredWidth() - mViewFlipper.getWidth());
-
- if (animate) {
- // This animation is a bit fun to implement. The bouncer needs to move, and fade
- // in/out at the same time. The issue is, the bouncer should only move a short
- // amount (120dp or so), but obviously needs to go from one side of the screen to
- // the other. This needs a pretty custom animation.
- //
- // This works as follows. It uses a ValueAnimation to simply drive the animation
- // progress. This animator is responsible for both the translation of the bouncer,
- // and the current fade. It will fade the bouncer out while also moving it along the
- // 120dp path. Once the bouncer is fully faded out though, it will "snap" the
- // bouncer closer to its destination, then fade it back in again. The effect is that
- // the bouncer will move from 0 -> X while fading out, then
- // (destination - X) -> destination while fading back in again.
- // TODO(b/208250221): Make this animation properly abortable.
- Interpolator positionInterpolator = AnimationUtils.loadInterpolator(
- mView.getContext(), android.R.interpolator.fast_out_extra_slow_in);
- Interpolator fadeOutInterpolator = Interpolators.FAST_OUT_LINEAR_IN;
- Interpolator fadeInInterpolator = Interpolators.LINEAR_OUT_SLOW_IN;
-
- mRunningOneHandedAnimator = ValueAnimator.ofFloat(0.0f, 1.0f);
- mRunningOneHandedAnimator.setDuration(BOUNCER_HANDEDNESS_ANIMATION_DURATION_MS);
- mRunningOneHandedAnimator.setInterpolator(Interpolators.LINEAR);
-
- int initialTranslation = (int) mViewFlipper.getTranslationX();
- int totalTranslation = (int) mView.getResources().getDimension(
- R.dimen.one_handed_bouncer_move_animation_translation);
-
- final boolean shouldRestoreLayerType = mViewFlipper.hasOverlappingRendering()
- && mViewFlipper.getLayerType() != View.LAYER_TYPE_HARDWARE;
- if (shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_HARDWARE, /* paint= */null);
- }
-
- float initialAlpha = mViewFlipper.getAlpha();
-
- mRunningOneHandedAnimator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mRunningOneHandedAnimator = null;
- }
- });
- mRunningOneHandedAnimator.addUpdateListener(animation -> {
- float switchPoint = BOUNCER_HANDEDNESS_ANIMATION_FADE_OUT_PROPORTION;
- boolean isFadingOut = animation.getAnimatedFraction() < switchPoint;
-
- int currentTranslation = (int) (positionInterpolator.getInterpolation(
- animation.getAnimatedFraction()) * totalTranslation);
- int translationRemaining = totalTranslation - currentTranslation;
-
- // Flip the sign if we're going from right to left.
- if (leftAlign) {
- currentTranslation = -currentTranslation;
- translationRemaining = -translationRemaining;
- }
-
- if (isFadingOut) {
- // The bouncer fades out over the first X%.
- float fadeOutFraction = MathUtils.constrainedMap(
- /* rangeMin= */1.0f,
- /* rangeMax= */0.0f,
- /* valueMin= */0.0f,
- /* valueMax= */switchPoint,
- animation.getAnimatedFraction());
- float opacity = fadeOutInterpolator.getInterpolation(fadeOutFraction);
-
- // When fading out, the alpha needs to start from the initial opacity of the
- // view flipper, otherwise we get a weird bit of jank as it ramps back to
- // 100%.
- mViewFlipper.setAlpha(opacity * initialAlpha);
-
- // Animate away from the source.
- mViewFlipper.setTranslationX(initialTranslation + currentTranslation);
- } else {
- // And in again over the remaining (100-X)%.
- float fadeInFraction = MathUtils.constrainedMap(
- /* rangeMin= */0.0f,
- /* rangeMax= */1.0f,
- /* valueMin= */switchPoint,
- /* valueMax= */1.0f,
- animation.getAnimatedFraction());
-
- float opacity = fadeInInterpolator.getInterpolation(fadeInFraction);
- mViewFlipper.setAlpha(opacity);
-
- // Fading back in, animate towards the destination.
- mViewFlipper.setTranslationX(targetTranslation - translationRemaining);
- }
-
- if (animation.getAnimatedFraction() == 1.0f && shouldRestoreLayerType) {
- mViewFlipper.setLayerType(View.LAYER_TYPE_NONE, /* paint= */null);
- }
- });
-
- mRunningOneHandedAnimator.start();
- } else {
- mViewFlipper.setTranslationX(targetTranslation);
- }
+ protected void updateSecurityViewLocation(boolean leftAlign, boolean animate) {
+ translateSecurityViewLocation(leftAlign, animate);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 19a2d9ed5b7b..5ee659be6dd2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -17,6 +17,7 @@
package com.android.keyguard;
import static android.app.StatusBarManager.SESSION_KEYGUARD;
+import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
@@ -32,11 +33,13 @@ import android.app.admin.DevicePolicyManager;
import android.content.Intent;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
+import android.hardware.biometrics.BiometricSourceType;
import android.metrics.LogMaker;
import android.os.UserHandle;
import android.util.Log;
import android.util.Slog;
import android.view.MotionEvent;
+import android.view.View;
import androidx.annotation.Nullable;
@@ -55,6 +58,7 @@ import com.android.keyguard.dagger.KeyguardBouncerScope;
import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
+import com.android.systemui.biometrics.SidefpsController;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -67,6 +71,8 @@ import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.ViewController;
import com.android.systemui.util.settings.GlobalSettings;
+import java.util.Optional;
+
import javax.inject.Inject;
/** Controller for {@link KeyguardSecurityContainer} */
@@ -93,6 +99,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final GlobalSettings mGlobalSettings;
private final FeatureFlags mFeatureFlags;
private final SessionTracker mSessionTracker;
+ private final Optional<SidefpsController> mSidefpsController;
private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
@@ -116,12 +123,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
// If we're in one handed mode, the user can tap on the opposite side of the screen
// to move the bouncer across. In that case, inhibit the falsing (otherwise the taps
// to move the bouncer to each screen side can end up closing it instead).
- if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
- boolean isLeftAligned = mView.isOneHandedModeLeftAligned();
- if ((isLeftAligned && ev.getX() > mView.getWidth() / 2f)
- || (!isLeftAligned && ev.getX() <= mView.getWidth() / 2f)) {
- mFalsingCollector.avoidGesture();
- }
+ if (mView.isTouchOnTheOtherSideOfSecurity(ev)) {
+ mFalsingCollector.avoidGesture();
}
if (mTouchDown != null) {
@@ -169,8 +172,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void reportUnlockAttempt(int userId, boolean success, int timeoutMs) {
int bouncerSide = SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__DEFAULT;
- if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
- bouncerSide = mView.isOneHandedModeLeftAligned()
+ if (mView.isSidedSecurityMode()) {
+ bouncerSide = mView.isSecurityLeftAligned()
? SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_PASSWORD_ENTERED__SIDE__RIGHT;
}
@@ -240,13 +243,27 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
reloadColors();
}
};
+ private boolean mBouncerVisible = false;
private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
new KeyguardUpdateMonitorCallback() {
- @Override
- public void onDevicePolicyManagerStateChanged() {
- showPrimarySecurityScreen(false);
- }
- };
+ @Override
+ public void onDevicePolicyManagerStateChanged() {
+ showPrimarySecurityScreen(false);
+ }
+
+ @Override
+ public void onBiometricRunningStateChanged(boolean running,
+ BiometricSourceType biometricSourceType) {
+ if (biometricSourceType == FINGERPRINT) {
+ updateSideFpsVisibility();
+ }
+ }
+
+ @Override
+ public void onStrongAuthStateChanged(int userId) {
+ updateSideFpsVisibility();
+ }
+ };
private KeyguardSecurityContainerController(KeyguardSecurityContainer view,
AdminSecondaryLockScreenController.Factory adminSecondaryLockScreenControllerFactory,
@@ -264,7 +281,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
GlobalSettings globalSettings,
- SessionTracker sessionTracker) {
+ SessionTracker sessionTracker,
+ Optional<SidefpsController> sidefpsController) {
super(view);
mLockPatternUtils = lockPatternUtils;
mUpdateMonitor = keyguardUpdateMonitor;
@@ -284,6 +302,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mFeatureFlags = featureFlags;
mGlobalSettings = globalSettings;
mSessionTracker = sessionTracker;
+ mSidefpsController = sidefpsController;
}
@Override
@@ -315,8 +334,23 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
getCurrentSecurityController().onPause();
}
mView.onPause();
+ // It might happen that onStartingToHide is not called when the device is locked while on
+ // bouncer.
+ setBouncerVisible(false);
}
+ private void updateSideFpsVisibility() {
+ if (!mSidefpsController.isPresent()) {
+ return;
+ }
+ if (mBouncerVisible && mView.isSidedSecurityMode()
+ && mUpdateMonitor.isFingerprintDetectionRunning()
+ && !mUpdateMonitor.userNeedsStrongAuth()) {
+ mSidefpsController.get().show();
+ } else {
+ mSidefpsController.get().hide();
+ }
+ }
/**
* Shows the primary security screen for the user. This will be either the multi-selector
@@ -367,8 +401,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
public void onResume(int reason) {
if (mCurrentSecurityMode != SecurityMode.None) {
int state = SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN;
- if (mView.getMode() == KeyguardSecurityContainer.MODE_ONE_HANDED) {
- state = mView.isOneHandedModeLeftAligned()
+ if (mView.isSidedSecurityMode()) {
+ state = mView.isSecurityLeftAligned()
? SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_LEFT
: SysUiStatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN_RIGHT;
}
@@ -401,6 +435,17 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
if (mCurrentSecurityMode != SecurityMode.None) {
getCurrentSecurityController().onStartingToHide();
}
+ setBouncerVisible(false);
+ }
+
+ /** Called when the bouncer changes visibility. */
+ public void onBouncerVisibilityChanged(@View.Visibility int visibility) {
+ setBouncerVisible(visibility == View.VISIBLE);
+ }
+
+ private void setBouncerVisible(boolean visible) {
+ mBouncerVisible = visible;
+ updateSideFpsVisibility();
}
/**
@@ -659,6 +704,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
private final FeatureFlags mFeatureFlags;
private final UserSwitcherController mUserSwitcherController;
private final SessionTracker mSessionTracker;
+ private final Optional<SidefpsController> mSidefpsController;
@Inject
Factory(KeyguardSecurityContainer view,
@@ -677,7 +723,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
UserSwitcherController userSwitcherController,
FeatureFlags featureFlags,
GlobalSettings globalSettings,
- SessionTracker sessionTracker) {
+ SessionTracker sessionTracker,
+ Optional<SidefpsController> sidefpsController) {
mView = view;
mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
mLockPatternUtils = lockPatternUtils;
@@ -694,6 +741,7 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mGlobalSettings = globalSettings;
mUserSwitcherController = userSwitcherController;
mSessionTracker = sessionTracker;
+ mSidefpsController = sidefpsController;
}
public KeyguardSecurityContainerController create(
@@ -703,7 +751,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard
mKeyguardUpdateMonitor, mKeyguardSecurityModel, mMetricsLogger, mUiEventLogger,
mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
- mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker);
+ mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker,
+ mSidefpsController);
}
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 47df70b522f7..2f6fa145664e 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+
import android.annotation.NonNull;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
@@ -241,10 +243,9 @@ public class KeyguardSimPinViewController
displayMessage = mView.getResources().getString(
R.string.kg_password_wrong_pin_code_pukked);
} else if (attemptsRemaining > 0) {
- msgId = isDefault ? R.plurals.kg_password_default_pin_message :
- R.plurals.kg_password_wrong_pin_code;
- displayMessage = mView.getResources()
- .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
+ msgId = isDefault ? R.string.kg_password_default_pin_message :
+ R.string.kg_password_wrong_pin_code;
+ displayMessage = icuMessageFormat(mView.getResources(), msgId, attemptsRemaining);
} else {
msgId = isDefault ? R.string.kg_sim_pin_instructions : R.string.kg_password_pin_failed;
displayMessage = mView.getResources().getString(msgId);
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
index 760c2ccb71b5..c0971bf8c16d 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPukView.java
@@ -16,6 +16,8 @@
package com.android.keyguard;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
@@ -57,10 +59,9 @@ public class KeyguardSimPukView extends KeyguardPinBasedInputView {
if (attemptsRemaining == 0) {
displayMessage = getContext().getString(R.string.kg_password_wrong_puk_code_dead);
} else if (attemptsRemaining > 0) {
- int msgId = isDefault ? R.plurals.kg_password_default_puk_message :
- R.plurals.kg_password_wrong_puk_code;
- displayMessage = getContext().getResources()
- .getQuantityString(msgId, attemptsRemaining, attemptsRemaining);
+ int msgId = isDefault ? R.string.kg_password_default_puk_message :
+ R.string.kg_password_wrong_puk_code;
+ displayMessage = icuMessageFormat(getResources(), msgId, attemptsRemaining);
} else {
int msgId = isDefault ? R.string.kg_puk_enter_puk_hint :
R.string.kg_password_puk_failed;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
index 8921780fdacf..014d08288158 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusViewController.java
@@ -240,11 +240,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
}
@Override
- public void onTimeFormatChanged(String timeFormat) {
- mKeyguardClockSwitchController.refreshFormat();
- }
-
- @Override
public void onTimeZoneChanged(TimeZone timeZone) {
mKeyguardClockSwitchController.updateTimeZone(timeZone);
}
@@ -256,11 +251,6 @@ public class KeyguardStatusViewController extends ViewController<KeyguardStatusV
refreshTime();
}
}
-
- @Override
- public void onUserSwitchComplete(int userId) {
- mKeyguardClockSwitchController.refreshFormat();
- }
};
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 727d108df339..4ae2cad4a01f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -41,7 +41,6 @@ import android.app.ActivityManager;
import android.app.ActivityTaskManager;
import android.app.ActivityTaskManager.RootTaskInfo;
import android.app.AlarmManager;
-import android.app.PendingIntent;
import android.app.UserSwitchObserver;
import android.app.admin.DevicePolicyManager;
import android.app.trust.TrustManager;
@@ -150,11 +149,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final boolean DEBUG_SPEW = false;
private static final int BIOMETRIC_LOCKOUT_RESET_DELAY_MS = 600;
- private static final String ACTION_FACE_UNLOCK_STARTED
- = "com.android.facelock.FACE_UNLOCK_STARTED";
- private static final String ACTION_FACE_UNLOCK_STOPPED
- = "com.android.facelock.FACE_UNLOCK_STOPPED";
-
// Callback messages
private static final int MSG_TIME_UPDATE = 301;
private static final int MSG_BATTERY_UPDATE = 302;
@@ -165,13 +159,11 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private static final int MSG_USER_SWITCHING = 310;
private static final int MSG_KEYGUARD_RESET = 312;
private static final int MSG_USER_SWITCH_COMPLETE = 314;
- private static final int MSG_USER_INFO_CHANGED = 317;
private static final int MSG_REPORT_EMERGENCY_CALL_ACTION = 318;
private static final int MSG_STARTED_WAKING_UP = 319;
private static final int MSG_FINISHED_GOING_TO_SLEEP = 320;
private static final int MSG_STARTED_GOING_TO_SLEEP = 321;
private static final int MSG_KEYGUARD_BOUNCER_CHANGED = 322;
- private static final int MSG_FACE_UNLOCK_STATE_CHANGED = 327;
private static final int MSG_SIM_SUBSCRIPTION_INFO_CHANGED = 328;
private static final int MSG_AIRPLANE_MODE_CHANGED = 329;
private static final int MSG_SERVICE_STATE_CHANGE = 330;
@@ -250,7 +242,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final Context mContext;
private final boolean mIsPrimaryUser;
- private final boolean mIsAutomotive;
private final AuthController mAuthController;
private final StatusBarStateController mStatusBarStateController;
private int mStatusBarState;
@@ -328,8 +319,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private final LatencyTracker mLatencyTracker;
private boolean mLogoutEnabled;
private boolean mIsFaceEnrolled;
- // If the user long pressed the lock icon, disabling face auth for the current session.
- private boolean mLockIconPressed;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
private final Executor mBackgroundExecutor;
private SensorPrivacyManager mSensorPrivacyManager;
@@ -408,7 +397,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private SparseBooleanArray mUserHasTrust = new SparseBooleanArray();
private SparseBooleanArray mUserTrustIsManaged = new SparseBooleanArray();
private SparseBooleanArray mUserTrustIsUsuallyManaged = new SparseBooleanArray();
- private SparseBooleanArray mUserFaceUnlockRunning = new SparseBooleanArray();
private Map<Integer, Intent> mSecondaryLockscreenRequirement = new HashMap<Integer, Intent>();
@VisibleForTesting
@@ -817,8 +805,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
private Runnable mRetryFingerprintAuthentication = new Runnable() {
@Override
public void run() {
- Log.w(TAG, "Retrying fingerprint after HW unavailable, attempt " +
- mHardwareFingerprintUnavailableRetryCount);
+ Log.w(TAG,
+ "Retrying fingerprint attempt: " + mHardwareFingerprintUnavailableRetryCount);
if (mFpm.isHardwareDetected()) {
updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
} else if (mHardwareFingerprintUnavailableRetryCount < HAL_ERROR_RETRY_MAX) {
@@ -845,7 +833,9 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
}
- if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE) {
+ if (msgId == FingerprintManager.FINGERPRINT_ERROR_HW_UNAVAILABLE
+ || msgId == FingerprintManager.BIOMETRIC_ERROR_POWER_PRESSED) {
+ Log.d(TAG, "Fingerprint retrying auth due to(" + msgId + ") -> " + errString);
mHandler.postDelayed(mRetryFingerprintAuthentication, HAL_ERROR_RETRY_TIMEOUT);
}
@@ -1135,21 +1125,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- private void handleFaceUnlockStateChanged(boolean running, int userId) {
- Assert.isMainThread();
- mUserFaceUnlockRunning.put(userId, running);
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onFaceUnlockStateChanged(running, userId);
- }
- }
- }
-
- public boolean isFaceUnlockRunning(int userId) {
- return mUserFaceUnlockRunning.get(userId);
- }
-
public boolean isFingerprintDetectionRunning() {
return mFingerprintRunningState == BIOMETRIC_STATE_RUNNING;
}
@@ -1373,16 +1348,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- static class DisplayClientState {
- public int clientGeneration;
- public boolean clearing;
- public PendingIntent intent;
- public int playbackState;
- public long playbackEventTime;
- }
-
- private DisplayClientState mDisplayClientState = new DisplayClientState();
-
@VisibleForTesting
protected final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -1456,19 +1421,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
final String action = intent.getAction();
if (AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED.equals(action)) {
mHandler.sendEmptyMessage(MSG_TIME_UPDATE);
- } else if (Intent.ACTION_USER_INFO_CHANGED.equals(action)) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_USER_INFO_CHANGED,
- intent.getIntExtra(Intent.EXTRA_USER_HANDLE, getSendingUserId()), 0));
- } else if (ACTION_FACE_UNLOCK_STARTED.equals(action)) {
- Trace.beginSection(
- "KeyguardUpdateMonitor.mBroadcastAllReceiver#onReceive "
- + "ACTION_FACE_UNLOCK_STARTED");
- mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 1,
- getSendingUserId()));
- Trace.endSection();
- } else if (ACTION_FACE_UNLOCK_STOPPED.equals(action)) {
- mHandler.sendMessage(mHandler.obtainMessage(MSG_FACE_UNLOCK_STATE_CHANGED, 0,
- getSendingUserId()));
} else if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED
.equals(action)) {
mHandler.sendMessage(mHandler.obtainMessage(MSG_DPM_STATE_CHANGED,
@@ -1767,7 +1719,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
protected void handleStartedGoingToSleep(int arg1) {
Assert.isMainThread();
- mLockIconPressed = false;
clearBiometricRecognized();
for (int i = 0; i < mCallbacks.size(); i++) {
KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
@@ -1815,16 +1766,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- private void handleUserInfoChanged(int userId) {
- Assert.isMainThread();
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onUserInfoChanged(userId);
- }
- }
- }
-
private void handleUserUnlocked(int userId) {
Assert.isMainThread();
mUserIsUnlocked.put(userId, true);
@@ -1942,9 +1883,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
case MSG_KEYGUARD_BOUNCER_CHANGED:
handleKeyguardBouncerChanged(msg.arg1, msg.arg2);
break;
- case MSG_USER_INFO_CHANGED:
- handleUserInfoChanged(msg.arg1);
- break;
case MSG_REPORT_EMERGENCY_CALL_ACTION:
handleReportEmergencyCallAction();
break;
@@ -1959,12 +1897,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
handleStartedWakingUp();
Trace.endSection();
break;
- case MSG_FACE_UNLOCK_STATE_CHANGED:
- Trace.beginSection(
- "KeyguardUpdateMonitor#handler MSG_FACE_UNLOCK_STATE_CHANGED");
- handleFaceUnlockStateChanged(msg.arg1 != 0, msg.arg2);
- Trace.endSection();
- break;
case MSG_SIM_SUBSCRIPTION_INFO_CHANGED:
handleSimSubscriptionInfoChanged();
break;
@@ -2052,24 +1984,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
.getServiceStateForSubscriber(subId);
mHandler.sendMessage(
mHandler.obtainMessage(MSG_SERVICE_STATE_CHANGE, subId, 0, serviceState));
-
- // Get initial state. Relying on Sticky behavior until API for getting info.
- if (mBatteryStatus == null) {
- Intent intent = mContext.registerReceiver(
- null,
- new IntentFilter(Intent.ACTION_BATTERY_CHANGED)
- );
- if (intent != null && mBatteryStatus == null) {
- mBroadcastReceiver.onReceive(mContext, intent);
- }
- }
});
final IntentFilter allUserFilter = new IntentFilter();
- allUserFilter.addAction(Intent.ACTION_USER_INFO_CHANGED);
allUserFilter.addAction(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
- allUserFilter.addAction(ACTION_FACE_UNLOCK_STARTED);
- allUserFilter.addAction(ACTION_FACE_UNLOCK_STOPPED);
allUserFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
allUserFilter.addAction(ACTION_USER_UNLOCKED);
allUserFilter.addAction(ACTION_USER_STOPPED);
@@ -2127,8 +2045,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mFaceManager.addLockoutResetCallback(mFaceLockoutResetCallback);
}
- mIsAutomotive = isAutomotive();
-
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
mUserManager = context.getSystemService(UserManager.class);
mIsPrimaryUser = mUserManager.isPrimaryUser();
@@ -2629,7 +2545,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| mAuthController.isUdfpsFingerDown()
|| mUdfpsBouncerShowing)
&& !mSwitchingUser && !faceDisabledForUser && becauseCannotSkipBouncer
- && !mKeyguardGoingAway && biometricEnabledForUser && !mLockIconPressed
+ && !mKeyguardGoingAway && biometricEnabledForUser
&& strongAuthAllowsScanning && mIsPrimaryUser
&& (!mSecureCameraLaunched || mOccludingAppRequestingFace)
&& !faceAuthenticated
@@ -2652,7 +2568,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
awakeKeyguard,
mKeyguardGoingAway,
shouldListenForFaceAssistant,
- mLockIconPressed,
mOccludingAppRequestingFace,
mIsPrimaryUser,
strongAuthAllowsScanning,
@@ -2697,18 +2612,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
}
}
- /**
- * Whenever the lock icon is long pressed, disabling trust agents.
- * This means that we cannot auth passively (face) until the user presses power.
- */
- public void onLockIconPressed() {
- mLockIconPressed = true;
- final int userId = getCurrentUser();
- mUserFaceAuthenticated.put(userId, null);
- updateFaceListeningState(BIOMETRIC_ACTION_UPDATE);
- mStrongAuthTracker.onStrongAuthRequiredChanged(userId);
- }
-
private void startListeningForFingerprint() {
final int userId = getCurrentUser();
final boolean unlockPossible = isUnlockWithFingerprintPossible(userId);
@@ -3179,20 +3082,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
updateBiometricListeningState(BIOMETRIC_ACTION_UPDATE);
}
- /** Notifies that the occluded state changed. */
- public void onKeyguardOccludedChanged(boolean occluded) {
- Assert.isMainThread();
- if (DEBUG) {
- Log.d(TAG, "onKeyguardOccludedChanged(" + occluded + ")");
- }
- for (int i = 0; i < mCallbacks.size(); i++) {
- KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get();
- if (cb != null) {
- cb.onKeyguardOccludedChanged(occluded);
- }
- }
- }
-
/**
* Handle {@link #MSG_KEYGUARD_RESET}
*/
@@ -3347,10 +3236,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
return false;
}
- private boolean isAutomotive() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE);
- }
-
/**
* Remove the given observer's callback.
*
@@ -3413,8 +3298,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
callback.onTimeChanged();
callback.onPhoneStateChanged(mPhoneState);
callback.onRefreshCarrierInfo();
- callback.onClockVisibilityChanged();
- callback.onKeyguardOccludedChanged(mKeyguardOccluded);
callback.onKeyguardVisibilityChangedRaw(mKeyguardIsVisible);
callback.onTelephonyCapable(mTelephonyCapable);
@@ -3591,10 +3474,6 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
|| state == TelephonyManager.SIM_STATE_PERM_DISABLED);
}
- public DisplayClientState getCachedDisplayClientState() {
- return mDisplayClientState;
- }
-
// TODO: use these callbacks elsewhere in place of the existing notifyScreen*()
// (KeyguardViewMediator, KeyguardHostView)
public void dispatchStartedWakingUp() {
@@ -3643,6 +3522,10 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
mHandler.sendEmptyMessage(MSG_KEYGUARD_DISMISS_ANIMATION_FINISHED);
}
+ /**
+ * @return true when the screen is on (including when a screensaver is showing),
+ * false when the screen is OFF or DOZE (including showing AOD UI)
+ */
public boolean isDeviceInteractive() {
return mDeviceInteractive;
}
@@ -3785,6 +3668,8 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" mFingerprintLockedOut=" + mFingerprintLockedOut);
pw.println(" mFingerprintLockedOutPermanent=" + mFingerprintLockedOutPermanent);
pw.println(" enabledByUser=" + mBiometricEnabledForUser.get(userId));
+ pw.println(" mKeyguardOccluded=" + mKeyguardOccluded);
+ pw.println(" mIsDreaming=" + mIsDreaming);
if (isUdfpsSupported()) {
pw.println(" udfpsEnrolled=" + isUdfpsEnrolled());
pw.println(" shouldListenForUdfps=" + shouldListenForFingerprint(true));
@@ -3817,9 +3702,5 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab
pw.println(" mNeedsSlowUnlockTransition=" + mNeedsSlowUnlockTransition);
}
mListenModels.print(pw);
-
- if (mIsAutomotive) {
- pw.println(" Running on Automotive build");
- }
}
}
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 051b81e484d8..99e0ce29a8c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -80,12 +80,6 @@ public class KeyguardUpdateMonitorCallback {
*/
public void onKeyguardVisibilityChanged(boolean showing) { }
- /**
- * Called when the keyguard occluded state changes.
- * @param occluded Indicates if the keyguard is now occluded.
- */
- public void onKeyguardOccludedChanged(boolean occluded) { }
-
public void onKeyguardVisibilityChangedRaw(boolean showing) {
final long now = SystemClock.elapsedRealtime();
if (showing == mShowing
@@ -117,12 +111,6 @@ public class KeyguardUpdateMonitorCallback {
public void onKeyguardDismissAnimationFinished() { }
/**
- * Called when visibility of lockscreen clock changes, such as when
- * obscured by a widget.
- */
- public void onClockVisibilityChanged() { }
-
- /**
* Called when the device becomes provisioned
*/
public void onDeviceProvisioned() { }
@@ -151,11 +139,6 @@ public class KeyguardUpdateMonitorCallback {
public void onSimStateChanged(int subId, int slotId, int simState) { }
/**
- * Called when the user's info changed.
- */
- public void onUserInfoChanged(int userId) { }
-
- /**
* Called when a user got unlocked.
*/
public void onUserUnlocked() { }
@@ -260,11 +243,6 @@ public class KeyguardUpdateMonitorCallback {
BiometricSourceType biometricSourceType) { }
/**
- * Called when the state of face unlock changed.
- */
- public void onFaceUnlockStateChanged(boolean running, int userId) { }
-
- /**
* Called when biometric running state changed.
*/
public void onBiometricRunningStateChanged(boolean running,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index ca8728aecb4c..8293c74c5e75 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -23,10 +23,10 @@ import android.view.ViewRootImpl;
import androidx.annotation.Nullable;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
/**
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index c1b2aba22b57..06e1828ef9f4 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -23,7 +23,6 @@ import static com.android.keyguard.LockIconView.ICON_LOCK;
import static com.android.keyguard.LockIconView.ICON_UNLOCK;
import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset;
import android.content.res.Configuration;
import android.content.res.Resources;
@@ -403,7 +402,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
float offsetY = MathUtils.lerp(0f,
getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */)
- mMaxBurnInOffsetY, mInterpolatedDarkAmount);
- float progress = MathUtils.lerp(0f, getBurnInProgressOffset(), mInterpolatedDarkAmount);
mView.setTranslationX(offsetX);
mView.setTranslationY(offsetY);
@@ -652,7 +650,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
Process.myUid(),
getContext().getOpPackageName(),
UdfpsController.EFFECT_CLICK,
- "lock-icon-device-entry",
+ "lock-screen-lock-icon-longpress",
TOUCH_VIBRATION_ATTRIBUTES);
mKeyguardViewController.showBouncer(/* scrim */ true);
@@ -677,6 +675,12 @@ public class LockIconViewController extends ViewController<LockIconView> impleme
}
private boolean isActionable() {
+ if (mIsBouncerShowing) {
+ Log.v(TAG, "lock icon long-press ignored, bouncer already showing.");
+ // a long press gestures from AOD may have already triggered the bouncer to show,
+ // so this touch is no longer actionable
+ return false;
+ }
return mUdfpsSupported || mShowUnlockIcon;
}
diff --git a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
index 013cdac94ab8..9a0bfc19f848 100644
--- a/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
+++ b/packages/SystemUI/src/com/android/keyguard/clock/ClockManager.java
@@ -53,8 +53,11 @@ import javax.inject.Inject;
/**
* Manages custom clock faces for AOD and lock screen.
+ *
+ * @deprecated Migrate to ClockRegistry
*/
@SysUISingleton
+@Deprecated
public final class ClockManager {
private static final String TAG = "ClockOptsProvider";
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
index b3c11584bcf8..49e97836b18b 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardBouncerModule.java
@@ -16,6 +16,10 @@
package com.android.keyguard.dagger;
+import static com.android.systemui.biometrics.SidefpsControllerKt.hasSideFpsSensor;
+
+import android.annotation.Nullable;
+import android.hardware.fingerprint.FingerprintManager;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -23,9 +27,14 @@ import com.android.keyguard.KeyguardHostView;
import com.android.keyguard.KeyguardSecurityContainer;
import com.android.keyguard.KeyguardSecurityViewFlipper;
import com.android.systemui.R;
+import com.android.systemui.biometrics.SidefpsController;
import com.android.systemui.dagger.qualifiers.RootView;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import java.util.Optional;
+
+import javax.inject.Provider;
+
import dagger.Module;
import dagger.Provides;
@@ -60,4 +69,16 @@ public interface KeyguardBouncerModule {
KeyguardSecurityContainer containerView) {
return containerView.findViewById(R.id.view_flipper);
}
+
+ /** Provides {@link SidefpsController} if the device has the side fingerprint sensor. */
+ @Provides
+ @KeyguardBouncerScope
+ static Optional<SidefpsController> providesOptionalSidefpsController(
+ @Nullable FingerprintManager fingerprintManager,
+ Provider<SidefpsController> sidefpsControllerProvider) {
+ if (!hasSideFpsSensor(fingerprintManager)) {
+ return Optional.empty();
+ }
+ return Optional.of(sidefpsControllerProvider.get());
+ }
}
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
index 153da4b6695a..d01c98a934ff 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/KeyguardStatusBarViewComponent.java
@@ -17,9 +17,9 @@
package com.android.keyguard.dagger;
import com.android.keyguard.KeyguardStatusViewController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import dagger.BindsInstance;
import dagger.Subcomponent;
diff --git a/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java b/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java
index b2d5c216dcd1..74d7a8b03b7d 100644
--- a/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java
+++ b/packages/SystemUI/src/com/android/systemui/Gefingerpoken.java
@@ -20,6 +20,13 @@ import android.view.MotionEvent;
// ACHTUNG!
public interface Gefingerpoken {
- boolean onInterceptTouchEvent(MotionEvent ev);
- boolean onTouchEvent(MotionEvent ev);
+ /** Called when a touch is being intercepted in a ViewGroup. */
+ default boolean onInterceptTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ /** Called when a touch is being handled by a view. */
+ default boolean onTouchEvent(MotionEvent ev) {
+ return false;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
new file mode 100644
index 000000000000..fd84543ee50b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/GuestResetOrExitSessionReceiver.java
@@ -0,0 +1,269 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.UserInfo;
+import android.os.UserHandle;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.qs.QSUserSwitcherEvent;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+import com.android.systemui.statusbar.policy.UserSwitcherController;
+
+import javax.inject.Inject;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
+/**
+ * Manages handling of guest session persistent notification
+ * and actions to reset guest or exit guest session
+ */
+public final class GuestResetOrExitSessionReceiver extends BroadcastReceiver {
+
+ private static final String TAG = GuestResetOrExitSessionReceiver.class.getSimpleName();
+
+ /**
+ * Broadcast sent to the system when guest user needs to be reset.
+ * This is only sent to registered receivers, not manifest receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_GUEST_RESET = "android.intent.action.GUEST_RESET";
+
+ /**
+ * Broadcast sent to the system when guest user needs to exit.
+ * This is only sent to registered receivers, not manifest receivers.
+ *
+ * @hide
+ */
+ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+ public static final String ACTION_GUEST_EXIT = "android.intent.action.GUEST_EXIT";
+
+ public AlertDialog mExitSessionDialog;
+ public AlertDialog mResetSessionDialog;
+ private final UserTracker mUserTracker;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+ private final ExitSessionDialog.Factory mExitSessionDialogFactory;
+
+ @Inject
+ public GuestResetOrExitSessionReceiver(UserTracker userTracker,
+ BroadcastDispatcher broadcastDispatcher,
+ ResetSessionDialog.Factory resetSessionDialogFactory,
+ ExitSessionDialog.Factory exitSessionDialogFactory) {
+ mUserTracker = userTracker;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mResetSessionDialogFactory = resetSessionDialogFactory;
+ mExitSessionDialogFactory = exitSessionDialogFactory;
+ }
+
+ /**
+ * Register this receiver with the {@link BroadcastDispatcher}
+ */
+ public void register() {
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_GUEST_RESET);
+ intentFilter.addAction(ACTION_GUEST_EXIT);
+ mBroadcastDispatcher.registerReceiver(this, intentFilter, null /* handler */,
+ UserHandle.SYSTEM);
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+
+ cancelResetDialog();
+ cancelExitDialog();
+
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (!currentUser.isGuest()) {
+ return;
+ }
+
+ if (ACTION_GUEST_RESET.equals(action)) {
+ mResetSessionDialog = mResetSessionDialogFactory.create(currentUser.id);
+ mResetSessionDialog.show();
+ } else if (ACTION_GUEST_EXIT.equals(action)) {
+ mExitSessionDialog = mExitSessionDialogFactory.create(currentUser.id,
+ currentUser.isEphemeral());
+ mExitSessionDialog.show();
+ }
+ }
+
+ private void cancelResetDialog() {
+ if (mResetSessionDialog != null && mResetSessionDialog.isShowing()) {
+ mResetSessionDialog.cancel();
+ mResetSessionDialog = null;
+ }
+ }
+
+ private void cancelExitDialog() {
+ if (mExitSessionDialog != null && mExitSessionDialog.isShowing()) {
+ mExitSessionDialog.cancel();
+ mExitSessionDialog = null;
+ }
+ }
+
+ /**
+ * Dialog shown when asking for confirmation before
+ * reset and restart of guest user.
+ */
+ public static final class ResetSessionDialog extends SystemUIDialog implements
+ DialogInterface.OnClickListener {
+
+ private final UserSwitcherController mUserSwitcherController;
+ private final UiEventLogger mUiEventLogger;
+ private final int mUserId;
+
+ /** Factory class to create guest reset dialog instance */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a guest reset dialog instance */
+ ResetSessionDialog create(int userId);
+ }
+
+ @AssistedInject
+ ResetSessionDialog(Context context,
+ UserSwitcherController userSwitcherController,
+ UiEventLogger uiEventLogger,
+ @Assisted int userId) {
+ super(context);
+
+ setTitle(com.android.settingslib.R.string.guest_reset_and_restart_dialog_title);
+ setMessage(context.getString(
+ com.android.settingslib.R.string.guest_reset_and_restart_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_reset_guest_confirm_button), this);
+ setCanceledOnTouchOutside(false);
+
+ mUserSwitcherController = userSwitcherController;
+ mUiEventLogger = uiEventLogger;
+ mUserId = userId;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+ mUserSwitcherController.removeGuestUser(mUserId, UserHandle.USER_NULL);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ cancel();
+ }
+ }
+ }
+
+ /**
+ * Dialog shown when asking for confirmation before
+ * exit of guest user.
+ */
+ public static final class ExitSessionDialog extends SystemUIDialog implements
+ DialogInterface.OnClickListener {
+
+ private final UserSwitcherController mUserSwitcherController;
+ private final int mUserId;
+ private boolean mIsEphemeral;
+
+ /** Factory class to create guest exit dialog instance */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a guest exit dialog instance */
+ ExitSessionDialog create(int userId, boolean isEphemeral);
+ }
+
+ @AssistedInject
+ ExitSessionDialog(Context context,
+ UserSwitcherController userSwitcherController,
+ @Assisted int userId,
+ @Assisted boolean isEphemeral) {
+ super(context);
+
+ if (isEphemeral) {
+ setTitle(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_title));
+ setMessage(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_button), this);
+ } else {
+ setTitle(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_title_non_ephemeral));
+ setMessage(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_message_non_ephemeral));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_clear_data_button), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_save_data_button), this);
+ }
+ setCanceledOnTouchOutside(false);
+
+ mUserSwitcherController = userSwitcherController;
+ mUserId = userId;
+ mIsEphemeral = isEphemeral;
+ }
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (mIsEphemeral) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Ephemeral guest: exit guest, guest is removed by the system
+ // on exit, since its marked ephemeral
+ mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ } else {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ // Non-ephemeral guest: exit guest, guest is not removed by the system
+ // on exit, since its marked non-ephemeral
+ mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, false);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ // Non-ephemeral guest: remove guest and then exit
+ mUserSwitcherController.exitGuestUser(mUserId, UserHandle.USER_NULL, true);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
index 9a6020f8556b..76a7cad15419 100644
--- a/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/GuestResumeSessionReceiver.java
@@ -35,12 +35,18 @@ import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.statusbar.policy.UserSwitcherController;
import com.android.systemui.util.settings.SecureSettings;
+import javax.inject.Inject;
+
+import dagger.assisted.Assisted;
+import dagger.assisted.AssistedFactory;
+import dagger.assisted.AssistedInject;
+
/**
* Manages notification when a guest session is resumed.
*/
public class GuestResumeSessionReceiver extends BroadcastReceiver {
- private static final String TAG = "GuestResumeSessionReceiver";
+ private static final String TAG = GuestResumeSessionReceiver.class.getSimpleName();
@VisibleForTesting
public static final String SETTING_GUEST_HAS_LOGGED_IN = "systemui.guest_has_logged_in";
@@ -48,27 +54,31 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver {
@VisibleForTesting
public AlertDialog mNewSessionDialog;
private final UserTracker mUserTracker;
- private final UserSwitcherController mUserSwitcherController;
- private final UiEventLogger mUiEventLogger;
private final SecureSettings mSecureSettings;
-
- public GuestResumeSessionReceiver(UserSwitcherController userSwitcherController,
- UserTracker userTracker, UiEventLogger uiEventLogger,
- SecureSettings secureSettings) {
- mUserSwitcherController = userSwitcherController;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final ResetSessionDialog.Factory mResetSessionDialogFactory;
+ private final GuestSessionNotification mGuestSessionNotification;
+
+ @Inject
+ public GuestResumeSessionReceiver(
+ UserTracker userTracker,
+ SecureSettings secureSettings,
+ BroadcastDispatcher broadcastDispatcher,
+ GuestSessionNotification guestSessionNotification,
+ ResetSessionDialog.Factory resetSessionDialogFactory) {
mUserTracker = userTracker;
- mUiEventLogger = uiEventLogger;
mSecureSettings = secureSettings;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mGuestSessionNotification = guestSessionNotification;
+ mResetSessionDialogFactory = resetSessionDialogFactory;
}
/**
* Register this receiver with the {@link BroadcastDispatcher}
- *
- * @param broadcastDispatcher to register the receiver.
*/
- public void register(BroadcastDispatcher broadcastDispatcher) {
+ public void register() {
IntentFilter f = new IntentFilter(Intent.ACTION_USER_SWITCHED);
- broadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM);
+ mBroadcastDispatcher.registerReceiver(this, f, null /* handler */, UserHandle.SYSTEM);
}
@Override
@@ -89,14 +99,25 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver {
return;
}
- int notFirstLogin = mSecureSettings.getIntForUser(
+ int guestLoginState = mSecureSettings.getIntForUser(
SETTING_GUEST_HAS_LOGGED_IN, 0, userId);
- if (notFirstLogin != 0) {
- mNewSessionDialog = new ResetSessionDialog(context, mUserSwitcherController,
- mUiEventLogger, userId);
+
+ if (guestLoginState == 0) {
+ // set 1 to indicate, 1st login
+ guestLoginState = 1;
+ mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
+ } else if (guestLoginState == 1) {
+ // set 2 to indicate, 2nd or later login
+ guestLoginState = 2;
+ mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, guestLoginState, userId);
+ }
+
+ mGuestSessionNotification.createPersistentNotification(currentUser,
+ (guestLoginState <= 1));
+
+ if (guestLoginState > 1) {
+ mNewSessionDialog = mResetSessionDialogFactory.create(userId);
mNewSessionDialog.show();
- } else {
- mSecureSettings.putIntForUser(SETTING_GUEST_HAS_LOGGED_IN, 1, userId);
}
}
}
@@ -124,10 +145,19 @@ public class GuestResumeSessionReceiver extends BroadcastReceiver {
private final UiEventLogger mUiEventLogger;
private final int mUserId;
- ResetSessionDialog(Context context,
+
+ /** Factory class to create guest reset dialog instance */
+ @AssistedFactory
+ public interface Factory {
+ /** Create a guest reset dialog instance */
+ ResetSessionDialog create(int userId);
+ }
+
+ @AssistedInject
+ public ResetSessionDialog(Context context,
UserSwitcherController userSwitcherController,
UiEventLogger uiEventLogger,
- int userId) {
+ @Assisted int userId) {
super(context, false /* dismissOnDeviceLock */);
setTitle(context.getString(R.string.guest_wipe_session_title));
diff --git a/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
new file mode 100644
index 000000000000..b0eaab97c5ac
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/GuestSessionNotification.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.FeatureFlagUtils;
+
+import com.android.internal.messages.nano.SystemMessageProto;
+import com.android.systemui.util.NotificationChannels;
+
+import javax.inject.Inject;
+
+/**
+ * Posts a persistent notification on entry to guest mode
+ */
+public final class GuestSessionNotification {
+
+ private static final String TAG = GuestSessionNotification.class.getSimpleName();
+
+ private final Context mContext;
+ private final NotificationManager mNotificationManager;
+
+ @Inject
+ public GuestSessionNotification(Context context,
+ NotificationManager notificationManager) {
+ mContext = context;
+ mNotificationManager = notificationManager;
+ }
+
+ private void overrideNotificationAppName(Notification.Builder notificationBuilder) {
+ final Bundle extras = new Bundle();
+ String appName = mContext.getString(R.string.guest_notification_app_name);
+
+ extras.putString(Notification.EXTRA_SUBSTITUTE_APP_NAME, appName);
+
+ notificationBuilder.addExtras(extras);
+ }
+
+ void createPersistentNotification(UserInfo userInfo, boolean isGuestFirstLogin) {
+ if (!FeatureFlagUtils.isEnabled(mContext,
+ FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES)
+ || !userInfo.isGuest()) {
+ // we create a persistent notification only if enabled and only for guests
+ return;
+ }
+ String contentText;
+ if (userInfo.isEphemeral()) {
+ contentText = mContext.getString(R.string.guest_notification_ephemeral);
+ } else if (isGuestFirstLogin) {
+ contentText = mContext.getString(R.string.guest_notification_non_ephemeral);
+ } else {
+ contentText = mContext.getString(
+ R.string.guest_notification_non_ephemeral_non_first_login);
+ }
+
+ final Intent guestExitIntent = new Intent(
+ GuestResetOrExitSessionReceiver.ACTION_GUEST_EXIT);
+ final Intent userSettingsIntent = new Intent(Settings.ACTION_USER_SETTINGS);
+
+ PendingIntent guestExitPendingIntent =
+ PendingIntent.getBroadcastAsUser(mContext, 0, guestExitIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
+
+ PendingIntent userSettingsPendingIntent =
+ PendingIntent.getActivityAsUser(mContext, 0, userSettingsIntent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE,
+ null,
+ UserHandle.of(userInfo.id));
+
+ Notification.Builder builder = new Notification.Builder(mContext,
+ NotificationChannels.ALERTS)
+ .setSmallIcon(R.drawable.ic_account_circle)
+ .setContentTitle(mContext.getString(R.string.guest_notification_session_active))
+ .setContentText(contentText)
+ .setPriority(Notification.PRIORITY_DEFAULT)
+ .setOngoing(true)
+ .setContentIntent(userSettingsPendingIntent);
+
+ // we show reset button only if this is a 2nd or later login
+ if (!isGuestFirstLogin) {
+ final Intent guestResetIntent = new Intent(
+ GuestResetOrExitSessionReceiver.ACTION_GUEST_RESET);
+
+ PendingIntent guestResetPendingIntent =
+ PendingIntent.getBroadcastAsUser(mContext, 0, guestResetIntent,
+ PendingIntent.FLAG_IMMUTABLE,
+ UserHandle.SYSTEM);
+
+ builder.addAction(R.drawable.ic_sysbar_home,
+ mContext.getString(
+ com.android.settingslib.R.string.guest_reset_guest_confirm_button),
+ guestResetPendingIntent);
+ }
+ builder.addAction(R.drawable.ic_sysbar_home,
+ mContext.getString(
+ com.android.settingslib.R.string.guest_exit_button),
+ guestExitPendingIntent);
+
+ overrideNotificationAppName(builder);
+
+ mNotificationManager.notifyAsUser(null,
+ SystemMessageProto.SystemMessage.NOTE_GUEST_SESSION,
+ builder.build(),
+ UserHandle.of(userInfo.id));
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/Somnambulator.java b/packages/SystemUI/src/com/android/systemui/Somnambulator.java
index 0dd6d9283223..25801cfe62b0 100644
--- a/packages/SystemUI/src/com/android/systemui/Somnambulator.java
+++ b/packages/SystemUI/src/com/android/systemui/Somnambulator.java
@@ -17,12 +17,15 @@
package com.android.systemui;
import android.app.Activity;
-import android.content.Intent;
import android.service.dreams.Sandman;
/**
* A simple activity that launches a dream.
* <p>
+ *
+ * This activity has been deprecated and no longer used. The system uses its presence to determine
+ * whether a dock app should be started on dock through intent resolution.
+ *
* Note: This Activity is special. If this class is moved to another package or
* renamed, be sure to update the component name in {@link Sandman}.
* </p>
@@ -34,27 +37,6 @@ public class Somnambulator extends Activity {
@Override
public void onStart() {
super.onStart();
-
- final Intent launchIntent = getIntent();
- final String action = launchIntent.getAction();
- if (Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
- Intent shortcutIntent = new Intent(this, Somnambulator.class);
- shortcutIntent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
- | Intent.FLAG_ACTIVITY_NEW_TASK);
- Intent resultIntent = new Intent();
- resultIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,
- Intent.ShortcutIconResource.fromContext(this, R.mipmap.ic_launcher_dreams));
- resultIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
- resultIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getString(R.string.start_dreams));
- setResult(RESULT_OK, resultIntent);
- } else {
- boolean docked = launchIntent.hasCategory(Intent.CATEGORY_DESK_DOCK);
- if (docked) {
- Sandman.startDreamWhenDockedIfAppropriate(this);
- } else {
- Sandman.startDreamByUserRequest(this);
- }
- }
finish();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
index 714d267bb07d..527ce127820e 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactory.java
@@ -16,160 +16,22 @@
package com.android.systemui;
-import android.app.Activity;
-import android.app.Application;
-import android.app.Service;
-import android.content.BroadcastReceiver;
-import android.content.ContentProvider;
import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.app.AppComponentFactory;
-
-import com.android.systemui.dagger.ContextComponentHelper;
-import com.android.systemui.dagger.SysUIComponent;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import javax.inject.Inject;
/**
- * Implementation of AppComponentFactory that injects into constructors.
+ * Starts up SystemUI using the AOSP {@link SystemUIInitializerImpl}.
*
- * This class sets up dependency injection when creating our application.
+ * This initializer relies on reflection to start everything up and should be considered deprecated.
+ * Instead, create your own {@link SystemUIAppComponentFactoryBase}, specify it in your
+ * AndroidManifest.xml and construct your own {@link SystemUIInitializer} directly.
*
- * Services support dependency injection into their constructors.
- *
- * ContentProviders support injection into member variables - _not_ constructors.
+ * @deprecated Define your own SystemUIAppComponentFactoryBase implementation and use that. This
+ * implementation may be changed or removed in future releases.
*/
-public class SystemUIAppComponentFactory extends AppComponentFactory {
-
- private static final String TAG = "AppComponentFactory";
- @Inject
- public ContextComponentHelper mComponentHelper;
-
- public SystemUIAppComponentFactory() {
- super();
- }
-
- @NonNull
+@Deprecated
+public class SystemUIAppComponentFactory extends SystemUIAppComponentFactoryBase {
@Override
- public Application instantiateApplicationCompat(
- @NonNull ClassLoader cl, @NonNull String className)
- throws InstantiationException, IllegalAccessException, ClassNotFoundException {
- Application app = super.instantiateApplicationCompat(cl, className);
- if (app instanceof ContextInitializer) {
- ((ContextInitializer) app).setContextAvailableCallback(
- context -> {
- SystemUIFactory.createFromConfig(context);
- SystemUIFactory.getInstance().getSysUIComponent().inject(
- SystemUIAppComponentFactory.this);
- }
- );
- }
-
- return app;
- }
-
- @NonNull
- @Override
- public ContentProvider instantiateProviderCompat(
- @NonNull ClassLoader cl, @NonNull String className)
- throws InstantiationException, IllegalAccessException, ClassNotFoundException {
-
- ContentProvider contentProvider = super.instantiateProviderCompat(cl, className);
- if (contentProvider instanceof ContextInitializer) {
- ((ContextInitializer) contentProvider).setContextAvailableCallback(
- context -> {
- SystemUIFactory.createFromConfig(context);
- SysUIComponent rootComponent =
- SystemUIFactory.getInstance().getSysUIComponent();
- try {
- Method injectMethod = rootComponent.getClass()
- .getMethod("inject", contentProvider.getClass());
- injectMethod.invoke(rootComponent, contentProvider);
- } catch (NoSuchMethodException
- | IllegalAccessException
- | InvocationTargetException e) {
- Log.w(TAG, "No injector for class: " + contentProvider.getClass(), e);
- }
- }
- );
- }
-
- return contentProvider;
- }
-
- @NonNull
- @Override
- public Activity instantiateActivityCompat(@NonNull ClassLoader cl, @NonNull String className,
- @Nullable Intent intent)
- throws InstantiationException, IllegalAccessException, ClassNotFoundException {
- if (mComponentHelper == null) {
- // This shouldn't happen, but is seen on occasion.
- // Bug filed against framework to take a look: http://b/141008541
- SystemUIFactory.getInstance().getSysUIComponent().inject(
- SystemUIAppComponentFactory.this);
- }
- Activity activity = mComponentHelper.resolveActivity(className);
- if (activity != null) {
- return activity;
- }
- return super.instantiateActivityCompat(cl, className, intent);
- }
-
- @NonNull
- @Override
- public Service instantiateServiceCompat(
- @NonNull ClassLoader cl, @NonNull String className, Intent intent)
- throws InstantiationException, IllegalAccessException, ClassNotFoundException {
- if (mComponentHelper == null) {
- // This shouldn't happen, but does when a device is freshly formatted.
- // Bug filed against framework to take a look: http://b/141008541
- SystemUIFactory.getInstance().getSysUIComponent().inject(
- SystemUIAppComponentFactory.this);
- }
- Service service = mComponentHelper.resolveService(className);
- if (service != null) {
- return service;
- }
- return super.instantiateServiceCompat(cl, className, intent);
- }
-
- @NonNull
- @Override
- public BroadcastReceiver instantiateReceiverCompat(@NonNull ClassLoader cl,
- @NonNull String className, @Nullable Intent intent)
- throws InstantiationException, IllegalAccessException, ClassNotFoundException {
- if (mComponentHelper == null) {
- // This shouldn't happen, but does when a device is freshly formatted.
- // Bug filed against framework to take a look: http://b/141008541
- SystemUIFactory.getInstance().getSysUIComponent().inject(
- SystemUIAppComponentFactory.this);
- }
- BroadcastReceiver receiver = mComponentHelper.resolveBroadcastReceiver(className);
- if (receiver != null) {
- return receiver;
- }
-
- return super.instantiateReceiverCompat(cl, className, intent);
- }
-
- /**
- * A callback that receives a Context when one is ready.
- */
- public interface ContextAvailableCallback {
- void onContextAvailable(Context context);
- }
-
- /**
- * Implemented in classes that get started by the system before a context is available.
- */
- public interface ContextInitializer {
- void setContextAvailableCallback(ContextAvailableCallback callback);
+ protected SystemUIInitializer createSystemUIInitializer(Context context) {
+ return SystemUIInitializerFactory.createWithContext(context);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
new file mode 100644
index 000000000000..12108b01ab28
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIAppComponentFactoryBase.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui
+
+import android.app.Activity
+import android.app.Application
+import android.app.Service
+import android.content.BroadcastReceiver
+import android.content.ContentProvider
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.core.app.AppComponentFactory
+import com.android.systemui.dagger.ContextComponentHelper
+import java.lang.reflect.InvocationTargetException
+import java.util.concurrent.ExecutionException
+import javax.inject.Inject
+
+/**
+ * Implementation of AppComponentFactory that injects into constructors.
+ *
+ * This class sets up dependency injection when creating our application.
+ *
+ * Activities, Services, and BroadcastReceivers support dependency injection into
+ * their constructors.
+ *
+ * ContentProviders support injection into member variables - _not_ constructors.
+ */
+abstract class SystemUIAppComponentFactoryBase : AppComponentFactory() {
+ companion object {
+ private const val TAG = "AppComponentFactory"
+ // Must be static due to http://b/141008541.
+ var systemUIInitializer: SystemUIInitializer? = null
+ }
+
+ @set:Inject
+ lateinit var componentHelper: ContextComponentHelper
+
+ /**
+ * Returns a new [SystemUIInitializer].
+ *
+ * The returned implementation should be specific to your build.
+ */
+ protected abstract fun createSystemUIInitializer(context: Context): SystemUIInitializer
+
+ private fun createSystemUIInitializerInternal(context: Context): SystemUIInitializer {
+ return systemUIInitializer ?: run {
+ val initializer = createSystemUIInitializer(context.applicationContext)
+ try {
+ initializer.init(false)
+ } catch (exception: ExecutionException) {
+ throw RuntimeException("Failed to initialize SysUI", exception)
+ } catch (exception: InterruptedException) {
+ throw RuntimeException("Failed to initialize SysUI", exception)
+ }
+ initializer.sysUIComponent.inject(
+ this@SystemUIAppComponentFactoryBase
+ )
+
+ systemUIInitializer = initializer
+ return initializer
+ }
+ }
+
+ override fun instantiateApplicationCompat(cl: ClassLoader, className: String): Application {
+ val app = super.instantiateApplicationCompat(cl, className)
+ if (app !is ContextInitializer) {
+ throw RuntimeException("App must implement ContextInitializer")
+ } else {
+ app.setContextAvailableCallback { context ->
+ createSystemUIInitializerInternal(context)
+ }
+ }
+
+ return app
+ }
+
+ override fun instantiateProviderCompat(cl: ClassLoader, className: String): ContentProvider {
+ val contentProvider = super.instantiateProviderCompat(cl, className)
+ if (contentProvider is ContextInitializer) {
+ contentProvider.setContextAvailableCallback { context ->
+ val initializer = createSystemUIInitializerInternal(context)
+ val rootComponent = initializer.sysUIComponent
+ try {
+ val injectMethod = rootComponent.javaClass
+ .getMethod("inject", contentProvider.javaClass)
+ injectMethod.invoke(rootComponent, contentProvider)
+ } catch (e: NoSuchMethodException) {
+ Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+ } catch (e: IllegalAccessException) {
+ Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+ } catch (e: InvocationTargetException) {
+ Log.w(TAG, "No injector for class: " + contentProvider.javaClass, e)
+ }
+ initializer
+ }
+ }
+ return contentProvider
+ }
+
+ override fun instantiateActivityCompat(
+ cl: ClassLoader,
+ className: String,
+ intent: Intent?
+ ): Activity {
+ if (!this::componentHelper.isInitialized) {
+ // This shouldn't happen, but is seen on occasion.
+ // Bug filed against framework to take a look: http://b/141008541
+ systemUIInitializer?.sysUIComponent?.inject(this@SystemUIAppComponentFactoryBase)
+ }
+ return componentHelper.resolveActivity(className)
+ ?: super.instantiateActivityCompat(cl, className, intent)
+ }
+
+ override fun instantiateServiceCompat(
+ cl: ClassLoader,
+ className: String,
+ intent: Intent?
+ ): Service {
+ if (!this::componentHelper.isInitialized) {
+ // This shouldn't happen, but does when a device is freshly formatted.
+ // Bug filed against framework to take a look: http://b/141008541
+ systemUIInitializer?.sysUIComponent?.inject(this@SystemUIAppComponentFactoryBase)
+ }
+ return componentHelper.resolveService(className)
+ ?: super.instantiateServiceCompat(cl, className, intent)
+ }
+
+ override fun instantiateReceiverCompat(
+ cl: ClassLoader,
+ className: String,
+ intent: Intent?
+ ): BroadcastReceiver {
+ if (!this::componentHelper.isInitialized) {
+ // This shouldn't happen, but does when a device is freshly formatted.
+ // Bug filed against framework to take a look: http://b/141008541
+ systemUIInitializer?.sysUIComponent?.inject(this@SystemUIAppComponentFactoryBase)
+ }
+ return componentHelper.resolveBroadcastReceiver(className)
+ ?: super.instantiateReceiverCompat(cl, className, intent)
+ }
+
+ /**
+ * An Interface for classes that can be notified when an Application Context becomes available.
+ *
+ * An instance of this will be passed to implementers of [ContextInitializer].
+ */
+ fun interface ContextAvailableCallback {
+ /** Notifies when the Application Context is available. */
+ fun onContextAvailable(context: Context): SystemUIInitializer
+ }
+
+ /**
+ * Interface for classes that can be constructed by the system before a context is available.
+ *
+ * This is intended for [Application] and [ContentProvider] implementations that
+ * either may not have a Context until some point after construction or are themselves
+ * a [Context].
+ *
+ * Implementers will be passed a [ContextAvailableCallback] that they should call as soon
+ * as an Application Context is ready.
+ */
+ interface ContextInitializer {
+ /**
+ * Called to supply the [ContextAvailableCallback] that should be called when an
+ * Application [Context] is available.
+ */
+ fun setContextAvailableCallback(callback: ContextAvailableCallback)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index 6d3fd503dff6..9138b2346ab8 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -42,7 +42,6 @@ import android.view.SurfaceControl;
import android.view.ThreadedRenderer;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.systemui.dagger.ContextComponentHelper;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dump.DumpManager;
@@ -65,7 +64,6 @@ public class SystemUIApplication extends Application implements
public static final String TAG = "SystemUIService";
private static final boolean DEBUG = false;
- private ContextComponentHelper mComponentHelper;
private BootCompleteCacheImpl mBootCompleteCache;
private DumpManager mDumpManager;
@@ -80,8 +78,8 @@ public class SystemUIApplication extends Application implements
private CoreStartable[] mServices;
private boolean mServicesStarted;
private SystemUIAppComponentFactory.ContextAvailableCallback mContextAvailableCallback;
- private GlobalRootComponent mRootComponent;
private SysUIComponent mSysUIComponent;
+ private SystemUIInitializer mInitializer;
public SystemUIApplication() {
super();
@@ -90,6 +88,10 @@ public class SystemUIApplication extends Application implements
ProtoLog.REQUIRE_PROTOLOGTOOL = false;
}
+ protected GlobalRootComponent getRootComponent() {
+ return mInitializer.getRootComponent();
+ }
+
@Override
public void onCreate() {
super.onCreate();
@@ -99,10 +101,8 @@ public class SystemUIApplication extends Application implements
TimingsTraceLog log = new TimingsTraceLog("SystemUIBootTiming",
Trace.TRACE_TAG_APP);
log.traceBegin("DependencyInjection");
- mContextAvailableCallback.onContextAvailable(this);
- mRootComponent = SystemUIFactory.getInstance().getRootComponent();
- mSysUIComponent = SystemUIFactory.getInstance().getSysUIComponent();
- mComponentHelper = mSysUIComponent.getContextComponentHelper();
+ mInitializer = mContextAvailableCallback.onContextAvailable(this);
+ mSysUIComponent = mInitializer.getSysUIComponent();
mBootCompleteCache = mSysUIComponent.provideBootCacheImpl();
log.traceEnd();
@@ -189,15 +189,14 @@ public class SystemUIApplication extends Application implements
*/
public void startServicesIfNeeded() {
- final String vendorComponent = SystemUIFactory.getInstance()
- .getVendorComponent(getResources());
+ final String vendorComponent = mInitializer.getVendorComponent(getResources());
// Sort the startables so that we get a deterministic ordering.
// TODO: make #start idempotent and require users of CoreStartable to call it.
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
- sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponents());
- sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser());
+ sortedStartables.putAll(mSysUIComponent.getStartables());
+ sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
startServicesIfNeeded(
sortedStartables, "StartServices", vendorComponent);
}
@@ -212,7 +211,7 @@ public class SystemUIApplication extends Application implements
// Sort the startables so that we get a deterministic ordering.
Map<Class<?>, Provider<CoreStartable>> sortedStartables = new TreeMap<>(
Comparator.comparing(Class::getName));
- sortedStartables.putAll(SystemUIFactory.getInstance().getStartableComponentsPerUser());
+ sortedStartables.putAll(mSysUIComponent.getPerUserStartables());
startServicesIfNeeded(
sortedStartables, "StartSecondaryServices", null);
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
index f5084f5cff76..e9ca0fdbb929 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializer.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -11,97 +11,75 @@
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
- * limitations under the License
+ * limitations under the License.
*/
package com.android.systemui;
-import android.app.ActivityThread;
import android.content.Context;
-import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.Log;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.DaggerGlobalRootComponent;
import com.android.systemui.dagger.GlobalRootComponent;
import com.android.systemui.dagger.SysUIComponent;
import com.android.systemui.dagger.WMComponent;
+import com.android.systemui.util.InitializationChecker;
import com.android.wm.shell.dagger.WMShellConcurrencyModule;
-import com.android.systemui.navigationbar.gestural.BackGestureTfClassifierProvider;
-import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider;
+import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
-import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
/**
- * Class factory to provide customizable SystemUI components.
+ * Initializer that stands up SystemUI.
+ *
+ * Implementations should override {@link #getGlobalRootComponentBuilder()} to fill in their own
+ * Dagger root component.
*/
-public class SystemUIFactory {
+public abstract class SystemUIInitializer {
private static final String TAG = "SystemUIFactory";
- static SystemUIFactory mFactory;
+ private final Context mContext;
+
private GlobalRootComponent mRootComponent;
private WMComponent mWMComponent;
private SysUIComponent mSysUIComponent;
- private boolean mInitializeComponents;
+ private InitializationChecker mInitializationChecker;
- public static <T extends SystemUIFactory> T getInstance() {
- return (T) mFactory;
+ public SystemUIInitializer(Context context) {
+ mContext = context;
}
- public static void createFromConfig(Context context) {
- createFromConfig(context, false);
- }
-
- @VisibleForTesting
- public static void createFromConfig(Context context, boolean fromTest) {
- if (mFactory != null) {
- return;
- }
-
- final String clsName = context.getString(R.string.config_systemUIFactoryComponent);
- if (clsName == null || clsName.length() == 0) {
- throw new RuntimeException("No SystemUIFactory component configured");
- }
+ protected abstract GlobalRootComponent.Builder getGlobalRootComponentBuilder();
- try {
- Class<?> cls = null;
- cls = context.getClassLoader().loadClass(clsName);
- mFactory = (SystemUIFactory) cls.newInstance();
- mFactory.init(context, fromTest);
- } catch (Throwable t) {
- Log.w(TAG, "Error creating SystemUIFactory component: " + clsName, t);
- throw new RuntimeException(t);
- }
- }
-
- @VisibleForTesting
- static void cleanup() {
- mFactory = null;
+ /**
+ * Prepares the SysUIComponent builder before it is built.
+ * @param sysUIBuilder the builder provided by the root component's getSysUIComponent() method
+ * @param wm the built WMComponent from the root component's getWMComponent() method
+ */
+ protected SysUIComponent.Builder prepareSysUIComponentBuilder(
+ SysUIComponent.Builder sysUIBuilder, WMComponent wm) {
+ return sysUIBuilder;
}
- public SystemUIFactory() {}
+ /**
+ * Starts the initialization process. This stands up the Dagger graph.
+ */
+ public void init(boolean fromTest) throws ExecutionException, InterruptedException {
+ mRootComponent = getGlobalRootComponentBuilder()
+ .context(mContext)
+ .instrumentationTest(fromTest)
+ .build();
- @VisibleForTesting
- public void init(Context context, boolean fromTest)
- throws ExecutionException, InterruptedException {
- // Only initialize components for the main system ui process running as the primary user
- mInitializeComponents = !fromTest
- && android.os.Process.myUserHandle().isSystem()
- && ActivityThread.currentProcessName().equals(ActivityThread.currentPackageName());
- mRootComponent = buildGlobalRootComponent(context);
+ mInitializationChecker = mRootComponent.getInitializationChecker();
+ boolean initializeComponents = mInitializationChecker.initializeComponents();
// Stand up WMComponent
- setupWmComponent(context);
- if (mInitializeComponents) {
+ setupWmComponent(mContext);
+ if (initializeComponents) {
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
mWMComponent.init();
@@ -109,51 +87,39 @@ public class SystemUIFactory {
// And finally, retrieve whatever SysUI needs from WMShell and build SysUI.
SysUIComponent.Builder builder = mRootComponent.getSysUIComponent();
- if (mInitializeComponents) {
+ if (initializeComponents) {
// Only initialize when not starting from tests since this currently initializes some
// components that shouldn't be run in the test environment
builder = prepareSysUIComponentBuilder(builder, mWMComponent)
+ .setShell(mWMComponent.getShell())
.setPip(mWMComponent.getPip())
- .setLegacySplitScreen(mWMComponent.getLegacySplitScreen())
.setSplitScreen(mWMComponent.getSplitScreen())
.setOneHanded(mWMComponent.getOneHanded())
.setBubbles(mWMComponent.getBubbles())
- .setHideDisplayCutout(mWMComponent.getHideDisplayCutout())
- .setShellCommandHandler(mWMComponent.getShellCommandHandler())
- .setAppPairs(mWMComponent.getAppPairs())
.setTaskViewFactory(mWMComponent.getTaskViewFactory())
.setTransitions(mWMComponent.getTransitions())
.setStartingSurface(mWMComponent.getStartingSurface())
.setDisplayAreaHelper(mWMComponent.getDisplayAreaHelper())
- .setTaskSurfaceHelper(mWMComponent.getTaskSurfaceHelper())
.setRecentTasks(mWMComponent.getRecentTasks())
- .setCompatUI(mWMComponent.getCompatUI())
- .setDragAndDrop(mWMComponent.getDragAndDrop())
.setBackAnimation(mWMComponent.getBackAnimation());
} else {
// TODO: Call on prepareSysUIComponentBuilder but not with real components. Other option
// is separating this logic into newly creating SystemUITestsFactory.
builder = prepareSysUIComponentBuilder(builder, mWMComponent)
+ .setShell(new ShellInterface() {})
.setPip(Optional.ofNullable(null))
- .setLegacySplitScreen(Optional.ofNullable(null))
.setSplitScreen(Optional.ofNullable(null))
.setOneHanded(Optional.ofNullable(null))
.setBubbles(Optional.ofNullable(null))
- .setHideDisplayCutout(Optional.ofNullable(null))
- .setShellCommandHandler(Optional.ofNullable(null))
- .setAppPairs(Optional.ofNullable(null))
.setTaskViewFactory(Optional.ofNullable(null))
.setTransitions(new ShellTransitions() {})
.setDisplayAreaHelper(Optional.ofNullable(null))
.setStartingSurface(Optional.ofNullable(null))
- .setTaskSurfaceHelper(Optional.ofNullable(null))
.setRecentTasks(Optional.ofNullable(null))
- .setCompatUI(Optional.ofNullable(null))
- .setDragAndDrop(Optional.ofNullable(null))
.setBackAnimation(Optional.ofNullable(null));
}
mSysUIComponent = builder.build();
- if (mInitializeComponents) {
+ if (initializeComponents) {
mSysUIComponent.init();
}
@@ -171,7 +137,8 @@ public class SystemUIFactory {
*/
private void setupWmComponent(Context context) {
WMComponent.Builder wmBuilder = mRootComponent.getWMComponentBuilder();
- if (!mInitializeComponents || !WMShellConcurrencyModule.enableShellMainThread(context)) {
+ if (!mInitializationChecker.initializeComponents()
+ || !WMShellConcurrencyModule.enableShellMainThread(context)) {
// If running under tests or shell thread is not enabled, we don't need anything special
mWMComponent = wmBuilder.build();
return;
@@ -193,26 +160,6 @@ public class SystemUIFactory {
}
}
- /**
- * Prepares the SysUIComponent builder before it is built.
- * @param sysUIBuilder the builder provided by the root component's getSysUIComponent() method
- * @param wm the built WMComponent from the root component's getWMComponent() method
- */
- protected SysUIComponent.Builder prepareSysUIComponentBuilder(
- SysUIComponent.Builder sysUIBuilder, WMComponent wm) {
- return sysUIBuilder;
- }
-
- protected GlobalRootComponent buildGlobalRootComponent(Context context) {
- return DaggerGlobalRootComponent.builder()
- .context(context)
- .build();
- }
-
- protected boolean shouldInitializeComponents() {
- return mInitializeComponents;
- }
-
public GlobalRootComponent getRootComponent() {
return mRootComponent;
}
@@ -226,42 +173,9 @@ public class SystemUIFactory {
}
/**
- * Returns the list of {@link CoreStartable} components that should be started at startup.
- */
- public Map<Class<?>, Provider<CoreStartable>> getStartableComponents() {
- return mSysUIComponent.getStartables();
- }
-
- /**
* Returns the list of additional system UI components that should be started.
*/
public String getVendorComponent(Resources resources) {
return resources.getString(R.string.config_systemUIVendorServiceComponent);
}
-
- /**
- * Returns the list of {@link CoreStartable} components that should be started per user.
- */
- public Map<Class<?>, Provider<CoreStartable>> getStartableComponentsPerUser() {
- return mSysUIComponent.getPerUserStartables();
- }
-
- /**
- * Creates an instance of ScreenshotNotificationSmartActionsProvider.
- * This method is overridden in vendor specific implementation of Sys UI.
- */
- public ScreenshotNotificationSmartActionsProvider
- createScreenshotNotificationSmartActionsProvider(
- Context context, Executor executor, Handler uiHandler) {
- return new ScreenshotNotificationSmartActionsProvider();
- }
-
- /**
- * Creates an instance of BackGestureTfClassifierProvider.
- * This method is overridden in vendor specific implementation of Sys UI.
- */
- public BackGestureTfClassifierProvider createBackGestureTfClassifierProvider(
- AssetManager am, String modelName) {
- return new BackGestureTfClassifierProvider();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
new file mode 100644
index 000000000000..b9454e8c3be8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerFactory.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.util.Log
+import com.android.internal.annotations.VisibleForTesting
+import com.android.systemui.util.Assert
+
+/**
+ * Factory to reflectively lookup a [SystemUIInitializer] to start SystemUI with.
+ */
+@Deprecated("Provide your own {@link SystemUIAppComponentFactoryBase} that doesn't need this.")
+object SystemUIInitializerFactory {
+ private const val TAG = "SysUIInitializerFactory"
+ @SuppressLint("StaticFieldLeak")
+ private var initializer: SystemUIInitializer? = null
+
+ /**
+ * Instantiate a [SystemUIInitializer] reflectively.
+ */
+ @JvmStatic
+ fun createWithContext(context: Context): SystemUIInitializer {
+ return createFromConfig(context)
+ }
+
+ /**
+ * Instantiate a [SystemUIInitializer] reflectively.
+ */
+ @JvmStatic
+ private fun createFromConfig(context: Context): SystemUIInitializer {
+ Assert.isMainThread()
+
+ return createFromConfigNoAssert(context)
+ }
+
+ @JvmStatic
+ @VisibleForTesting
+ fun createFromConfigNoAssert(context: Context): SystemUIInitializer {
+
+ return initializer ?: run {
+ val className = context.getString(R.string.config_systemUIFactoryComponent)
+ if (className.isEmpty()) {
+ throw RuntimeException("No SystemUIFactory component configured")
+ }
+ try {
+ val cls = context.classLoader.loadClass(className)
+ val constructor = cls.getConstructor(Context::class.java)
+ (constructor.newInstance(context) as SystemUIInitializer).apply {
+ initializer = this
+ }
+ } catch (t: Throwable) {
+ Log.w(TAG, "Error creating SystemUIInitializer component: $className", t)
+ throw t
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
new file mode 100644
index 000000000000..8920c928da09
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIInitializerImpl.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui
+
+import android.content.Context
+import com.android.systemui.dagger.DaggerGlobalRootComponent
+import com.android.systemui.dagger.GlobalRootComponent
+
+/**
+ * {@link SystemUIInitializer} that stands up AOSP SystemUI.
+ */
+class SystemUIInitializerImpl(context: Context) : SystemUIInitializer(context) {
+ override fun getGlobalRootComponentBuilder(): GlobalRootComponent.Builder {
+ return DaggerGlobalRootComponent.builder()
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index bd8e44ceab80..448b99b6e5d0 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -199,8 +199,9 @@ public class SystemActions extends CoreStartable {
mNotificationShadeController = notificationShadeController;
// Saving in instance variable since to prevent GC since
// NotificationShadeWindowController.registerCallback() only keeps weak references.
- mNotificationShadeCallback = (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing) ->
- registerOrUnregisterDismissNotificationShadeAction();
+ mNotificationShadeCallback =
+ (keyguardShowing, keyguardOccluded, bouncerShowing, mDozing, panelExpanded) ->
+ registerOrUnregisterDismissNotificationShadeAction();
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
index d2703f5e73a2..aff0b1fe287c 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java
@@ -353,6 +353,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
}
mIsShowing = false;
+ mDragAnimator.cancel();
mWindowManager.removeView(this);
setOnApplyWindowInsetsListener(null);
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index bc1c5f4baceb..84e1c3d4c8f0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -90,6 +90,8 @@ public class AuthContainerView extends LinearLayout
private static final int STATE_ANIMATING_OUT = 4;
private static final int STATE_GONE = 5;
+ private static final float BACKGROUND_DIM_AMOUNT = 0.5f;
+
/** Shows biometric prompt dialog animation. */
private static final String SHOW = "show";
/** Dismiss biometric prompt dialog animation. */
@@ -247,13 +249,13 @@ public class AuthContainerView extends LinearLayout
break;
case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
mFailedModalities.clear();
- mConfig.mCallback.onTryAgainPressed();
+ mConfig.mCallback.onTryAgainPressed(getRequestId());
break;
case AuthBiometricView.Callback.ACTION_ERROR:
animateAway(AuthDialogCallback.DISMISSED_ERROR);
break;
case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
- mConfig.mCallback.onDeviceCredentialPressed();
+ mConfig.mCallback.onDeviceCredentialPressed(getRequestId());
mHandler.postDelayed(() -> {
addCredentialView(false /* animatePanel */, true /* animateContents */);
}, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS);
@@ -371,7 +373,7 @@ public class AuthContainerView extends LinearLayout
void sendEarlyUserCanceled() {
mConfig.mCallback.onSystemEvent(
- BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL);
+ BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL, getRequestId());
}
@Override
@@ -754,6 +756,16 @@ public class AuthContainerView extends LinearLayout
.setDuration(animateDuration)
.setInterpolator(mLinearOutSlowIn)
.setListener(getJankListener(this, DISMISS, animateDuration))
+ .setUpdateListener(animation -> {
+ if (mWindowManager == null || getViewRootImpl() == null) {
+ Log.w(TAG, "skip updateViewLayout() for dim animation.");
+ return;
+ }
+ final WindowManager.LayoutParams lp = getViewRootImpl().mWindowAttributes;
+ lp.dimAmount = (1.0f - (Float) animation.getAnimatedValue())
+ * BACKGROUND_DIM_AMOUNT;
+ mWindowManager.updateViewLayout(this, lp);
+ })
.withLayer()
.start();
});
@@ -762,7 +774,8 @@ public class AuthContainerView extends LinearLayout
private void sendPendingCallbackIfNotNull() {
Log.d(TAG, "pendingCallback: " + mPendingCallbackReason);
if (mPendingCallbackReason != null) {
- mConfig.mCallback.onDismissed(mPendingCallbackReason, mCredentialAttestation);
+ mConfig.mCallback.onDismissed(mPendingCallbackReason,
+ mCredentialAttestation, getRequestId());
mPendingCallbackReason = null;
}
}
@@ -792,7 +805,7 @@ public class AuthContainerView extends LinearLayout
}
mContainerState = STATE_SHOWING;
if (mBiometricView != null) {
- mConfig.mCallback.onDialogAnimatedIn();
+ mConfig.mCallback.onDialogAnimatedIn(getRequestId());
mBiometricView.onDialogAnimatedIn();
}
}
@@ -800,7 +813,8 @@ public class AuthContainerView extends LinearLayout
@VisibleForTesting
static WindowManager.LayoutParams getLayoutParams(IBinder windowToken, CharSequence title) {
final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
- | WindowManager.LayoutParams.FLAG_SECURE;
+ | WindowManager.LayoutParams.FLAG_SECURE
+ | WindowManager.LayoutParams.FLAG_DIM_BEHIND;
final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT,
@@ -811,6 +825,7 @@ public class AuthContainerView extends LinearLayout
lp.setFitInsetsTypes(lp.getFitInsetsTypes() & ~WindowInsets.Type.ime());
lp.setTitle("BiometricPrompt");
lp.accessibilityTitle = title;
+ lp.dimAmount = BACKGROUND_DIM_AMOUNT;
lp.token = windowToken;
return lp;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index a097c5e76149..47ff59cfc281 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -340,11 +340,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- public void onTryAgainPressed() {
+ public void onTryAgainPressed(long requestId) {
if (mReceiver == null) {
Log.e(TAG, "onTryAgainPressed: Receiver is null");
return;
}
+
+ if (requestId != mCurrentDialog.getRequestId()) {
+ Log.w(TAG, "requestId doesn't match, skip onTryAgainPressed");
+ return;
+ }
+
try {
mReceiver.onTryAgainPressed();
} catch (RemoteException e) {
@@ -353,11 +359,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- public void onDeviceCredentialPressed() {
+ public void onDeviceCredentialPressed(long requestId) {
if (mReceiver == null) {
Log.e(TAG, "onDeviceCredentialPressed: Receiver is null");
return;
}
+
+ if (requestId != mCurrentDialog.getRequestId()) {
+ Log.w(TAG, "requestId doesn't match, skip onDeviceCredentialPressed");
+ return;
+ }
+
try {
mReceiver.onDeviceCredentialPressed();
} catch (RemoteException e) {
@@ -366,11 +378,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- public void onSystemEvent(int event) {
+ public void onSystemEvent(int event, long requestId) {
if (mReceiver == null) {
Log.e(TAG, "onSystemEvent(" + event + "): Receiver is null");
return;
}
+
+ if (requestId != mCurrentDialog.getRequestId()) {
+ Log.w(TAG, "requestId doesn't match, skip onSystemEvent");
+ return;
+ }
+
try {
mReceiver.onSystemEvent(event);
} catch (RemoteException e) {
@@ -379,12 +397,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- public void onDialogAnimatedIn() {
+ public void onDialogAnimatedIn(long requestId) {
if (mReceiver == null) {
Log.e(TAG, "onDialogAnimatedIn: Receiver is null");
return;
}
+ if (requestId != mCurrentDialog.getRequestId()) {
+ Log.w(TAG, "requestId doesn't match, skip onDialogAnimatedIn");
+ return;
+ }
+
try {
mReceiver.onDialogAnimatedIn();
} catch (RemoteException e) {
@@ -393,7 +416,14 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba
}
@Override
- public void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation) {
+ public void onDismissed(@DismissedReason int reason,
+ @Nullable byte[] credentialAttestation, long requestId) {
+
+ if (mCurrentDialog != null && requestId != mCurrentDialog.getRequestId()) {
+ Log.w(TAG, "requestId doesn't match, skip onDismissed");
+ return;
+ }
+
switch (reason) {
case AuthDialogCallback.DISMISSED_USER_CANCELED:
sendResultAndCleanUp(BiometricPrompt.DISMISSED_REASON_USER_CANCEL,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
index a7d2901b21c3..bbe461aaf6d9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialogCallback.java
@@ -47,27 +47,28 @@ public interface AuthDialogCallback {
* @param reason
* @param credentialAttestation the HAT received from LockSettingsService upon verification
*/
- void onDismissed(@DismissedReason int reason, @Nullable byte[] credentialAttestation);
+ void onDismissed(@DismissedReason int reason,
+ @Nullable byte[] credentialAttestation, long requestId);
/**
* Invoked when the "try again" button is clicked
*/
- void onTryAgainPressed();
+ void onTryAgainPressed(long requestId);
/**
* Invoked when the "use password" button is clicked
*/
- void onDeviceCredentialPressed();
+ void onDeviceCredentialPressed(long requestId);
/**
* See {@link android.hardware.biometrics.BiometricPrompt.Builder
* #setReceiveSystemEvents(boolean)}
* @param event
*/
- void onSystemEvent(int event);
+ void onSystemEvent(int event, long requestId);
/**
* Notifies when the dialog has finished animating.
*/
- void onDialogAnimatedIn();
+ void onDialogAnimatedIn(long requestId);
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
index 378ae14f0327..fef7383634f0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleView.kt
@@ -29,8 +29,7 @@ import android.view.View
import android.view.animation.PathInterpolator
import com.android.internal.graphics.ColorUtils
import com.android.systemui.animation.Interpolators
-import com.android.systemui.statusbar.charging.DwellRippleShader
-import com.android.systemui.statusbar.charging.RippleShader
+import com.android.systemui.ripple.RippleShader
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.4f
@@ -298,7 +297,7 @@ class AuthRippleView(context: Context?, attrs: AttributeSet?) : View(context, at
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationStart(animation: Animator?) {
unlockedRippleInProgress = true
- rippleShader.shouldFadeOutRipple = true
+ rippleShader.rippleFill = false
drawRipple = true
visibility = VISIBLE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
index 236129f8eb50..979fe33fb25b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/DwellRippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/DwellRippleShader.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.biometrics
import android.graphics.PointF
import android.graphics.RuntimeShader
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
index 04e2dccda528..bbffb73b7503 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SidefpsController.kt
@@ -34,16 +34,16 @@ import android.hardware.fingerprint.ISidefpsController
import android.os.Handler
import android.util.Log
import android.util.RotationUtils
-import android.view.View.AccessibilityDelegate
-import android.view.accessibility.AccessibilityEvent
import android.view.Display
import android.view.Gravity
import android.view.LayoutInflater
import android.view.Surface
import android.view.View
+import android.view.View.AccessibilityDelegate
import android.view.ViewPropertyAnimator
import android.view.WindowInsets
import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
import androidx.annotation.RawRes
import com.airbnb.lottie.LottieAnimationView
import com.airbnb.lottie.LottieProperty
@@ -70,13 +70,12 @@ class SidefpsController @Inject constructor(
private val activityTaskManager: ActivityTaskManager,
overviewProxyService: OverviewProxyService,
displayManager: DisplayManager,
- @Main mainExecutor: DelayableExecutor,
+ @Main private val mainExecutor: DelayableExecutor,
@Main private val handler: Handler
) {
@VisibleForTesting
val sensorProps: FingerprintSensorPropertiesInternal = fingerprintManager
- ?.sensorPropertiesInternal
- ?.firstOrNull { it.isAnySidefpsType }
+ ?.sideFpsSensorProperties
?: throw IllegalStateException("no side fingerprint sensor")
@VisibleForTesting
@@ -135,25 +134,34 @@ class SidefpsController @Inject constructor(
}
init {
- fingerprintManager?.setSidefpsController(object : ISidefpsController.Stub() {
- override fun show(
- sensorId: Int,
- @BiometricOverlayConstants.ShowReason reason: Int
- ) = if (reason.isReasonToShow(activityTaskManager)) doShow() else hide(sensorId)
-
- private fun doShow() = mainExecutor.execute {
- if (overlayView == null) {
- createOverlayForDisplay()
- } else {
- Log.v(TAG, "overlay already shown")
- }
- }
+ fingerprintManager?.setSidefpsController(
+ object : ISidefpsController.Stub() {
+ override fun show(
+ sensorId: Int,
+ @BiometricOverlayConstants.ShowReason reason: Int
+ ) = if (reason.isReasonToShow(activityTaskManager)) show() else hide()
- override fun hide(sensorId: Int) = mainExecutor.execute { overlayView = null }
- })
+ override fun hide(sensorId: Int) = hide()
+ })
overviewProxyService.addCallback(overviewProxyListener)
}
+ /** Shows the side fps overlay if not already shown. */
+ fun show() {
+ mainExecutor.execute {
+ if (overlayView == null) {
+ createOverlayForDisplay()
+ } else {
+ Log.v(TAG, "overlay already shown")
+ }
+ }
+ }
+
+ /** Hides the fps overlay if shown. */
+ fun hide() {
+ mainExecutor.execute { overlayView = null }
+ }
+
private fun onOrientationChanged() {
if (overlayView != null) {
createOverlayForDisplay()
@@ -266,6 +274,12 @@ class SidefpsController @Inject constructor(
}
}
+private val FingerprintManager?.sideFpsSensorProperties: FingerprintSensorPropertiesInternal?
+ get() = this?.sensorPropertiesInternal?.firstOrNull { it.isAnySidefpsType }
+
+/** Returns [True] when the device has a side fingerprint sensor. */
+fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
+
@BiometricOverlayConstants.ShowReason
private fun Int.isReasonToShow(activityTaskManager: ActivityTaskManager): Boolean = when (this) {
REASON_AUTH_KEYGUARD -> false
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index e5564b7f1f26..cf50f7f8524b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -20,6 +20,7 @@ import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPR
import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
import static com.android.internal.util.Preconditions.checkNotNull;
+import static com.android.systemui.classifier.Classifier.LOCK_ICON;
import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
import android.annotation.NonNull;
@@ -167,11 +168,16 @@ public class UdfpsController implements DozeReceiver {
private final Set<Callback> mCallbacks = new HashSet<>();
@VisibleForTesting
- public static final VibrationAttributes VIBRATION_ATTRIBUTES =
+ public static final VibrationAttributes UDFPS_VIBRATION_ATTRIBUTES =
new VibrationAttributes.Builder()
// vibration will bypass battery saver mode:
.setUsage(VibrationAttributes.USAGE_COMMUNICATION_REQUEST)
.build();
+ @VisibleForTesting
+ public static final VibrationAttributes LOCK_ICON_VIBRATION_ATTRIBUTES =
+ new VibrationAttributes.Builder()
+ .setUsage(VibrationAttributes.USAGE_TOUCH)
+ .build();
// haptic to use for successful device entry
public static final VibrationEffect EFFECT_CLICK =
@@ -603,7 +609,7 @@ public class UdfpsController implements DozeReceiver {
@NonNull SystemUIDialogManager dialogManager,
@NonNull LatencyTracker latencyTracker,
@NonNull ActivityLaunchAnimator activityLaunchAnimator,
- @NonNull Optional<AlternateUdfpsTouchProvider> aternateTouchProvider,
+ @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
@BiometricsBackground Executor biometricsExecutor) {
mContext = context;
mExecution = execution;
@@ -633,7 +639,7 @@ public class UdfpsController implements DozeReceiver {
mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
mLatencyTracker = latencyTracker;
mActivityLaunchAnimator = activityLaunchAnimator;
- mAlternateTouchProvider = aternateTouchProvider.orElse(null);
+ mAlternateTouchProvider = alternateTouchProvider.orElse(null);
mBiometricExecutor = biometricsExecutor;
mOrientationListener = new BiometricDisplayListener(
@@ -671,7 +677,7 @@ public class UdfpsController implements DozeReceiver {
mContext.getOpPackageName(),
EFFECT_CLICK,
"udfps-onStart-click",
- VIBRATION_ATTRIBUTES);
+ UDFPS_VIBRATION_ATTRIBUTES);
}
}
@@ -748,7 +754,19 @@ public class UdfpsController implements DozeReceiver {
}
if (!mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
+ if (mFalsingManager.isFalseTouch(LOCK_ICON)) {
+ Log.v(TAG, "aod lock icon long-press rejected by the falsing manager.");
+ return;
+ }
mKeyguardViewManager.showBouncer(true);
+
+ // play the same haptic as the LockIconViewController longpress
+ mVibrator.vibrate(
+ Process.myUid(),
+ mContext.getOpPackageName(),
+ UdfpsController.EFFECT_CLICK,
+ "aod-lock-icon-longpress",
+ LOCK_ICON_VIBRATION_ATTRIBUTES);
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
index ec4cf2fd8bd4..24b893340ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
@@ -510,6 +510,7 @@ public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<Ud
mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
.aboutToShowBouncerProgress(fraction) : fraction;
updateAlpha();
+ updatePauseAuth();
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
new file mode 100644
index 000000000000..9b7d49883222
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialog.java
@@ -0,0 +1,134 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.UiEvent;
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.media.MediaDataUtils;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
+
+/**
+ * Dialog for showing le audio broadcasting dialog.
+ */
+public class BroadcastDialog extends SystemUIDialog {
+
+ private static final String TAG = "BroadcastDialog";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ private Context mContext;
+ private UiEventLogger mUiEventLogger;
+ @VisibleForTesting
+ protected View mDialogView;
+ private MediaOutputDialogFactory mMediaOutputDialogFactory;
+ private String mSwitchBroadcastApp;
+ private String mOutputPackageName;
+
+ public BroadcastDialog(Context context, MediaOutputDialogFactory mediaOutputDialogFactory,
+ String switchBroadcastApp, String outputPkgName, UiEventLogger uiEventLogger) {
+ super(context);
+ if (DEBUG) {
+ Log.d(TAG, "Init BroadcastDialog");
+ }
+
+ mContext = getContext();
+ mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ mSwitchBroadcastApp = switchBroadcastApp;
+ mOutputPackageName = outputPkgName;
+ mUiEventLogger = uiEventLogger;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (DEBUG) {
+ Log.d(TAG, "onCreate");
+ }
+
+ mUiEventLogger.log(BroadcastDialogEvent.BROADCAST_DIALOG_SHOW);
+ mDialogView = LayoutInflater.from(mContext).inflate(R.layout.broadcast_dialog, null);
+ final Window window = getWindow();
+ window.setContentView(mDialogView);
+
+ TextView title = mDialogView.requireViewById(R.id.dialog_title);
+ TextView subTitle = mDialogView.requireViewById(R.id.dialog_subtitle);
+ title.setText(
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_title,
+ MediaDataUtils.getAppLabel(mContext, mOutputPackageName,
+ mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_unknown_name))));
+ subTitle.setText(
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_sub_title,
+ mSwitchBroadcastApp));
+
+ Button switchBroadcast = mDialogView.requireViewById(R.id.switch_broadcast);
+ Button changeOutput = mDialogView.requireViewById(R.id.change_output);
+ Button cancelBtn = mDialogView.requireViewById(R.id.cancel);
+ switchBroadcast.setText(mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_switch_app, mSwitchBroadcastApp), null);
+ changeOutput.setOnClickListener((view) -> {
+ mMediaOutputDialogFactory.create(mOutputPackageName, true, null);
+ dismiss();
+ });
+ cancelBtn.setOnClickListener((view) -> {
+ if (DEBUG) {
+ Log.d(TAG, "BroadcastDialog dismiss.");
+ }
+ dismiss();
+ });
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasFocus) {
+ super.onWindowFocusChanged(hasFocus);
+ if (!hasFocus && isShowing()) {
+ dismiss();
+ }
+ }
+
+ public enum BroadcastDialogEvent implements UiEventLogger.UiEventEnum {
+ @UiEvent(doc = "The Broadcast dialog became visible on the screen.")
+ BROADCAST_DIALOG_SHOW(1062);
+
+ private final int mId;
+
+ BroadcastDialogEvent(int id) {
+ mId = id;
+ }
+
+ @Override
+ public int getId() {
+ return mId;
+ }
+ }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
new file mode 100644
index 000000000000..8a54345ae28c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/BroadcastDialogController.java
@@ -0,0 +1,60 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth;
+
+import android.content.Context;
+import android.view.View;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.animation.DialogLaunchAnimator;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+
+import javax.inject.Inject;
+
+/**
+ * Controller to create BroadcastDialog objects.
+ */
+@SysUISingleton
+public class BroadcastDialogController {
+
+ private Context mContext;
+ private UiEventLogger mUiEventLogger;
+ private DialogLaunchAnimator mDialogLaunchAnimator;
+ private MediaOutputDialogFactory mMediaOutputDialogFactory;
+
+ @Inject
+ public BroadcastDialogController(Context context, UiEventLogger uiEventLogger,
+ DialogLaunchAnimator dialogLaunchAnimator,
+ MediaOutputDialogFactory mediaOutputDialogFactory) {
+ mContext = context;
+ mUiEventLogger = uiEventLogger;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
+ mMediaOutputDialogFactory = mediaOutputDialogFactory;
+ }
+
+ public void createBroadcastDialog(String switchAppName, String outputPkgName,
+ boolean aboveStatusBar, View view) {
+ BroadcastDialog broadcastDialog = new BroadcastDialog(mContext, mMediaOutputDialogFactory,
+ switchAppName, outputPkgName, mUiEventLogger);
+ if (view != null) {
+ mDialogLaunchAnimator.showFromView(broadcastDialog, view);
+ } else {
+ broadcastDialog.show();
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
new file mode 100644
index 000000000000..81da80233d42
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraGestureHelper.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera
+
+import android.app.ActivityManager
+import android.app.ActivityOptions
+import android.app.IActivityTaskManager
+import android.content.ContentResolver
+import android.content.Context
+import android.content.Intent
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import android.os.RemoteException
+import android.os.UserHandle
+import android.util.Log
+import android.view.WindowManager
+import androidx.annotation.VisibleForTesting
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.shade.PanelViewController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Helps with handling camera-related gestures (for example, double-tap the power button to launch
+ * the camera).
+ */
+class CameraGestureHelper @Inject constructor(
+ private val context: Context,
+ private val centralSurfaces: CentralSurfaces,
+ private val keyguardStateController: KeyguardStateController,
+ private val packageManager: PackageManager,
+ private val activityManager: ActivityManager,
+ private val activityStarter: ActivityStarter,
+ private val activityIntentHelper: ActivityIntentHelper,
+ private val activityTaskManager: IActivityTaskManager,
+ private val cameraIntents: CameraIntentsWrapper,
+ private val contentResolver: ContentResolver,
+ @Main private val uiExecutor: Executor,
+) {
+ /**
+ * Whether the camera application can be launched for the camera launch gesture.
+ */
+ fun canCameraGestureBeLaunched(statusBarState: Int): Boolean {
+ if (!centralSurfaces.isCameraAllowedByAdmin) {
+ return false
+ }
+
+ val resolveInfo: ResolveInfo? = packageManager.resolveActivityAsUser(
+ getStartCameraIntent(),
+ PackageManager.MATCH_DEFAULT_ONLY,
+ KeyguardUpdateMonitor.getCurrentUser()
+ )
+ val resolvedPackage = resolveInfo?.activityInfo?.packageName
+ return (resolvedPackage != null &&
+ (statusBarState != StatusBarState.SHADE ||
+ !activityManager.isInForeground(resolvedPackage)))
+ }
+
+ /**
+ * Launches the camera.
+ *
+ * @param source The source of the camera launch, to be passed to the camera app via [Intent]
+ */
+ fun launchCamera(source: Int) {
+ val intent: Intent = getStartCameraIntent()
+ intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source)
+ val wouldLaunchResolverActivity = activityIntentHelper.wouldLaunchResolverActivity(
+ intent, KeyguardUpdateMonitor.getCurrentUser()
+ )
+ if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
+ uiExecutor.execute {
+ // Normally an activity will set its requested rotation animation on its window.
+ // However when launching an activity causes the orientation to change this is too
+ // late. In these cases, the default animation is used. This doesn't look good for
+ // the camera (as it rotates the camera contents out of sync with physical reality).
+ // Therefore, we ask the WindowManager to force the cross-fade animation if an
+ // orientation change happens to occur during the launch.
+ val activityOptions = ActivityOptions.makeBasic()
+ activityOptions.setDisallowEnterPictureInPictureWhileLaunching(true)
+ activityOptions.rotationAnimationHint =
+ WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS
+ try {
+ activityTaskManager.startActivityAsUser(
+ null,
+ context.basePackageName,
+ context.attributionTag,
+ intent,
+ intent.resolveTypeIfNeeded(contentResolver),
+ null,
+ null,
+ 0,
+ Intent.FLAG_ACTIVITY_NEW_TASK,
+ null,
+ activityOptions.toBundle(),
+ UserHandle.CURRENT.identifier,
+ )
+ } catch (e: RemoteException) {
+ Log.w(
+ PanelViewController.TAG,
+ "Unable to start camera activity",
+ e
+ )
+ }
+ }
+ } else {
+ // We need to delay starting the activity because ResolverActivity finishes itself if
+ // launched from behind the lock-screen.
+ activityStarter.startActivity(intent, false /* dismissShade */)
+ }
+
+ // Call this to make sure that the keyguard returns if the app that is being launched
+ // crashes after a timeout.
+ centralSurfaces.startLaunchTransitionTimeout()
+ // Call this to make sure the keyguard is ready to be dismissed once the next intent is
+ // handled by the OS (in our case it is the activity we started right above)
+ centralSurfaces.readyForKeyguardDone()
+ }
+
+ /**
+ * Returns an [Intent] that can be used to start the camera app such that it occludes the
+ * lock-screen, if needed.
+ */
+ private fun getStartCameraIntent(): Intent {
+ val isLockScreenDismissible = keyguardStateController.canDismissLockScreen()
+ val isSecure = keyguardStateController.isMethodSecure
+ return if (isSecure && !isLockScreenDismissible) {
+ cameraIntents.getSecureCameraIntent()
+ } else {
+ cameraIntents.getInsecureCameraIntent()
+ }
+ }
+
+ companion object {
+ @VisibleForTesting
+ const val EXTRA_CAMERA_LAUNCH_SOURCE = "com.android.systemui.camera_launch_source"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
new file mode 100644
index 000000000000..cf02f8fb4a3c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/camera/CameraIntentsWrapper.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera
+
+import android.content.Context
+import android.content.Intent
+import javax.inject.Inject
+
+/** Injectable wrapper around [CameraIntents]. */
+class CameraIntentsWrapper @Inject constructor(
+ private val context: Context,
+) {
+
+ /**
+ * Returns an [Intent] that can be used to start the camera, suitable for when the device is
+ * already unlocked
+ */
+ fun getSecureCameraIntent(): Intent {
+ return CameraIntents.getSecureCameraIntent(context)
+ }
+
+ /**
+ * Returns an [Intent] that can be used to start the camera, suitable for when the device is not
+ * already unlocked
+ */
+ fun getInsecureCameraIntent(): Intent {
+ return CameraIntents.getInsecureCameraIntent(context)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index 5df593b64c24..8292e52b1ffd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
import android.content.Context
import android.content.res.Configuration
@@ -32,6 +32,7 @@ import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
@@ -61,7 +62,7 @@ class WiredChargingRippleController @Inject constructor(
private val systemClock: SystemClock,
private val uiEventLogger: UiEventLogger
) {
- private var pluggedIn: Boolean? = null
+ private var pluggedIn: Boolean = false
private val rippleEnabled: Boolean = featureFlags.isEnabled(Flags.CHARGING_RIPPLE) &&
!SystemProperties.getBoolean("persist.debug.suppress-charging-ripple", false)
private var normalizedPortPosX: Float = context.resources.getFloat(
@@ -84,7 +85,7 @@ class WiredChargingRippleController @Inject constructor(
private var debounceLevel = 0
@VisibleForTesting
- var rippleView: ChargingRippleView = ChargingRippleView(context, attrs = null)
+ var rippleView: RippleView = RippleView(context, attrs = null)
init {
pluggedIn = batteryController.isPluggedIn
@@ -99,15 +100,17 @@ class WiredChargingRippleController @Inject constructor(
nowPluggedIn: Boolean,
charging: Boolean
) {
- // Suppresses the ripple when the state change comes from wireless charging.
- if (batteryController.isPluggedInWireless) {
+ // Suppresses the ripple when the state change comes from wireless charging or
+ // its dock.
+ if (batteryController.isPluggedInWireless ||
+ batteryController.isChargingSourceDock) {
return
}
- val wasPluggedIn = pluggedIn
- pluggedIn = nowPluggedIn
- if ((wasPluggedIn == null || !wasPluggedIn) && nowPluggedIn) {
+
+ if (!pluggedIn && nowPluggedIn) {
startRippleWithDebounce()
}
+ pluggedIn = nowPluggedIn
}
}
batteryController.addCallback(batteryStateChangeCallback)
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index 0d3e2ae9a776..f6368ee19e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -34,7 +34,7 @@ import android.widget.TextView;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.charging.ChargingRippleView;
+import com.android.systemui.ripple.RippleView;
import java.text.NumberFormat;
@@ -46,7 +46,7 @@ public class WirelessChargingLayout extends FrameLayout {
private static final long RIPPLE_ANIMATION_DURATION = 1500;
private static final int SCRIM_COLOR = 0x4C000000;
private static final int SCRIM_FADE_DURATION = 300;
- private ChargingRippleView mRippleView;
+ private RippleView mRippleView;
public WirelessChargingLayout(Context context) {
super(context);
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index aa67ecd30627..e16ac08c88f8 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -169,6 +169,7 @@ public class ClipboardOverlayController {
private Animator mExitAnimator;
private Animator mEnterAnimator;
private final int mOrientation;
+ private boolean mKeyboardVisible;
public ClipboardOverlayController(Context context,
@@ -261,8 +262,22 @@ public class ClipboardOverlayController {
attachWindow();
withWindowAttached(() -> {
mWindow.setContentView(mView);
- updateInsets(mWindowManager.getCurrentWindowMetrics().getWindowInsets());
- mView.requestLayout();
+ WindowInsets insets = mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ mKeyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ updateInsets(insets);
+ mWindow.peekDecorView().getViewTreeObserver().addOnGlobalLayoutListener(
+ new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ WindowInsets insets =
+ mWindowManager.getCurrentWindowMetrics().getWindowInsets();
+ boolean keyboardVisible = insets.isVisible(WindowInsets.Type.ime());
+ if (keyboardVisible != mKeyboardVisible) {
+ mKeyboardVisible = keyboardVisible;
+ updateInsets(insets);
+ }
+ }
+ });
mWindow.peekDecorView().getViewRootImpl().setActivityConfigCallback(
new ViewRootImpl.ActivityConfigCallback() {
@Override
@@ -384,8 +399,6 @@ public class ClipboardOverlayController {
mRemoteCopyChip.setVisibility(View.GONE);
}
withWindowAttached(() -> {
- updateInsets(
- mWindowManager.getCurrentWindowMetrics().getWindowInsets());
if (mEnterAnimator == null || !mEnterAnimator.isRunning()) {
mView.post(this::animateIn);
}
@@ -509,7 +522,7 @@ public class ClipboardOverlayController {
private void shareContent(ClipData clip) {
mUiEventLogger.log(CLIPBOARD_OVERLAY_SHARE_TAPPED);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText());
+ shareIntent.putExtra(Intent.EXTRA_TEXT, clip.getItemAt(0).getText().toString());
shareIntent.setDataAndType(
clip.getItemAt(0).getUri(), clip.getDescription().getMimeType(0));
shareIntent.putExtra(Intent.EXTRA_STREAM, clip.getItemAt(0).getUri());
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
index 3f78f97ba563..1fa9ac574c7e 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java
@@ -74,6 +74,7 @@ public class EditTextActivity extends Activity
}
mEditText.setText(clip.getItemAt(0).getText());
mEditText.requestFocus();
+ mEditText.setSelection(0);
mSensitive = clip.getDescription().getExtras() != null
&& clip.getDescription().getExtras()
.getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index bed553e6e4d6..50ce9d4ec0f4 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -20,13 +20,11 @@ import android.app.PendingIntent
import android.app.backup.BackupManager
import android.content.BroadcastReceiver
import android.content.ComponentName
-import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.database.ContentObserver
import android.net.Uri
-import android.os.Environment
import android.os.UserHandle
import android.service.controls.Control
import android.service.controls.actions.ControlAction
@@ -43,6 +41,7 @@ import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_SEEDING_COMPLETED
@@ -61,6 +60,7 @@ class ControlsControllerImpl @Inject constructor (
private val bindingController: ControlsBindingController,
private val listingController: ControlsListingController,
private val broadcastDispatcher: BroadcastDispatcher,
+ private val userFileManager: UserFileManager,
optionalWrapper: Optional<ControlsFavoritePersistenceWrapper>,
dumpManager: DumpManager,
userTracker: UserTracker
@@ -84,15 +84,12 @@ class ControlsControllerImpl @Inject constructor (
override val currentUserId
get() = currentUser.identifier
- private val contentResolver: ContentResolver
- get() = context.contentResolver
-
private val persistenceWrapper: ControlsFavoritePersistenceWrapper
@VisibleForTesting
internal var auxiliaryPersistenceWrapper: AuxiliaryPersistenceWrapper
init {
- userStructure = UserStructure(context, currentUser)
+ userStructure = UserStructure(context, currentUser, userFileManager)
persistenceWrapper = optionalWrapper.orElseGet {
ControlsFavoritePersistenceWrapper(
@@ -111,7 +108,7 @@ class ControlsControllerImpl @Inject constructor (
private fun setValuesForUser(newUser: UserHandle) {
Log.d(TAG, "Changing to user: $newUser")
currentUser = newUser
- userStructure = UserStructure(context, currentUser)
+ userStructure = UserStructure(context, currentUser, userFileManager)
persistenceWrapper.changeFileAndBackupManager(
userStructure.file,
BackupManager(userStructure.userContext)
@@ -187,8 +184,11 @@ class ControlsControllerImpl @Inject constructor (
// When a component is uninstalled, allow seeding to happen again if the user
// reinstalls the app
- val prefs = userStructure.userContext.getSharedPreferences(
- PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+ val prefs = userFileManager.getSharedPreferences(
+ PREFS_CONTROLS_FILE,
+ Context.MODE_PRIVATE,
+ userTracker.userId
+ )
val completedSeedingPackageSet = prefs.getStringSet(
PREFS_CONTROLS_SEEDING_COMPLETED, mutableSetOf<String>())
val servicePackageSet = serviceInfoSet.map { it.packageName }
@@ -575,18 +575,12 @@ class ControlsControllerImpl @Inject constructor (
}
}
-class UserStructure(context: Context, user: UserHandle) {
+class UserStructure(context: Context, user: UserHandle, userFileManager: UserFileManager) {
val userContext = context.createContextAsUser(user, 0)
-
- val file = Environment.buildPath(
- userContext.filesDir,
- ControlsFavoritePersistenceWrapper.FILE_NAME
- )
-
- val auxiliaryFile = Environment.buildPath(
- userContext.filesDir,
- AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME
- )
+ val file = userFileManager.getFile(ControlsFavoritePersistenceWrapper.FILE_NAME,
+ user.identifier)
+ val auxiliaryFile = userFileManager.getFile(AuxiliaryPersistenceWrapper.AUXILIARY_FILE_NAME,
+ user.identifier)
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
index 0bc6579739db..2389ad134c55 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/AppAdapter.kt
@@ -28,6 +28,7 @@ import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.android.systemui.R
import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.util.icuMessageFormat
import java.text.Collator
import java.util.concurrent.Executor
@@ -61,7 +62,7 @@ class AppAdapter(
backgroundExecutor.execute {
val collator = Collator.getInstance(resources.configuration.locales[0])
val localeComparator = compareBy<ControlsServiceInfo, CharSequence>(collator) {
- it.loadLabel()
+ it.loadLabel() ?: ""
}
listOfServices = serviceInfos.sortedWith(localeComparator)
uiExecutor.execute(::notifyDataSetChanged)
@@ -74,8 +75,10 @@ class AppAdapter(
}
override fun onCreateViewHolder(parent: ViewGroup, i: Int): Holder {
- return Holder(layoutInflater.inflate(R.layout.controls_app_item, parent, false),
- favoritesRenderer)
+ return Holder(
+ layoutInflater.inflate(R.layout.controls_app_item, parent, false),
+ favoritesRenderer
+ )
}
override fun getItemCount() = listOfServices.size
@@ -116,10 +119,10 @@ class FavoritesRenderer(
fun renderFavoritesForComponent(component: ComponentName): String? {
val qty = favoriteFunction(component)
- if (qty != 0) {
- return resources.getQuantityString(R.plurals.controls_number_of_favorites, qty, qty)
+ return if (qty != 0) {
+ icuMessageFormat(resources, R.string.controls_number_of_favorites, qty)
} else {
- return null
+ null
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
index 0cf3333d12a6..8ba6f1c4a411 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultBroadcastReceiverBinder.java
@@ -18,6 +18,8 @@ package com.android.systemui.dagger;
import android.content.BroadcastReceiver;
+import com.android.systemui.GuestResetOrExitSessionReceiver;
+import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.media.dialog.MediaOutputDialogReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetPinnedReceiver;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
@@ -89,4 +91,21 @@ public abstract class DefaultBroadcastReceiverBinder {
public abstract BroadcastReceiver bindPeopleSpaceWidgetProvider(
PeopleSpaceWidgetProvider broadcastReceiver);
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(GuestResumeSessionReceiver.class)
+ public abstract BroadcastReceiver bindGuestResumeSessionReceiver(
+ GuestResumeSessionReceiver broadcastReceiver);
+
+ /**
+ *
+ */
+ @Binds
+ @IntoMap
+ @ClassKey(GuestResetOrExitSessionReceiver.class)
+ public abstract BroadcastReceiver bindGuestResetOrExitSessionReceiver(
+ GuestResetOrExitSessionReceiver broadcastReceiver);
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index cd8ca0553add..4096ed4283e5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -42,9 +42,11 @@ import android.content.pm.IPackageManager;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
+import android.content.res.AssetManager;
import android.content.res.Resources;
import android.hardware.SensorManager;
import android.hardware.SensorPrivacyManager;
+import android.hardware.camera2.CameraManager;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
import android.hardware.display.ColorDisplayManager;
@@ -90,6 +92,7 @@ import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.Prefs;
+import com.android.systemui.dagger.qualifiers.Application;
import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.TestHarness;
@@ -404,6 +407,12 @@ public class FrameworkServicesModule {
}
@Provides
+ @Application
+ static AssetManager provideAssetManager(@Application Context context) {
+ return context.getAssets();
+ }
+
+ @Provides
@Singleton
static RoleManager provideRoleManager(Context context) {
return context.getSystemService(RoleManager.class);
@@ -553,4 +562,10 @@ public class FrameworkServicesModule {
static SafetyCenterManager provideSafetyCenterManager(Context context) {
return context.getSystemService(SafetyCenterManager.class);
}
+
+ @Provides
+ @Singleton
+ static CameraManager provideCameraManager(Context context) {
+ return context.getSystemService(CameraManager.class);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
index 4f55ba496b6b..9e33ee1faab3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/GlobalRootComponent.java
@@ -18,6 +18,9 @@ package com.android.systemui.dagger;
import android.content.Context;
+import com.android.systemui.dagger.qualifiers.InstrumentationTest;
+import com.android.systemui.util.InitializationChecker;
+
import javax.inject.Singleton;
import dagger.BindsInstance;
@@ -37,7 +40,8 @@ public interface GlobalRootComponent {
interface Builder {
@BindsInstance
Builder context(Context context);
-
+ @BindsInstance
+ Builder instrumentationTest(@InstrumentationTest boolean test);
GlobalRootComponent build();
}
@@ -50,4 +54,9 @@ public interface GlobalRootComponent {
* Builder for a {@link SysUIComponent}, which makes it a subcomponent of this class.
*/
SysUIComponent.Builder getSysUIComponent();
+
+ /**
+ * Returns an {@link InitializationChecker}.
+ */
+ InitializationChecker getInitializationChecker();
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 4e48a5261f6b..2c1463d285f6 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -35,6 +35,7 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
import com.android.systemui.media.dagger.MediaModule;
+import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -43,6 +44,7 @@ import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
+import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManagerImpl;
@@ -94,9 +96,11 @@ import dagger.Provides;
* SystemUI code that variants of SystemUI _must_ include to function correctly.
*/
@Module(includes = {
+ GestureModule.class,
MediaModule.class,
PowerModule.class,
QSModule.class,
+ ReferenceScreenshotModule.class,
StartCentralSurfacesModule.class,
VolumeModule.class
})
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
index 5d34a6987b66..718befadd2b5 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java
@@ -21,7 +21,7 @@ import com.android.systemui.BootCompleteCacheImpl;
import com.android.systemui.CoreStartable;
import com.android.systemui.Dependency;
import com.android.systemui.InitController;
-import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIAppComponentFactoryBase;
import com.android.systemui.dagger.qualifiers.PerUser;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardSliceProvider;
@@ -31,28 +31,23 @@ import com.android.systemui.media.taptotransfer.MediaTttCommandLineHelper;
import com.android.systemui.media.taptotransfer.receiver.MediaTttChipControllerReceiver;
import com.android.systemui.media.taptotransfer.sender.MediaTttChipControllerSender;
import com.android.systemui.people.PeopleProvider;
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.unfold.FoldStateLogger;
import com.android.systemui.unfold.FoldStateLoggingProvider;
import com.android.systemui.unfold.SysUIUnfoldComponent;
import com.android.systemui.unfold.UnfoldLatencyTracker;
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider;
-import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.TaskViewFactory;
-import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
+import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
import java.util.Map;
@@ -83,18 +78,15 @@ public interface SysUIComponent {
@Subcomponent.Builder
interface Builder {
@BindsInstance
- Builder setPip(Optional<Pip> p);
+ Builder setShell(ShellInterface s);
@BindsInstance
- Builder setLegacySplitScreen(Optional<LegacySplitScreen> s);
+ Builder setPip(Optional<Pip> p);
@BindsInstance
Builder setSplitScreen(Optional<SplitScreen> s);
@BindsInstance
- Builder setAppPairs(Optional<AppPairs> s);
-
- @BindsInstance
Builder setOneHanded(Optional<OneHanded> o);
@BindsInstance
@@ -104,12 +96,6 @@ public interface SysUIComponent {
Builder setTaskViewFactory(Optional<TaskViewFactory> t);
@BindsInstance
- Builder setHideDisplayCutout(Optional<HideDisplayCutout> h);
-
- @BindsInstance
- Builder setShellCommandHandler(Optional<ShellCommandHandler> shellDump);
-
- @BindsInstance
Builder setTransitions(ShellTransitions t);
@BindsInstance
@@ -119,18 +105,9 @@ public interface SysUIComponent {
Builder setDisplayAreaHelper(Optional<DisplayAreaHelper> h);
@BindsInstance
- Builder setTaskSurfaceHelper(Optional<TaskSurfaceHelper> t);
-
- @BindsInstance
Builder setRecentTasks(Optional<RecentTasks> r);
@BindsInstance
- Builder setCompatUI(Optional<CompatUI> s);
-
- @BindsInstance
- Builder setDragAndDrop(Optional<DragAndDrop> d);
-
- @BindsInstance
Builder setBackAnimation(Optional<BackAnimation> b);
SysUIComponent build();
@@ -154,6 +131,7 @@ public interface SysUIComponent {
getMediaTttCommandLineHelper();
getMediaMuteAwaitConnectionCli();
getNearbyMediaDevicesManager();
+ getConnectivityInfoProcessor();
getUnfoldLatencyTracker().init();
getFoldStateLoggingProvider().ifPresent(FoldStateLoggingProvider::init);
getFoldStateLogger().ifPresent(FoldStateLogger::init);
@@ -236,6 +214,9 @@ public interface SysUIComponent {
/** */
Optional<NearbyMediaDevicesManager> getNearbyMediaDevicesManager();
+ /** */
+ Optional<ConnectivityInfoProcessor> getConnectivityInfoProcessor();
+
/**
* Returns {@link CoreStartable}s that should be started with the application.
*/
@@ -249,7 +230,7 @@ public interface SysUIComponent {
/**
* Member injection into the supplied argument.
*/
- void inject(SystemUIAppComponentFactory factory);
+ void inject(SystemUIAppComponentFactoryBase factory);
/**
* Member injection into the supplied argument.
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index a9f340854689..6db3e82a77b0 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -33,6 +33,7 @@ import com.android.systemui.log.SessionTracker
import com.android.systemui.media.RingtonePlayer
import com.android.systemui.power.PowerUI
import com.android.systemui.recents.Recents
+import com.android.systemui.settings.dagger.MultiUserUtilsModule
import com.android.systemui.shortcut.ShortcutKeyDispatcher
import com.android.systemui.statusbar.notification.InstantAppNotifier
import com.android.systemui.statusbar.phone.KeyguardLiftController
@@ -51,7 +52,7 @@ import dagger.multibindings.IntoMap
/**
* Collection of {@link CoreStartable}s that should be run on AOSP.
*/
-@Module
+@Module(includes = [MultiUserUtilsModule::class])
abstract class SystemUICoreStartableModule {
/** Inject into AuthController. */
@Binds
@@ -205,4 +206,4 @@ abstract class SystemUICoreStartableModule {
@IntoMap
@ClassKey(KeyguardLiftController::class)
abstract fun bindKeyguardLiftController(sysui: KeyguardLiftController): CoreStartable
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 366ef2651a92..ceb702eafe1e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -26,7 +26,6 @@ import com.android.keyguard.clock.ClockModule;
import com.android.keyguard.dagger.KeyguardBouncerComponent;
import com.android.systemui.BootCompleteCache;
import com.android.systemui.BootCompleteCacheImpl;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.appops.dagger.AppOpsModule;
import com.android.systemui.assist.AssistModule;
import com.android.systemui.biometrics.AlternateUdfpsTouchProvider;
@@ -43,20 +42,20 @@ import com.android.systemui.flags.FlagsModule;
import com.android.systemui.fragments.FragmentService;
import com.android.systemui.log.dagger.LogModule;
import com.android.systemui.lowlightclock.LowLightClockController;
+import com.android.systemui.media.dagger.MediaProjectionModule;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarComponent;
+import com.android.systemui.people.PeopleModule;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import com.android.systemui.privacy.PrivacyModule;
import com.android.systemui.recents.Recents;
import com.android.systemui.screenshot.dagger.ScreenshotModule;
-import com.android.systemui.settings.dagger.SettingsModule;
+import com.android.systemui.settings.dagger.MultiUserUtilsModule;
import com.android.systemui.smartspace.dagger.SmartspaceModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.QsFrameTranslateModule;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
@@ -71,7 +70,7 @@ import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfC
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -83,6 +82,7 @@ import com.android.systemui.unfold.SysUIUnfoldModule;
import com.android.systemui.user.UserModule;
import com.android.systemui.util.concurrency.SysUIConcurrencyModule;
import com.android.systemui.util.dagger.UtilModule;
+import com.android.systemui.util.kotlin.CoroutinesModule;
import com.android.systemui.util.sensors.SensorModule;
import com.android.systemui.util.settings.SettingsUtilModule;
import com.android.systemui.util.time.SystemClock;
@@ -115,22 +115,26 @@ import dagger.Provides;
AssistModule.class,
BiometricsModule.class,
ClockModule.class,
+ CoroutinesModule.class,
DreamModule.class,
ControlsModule.class,
DemoModeModule.class,
FalsingModule.class,
FlagsModule.class,
LogModule.class,
+ MediaProjectionModule.class,
PeopleHubModule.class,
+ PeopleModule.class,
PluginModule.class,
PrivacyModule.class,
QsFrameTranslateModule.class,
ScreenshotModule.class,
SensorModule.class,
- SettingsModule.class,
+ MultiUserUtilsModule.class,
SettingsUtilModule.class,
SmartRepliesInflationModule.class,
SmartspaceModule.class,
+ StatusBarPipelineModule.class,
StatusBarPolicyModule.class,
StatusBarWindowModule.class,
SysUIConcurrencyModule.class,
@@ -198,11 +202,6 @@ public abstract class SystemUIModule {
@Binds
abstract SystemClock bindSystemClock(SystemClockImpl systemClock);
- @Provides
- static SystemUIFactory getSystemUIFactory() {
- return SystemUIFactory.getInstance();
- }
-
// TODO: This should provided by the WM component
/** Provides Optional of BubbleManager */
@SysUISingleton
@@ -212,7 +211,6 @@ public abstract class SystemUIModule {
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
ShadeController shadeController,
- ConfigurationController configurationController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
NotificationVisibilityProvider visibilityProvider,
@@ -220,11 +218,9 @@ public abstract class SystemUIModule {
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
- NotifPipelineFlags notifPipelineFlags,
DumpManager dumpManager,
@Main Executor sysuiMainExecutor) {
return Optional.ofNullable(BubblesManager.create(context,
@@ -232,7 +228,6 @@ public abstract class SystemUIModule {
notificationShadeWindowController,
keyguardStateController,
shadeController,
- configurationController,
statusBarService,
notificationManager,
visibilityProvider,
@@ -240,11 +235,9 @@ public abstract class SystemUIModule {
zenModeController,
notifUserManager,
groupManager,
- entryManager,
notifCollection,
notifPipeline,
sysUiState,
- notifPipelineFlags,
dumpManager,
sysuiMainExecutor));
}
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
index b02074a65e9a..78a45f9d3310 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/WMComponent.java
@@ -21,29 +21,24 @@ import android.os.HandlerThread;
import androidx.annotation.Nullable;
-import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SystemUIInitializerFactory;
import com.android.systemui.tv.TvWMComponent;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.ShellInit;
+import com.android.wm.shell.sysui.ShellCommandHandler;
+import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.TaskViewFactory;
-import com.android.wm.shell.apppairs.AppPairs;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.common.annotations.ShellMainThread;
-import com.android.wm.shell.compatui.CompatUI;
import com.android.wm.shell.dagger.TvWMShellModule;
import com.android.wm.shell.dagger.WMShellModule;
import com.android.wm.shell.dagger.WMSingleton;
import com.android.wm.shell.displayareahelper.DisplayAreaHelper;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
-import com.android.wm.shell.legacysplitscreen.LegacySplitScreen;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.recents.RecentTasks;
import com.android.wm.shell.splitscreen.SplitScreen;
import com.android.wm.shell.startingsurface.StartingSurface;
-import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelper;
+import com.android.wm.shell.sysui.ShellInterface;
import com.android.wm.shell.transition.ShellTransitions;
import java.util.Optional;
@@ -54,7 +49,7 @@ import dagger.Subcomponent;
/**
* Dagger Subcomponent for WindowManager. This class explicitly describes the interfaces exported
* from the WM component into the SysUI component (in
- * {@link SystemUIFactory#init(Context, boolean)}), and references the specific dependencies
+ * {@link SystemUIInitializerFactory#init(Context, boolean)}), and references the specific dependencies
* provided by its particular device/form-factor SystemUI implementation.
*
* ie. {@link WMComponent} includes {@link WMShellModule}
@@ -80,14 +75,24 @@ public interface WMComponent {
* Initializes all the WMShell components before starting any of the SystemUI components.
*/
default void init() {
- getShellInit().init();
+ // TODO(238217847): To be removed once the dependencies are inverted and ShellController can
+ // inject these classes directly, otherwise, it's currently needed to ensure that these
+ // classes are created and set on the controller before onInit() is called
+ getShellInit();
+ getShellCommandHandler();
+ getShell().onInit();
}
@WMSingleton
+ ShellInterface getShell();
+
+ // TODO(238217847): To be removed once ShellController can inject ShellInit directly
+ @WMSingleton
ShellInit getShellInit();
+ // TODO(238217847): To be removed once ShellController can inject ShellCommandHandler directly
@WMSingleton
- Optional<ShellCommandHandler> getShellCommandHandler();
+ ShellCommandHandler getShellCommandHandler();
@WMSingleton
Optional<OneHanded> getOneHanded();
@@ -96,21 +101,12 @@ public interface WMComponent {
Optional<Pip> getPip();
@WMSingleton
- Optional<LegacySplitScreen> getLegacySplitScreen();
-
- @WMSingleton
Optional<SplitScreen> getSplitScreen();
@WMSingleton
- Optional<AppPairs> getAppPairs();
-
- @WMSingleton
Optional<Bubbles> getBubbles();
@WMSingleton
- Optional<HideDisplayCutout> getHideDisplayCutout();
-
- @WMSingleton
Optional<TaskViewFactory> getTaskViewFactory();
@WMSingleton
@@ -123,17 +119,8 @@ public interface WMComponent {
Optional<DisplayAreaHelper> getDisplayAreaHelper();
@WMSingleton
- Optional<TaskSurfaceHelper> getTaskSurfaceHelper();
-
- @WMSingleton
Optional<RecentTasks> getRecentTasks();
@WMSingleton
- Optional<CompatUI> getCompatUI();
-
- @WMSingleton
- Optional<DragAndDrop> getDragAndDrop();
-
- @WMSingleton
Optional<BackAnimation> getBackAnimation();
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
index edeff6e37182..a803a39f114f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDrop.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/qualifiers/InstrumentationTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,21 +14,21 @@
* limitations under the License.
*/
-package com.android.wm.shell.draganddrop;
+package com.android.systemui.dagger.qualifiers;
-import android.content.res.Configuration;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
-/**
- * Interface for telling DragAndDrop stuff.
- */
-@ExternalThread
-public interface DragAndDrop {
+import javax.inject.Qualifier;
- /** Called when the theme changes. */
- void onThemeChanged();
- /** Called when the configuration changes. */
- void onConfigChanged(Configuration newConfig);
+/**
+ * An annotation for injecting whether or not we are running in a test environment.
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface InstrumentationTest {
}
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 19287c133651..4161cf6d2657 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -332,6 +332,20 @@ public class DozeLog implements Dumpable {
}
/**
+ * Logs the car mode started event.
+ */
+ public void traceCarModeStarted() {
+ mLogger.logCarModeStarted();
+ }
+
+ /**
+ * Logs the car mode ended event.
+ */
+ public void traceCarModeEnded() {
+ mLogger.logCarModeEnded();
+ }
+
+ /**
* Appends power save changes that may cause a new doze state
* @param powerSaveActive true if power saving is active
* @param nextState the state that we'll transition to
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
index 4c81563e4f93..4b279ec8f008 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLogger.kt
@@ -105,7 +105,7 @@ class DozeLogger @Inject constructor(
bool4 = screenOnFromTouch
}, {
"Fling expand=$bool1 aboveThreshold=$bool2 thresholdNeeded=$bool3 " +
- "screenOnFromTouch=$bool4"
+ "screenOnFromTouch=$bool4"
})
}
@@ -151,7 +151,7 @@ class DozeLogger @Inject constructor(
long2 = triggerAt
}, {
"Time tick scheduledAt=${DATE_FORMAT.format(Date(long1))} " +
- "triggerAt=${DATE_FORMAT.format(Date(long2))}"
+ "triggerAt=${DATE_FORMAT.format(Date(long2))}"
})
}
@@ -220,7 +220,7 @@ class DozeLogger @Inject constructor(
str1 = partUpdated
}, {
"Posture changed, posture=${DevicePostureController.devicePostureToString(int1)}" +
- " partUpdated=$str1"
+ " partUpdated=$str1"
})
}
@@ -299,6 +299,18 @@ class DozeLogger @Inject constructor(
"Doze aod dimming scrim opacity set, opacity=$long1"
})
}
+
+ fun logCarModeEnded() {
+ buffer.log(TAG, INFO, {}, {
+ "Doze car mode ended"
+ })
+ }
+
+ fun logCarModeStarted() {
+ buffer.log(TAG, INFO, {}, {
+ "Doze car mode started"
+ })
+ }
}
private const val TAG = "DozeLog"
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 5779bb307790..32bc9de40f5b 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -20,6 +20,8 @@ import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWA
import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
import android.annotation.MainThread;
+import android.app.UiModeManager;
+import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.os.Trace;
import android.os.UserHandle;
@@ -33,7 +35,6 @@ import com.android.systemui.doze.dagger.WrappedService;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.Assert;
import com.android.systemui.util.wakelock.WakeLock;
@@ -66,6 +67,8 @@ public class DozeMachine {
INITIALIZED,
/** Regular doze. Device is asleep and listening for pulse triggers. */
DOZE,
+ /** Deep doze. Device is asleep and is not listening for pulse triggers. */
+ DOZE_SUSPEND_TRIGGERS,
/** Always-on doze. Device is asleep, showing UI and listening for pulse triggers. */
DOZE_AOD,
/** Pulse has been requested. Device is awake and preparing UI */
@@ -125,6 +128,7 @@ public class DozeMachine {
: Display.STATE_ON;
case DOZE_AOD_PAUSED:
case DOZE:
+ case DOZE_SUSPEND_TRIGGERS:
return Display.STATE_OFF;
case DOZE_PULSING:
case DOZE_PULSING_BRIGHT:
@@ -143,26 +147,27 @@ public class DozeMachine {
private final WakeLock mWakeLock;
private final AmbientDisplayConfiguration mConfig;
private final WakefulnessLifecycle mWakefulnessLifecycle;
- private final BatteryController mBatteryController;
private final DozeHost mDozeHost;
- private Part[] mParts;
+ private final UiModeManager mUiModeManager;
+ private final DockManager mDockManager;
+ private final Part[] mParts;
private final ArrayList<State> mQueuedRequests = new ArrayList<>();
private State mState = State.UNINITIALIZED;
private int mPulseReason;
private boolean mWakeLockHeldForCurrentState = false;
- private DockManager mDockManager;
@Inject
public DozeMachine(@WrappedService Service service, AmbientDisplayConfiguration config,
WakeLock wakeLock, WakefulnessLifecycle wakefulnessLifecycle,
- BatteryController batteryController, DozeLog dozeLog, DockManager dockManager,
+ UiModeManager uiModeManager,
+ DozeLog dozeLog, DockManager dockManager,
DozeHost dozeHost, Part[] parts) {
mDozeService = service;
mConfig = config;
mWakefulnessLifecycle = wakefulnessLifecycle;
mWakeLock = wakeLock;
- mBatteryController = batteryController;
+ mUiModeManager = uiModeManager;
mDozeLog = dozeLog;
mDockManager = dockManager;
mDozeHost = dozeHost;
@@ -244,7 +249,7 @@ public class DozeMachine {
Assert.isMainThread();
if (isExecutingTransition()) {
throw new IllegalStateException("Cannot get state because there were pending "
- + "transitions: " + mQueuedRequests.toString());
+ + "transitions: " + mQueuedRequests);
}
return mState;
}
@@ -313,11 +318,8 @@ public class DozeMachine {
}
mDozeLog.traceDozeStateSendComplete(newState);
- switch (newState) {
- case FINISH:
- mDozeService.finish();
- break;
- default:
+ if (newState == State.FINISH) {
+ mDozeService.finish();
}
}
@@ -357,6 +359,12 @@ public class DozeMachine {
if (mState == State.FINISH) {
return State.FINISH;
}
+ if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR
+ && (requestedState.canPulse() || requestedState.staysAwake())) {
+ Log.i(TAG, "Doze is suppressed with all triggers disabled as car mode is active");
+ mDozeLog.traceCarModeStarted();
+ return State.DOZE_SUSPEND_TRIGGERS;
+ }
if (mDozeHost.isAlwaysOnSuppressed() && requestedState.isAlwaysOn()) {
Log.i(TAG, "Doze is suppressed by an app. Suppressing state: " + requestedState);
mDozeLog.traceAlwaysOnSuppressed(requestedState, "app");
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 83220cab7149..60227ee95fc2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -147,6 +147,7 @@ public class DozeScreenBrightness extends BroadcastReceiver implements DozeMachi
setLightSensorEnabled(true);
break;
case DOZE:
+ case DOZE_SUSPEND_TRIGGERS:
setLightSensorEnabled(false);
resetBrightnessToDefault();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
index 89f50ad9fc21..7ed4b35e1ee7 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeSuppressor.java
@@ -17,6 +17,7 @@
package com.android.systemui.doze;
import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
+import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
@@ -96,6 +97,7 @@ public class DozeSuppressor implements DozeMachine.Part {
registerBroadcastReceiver();
mDozeHost.addCallback(mHostCallback);
checkShouldImmediatelyEndDoze();
+ checkShouldImmediatelySuspendDoze();
break;
case FINISH:
destroy();
@@ -110,11 +112,16 @@ public class DozeSuppressor implements DozeMachine.Part {
mDozeHost.removeCallback(mHostCallback);
}
+ private void checkShouldImmediatelySuspendDoze() {
+ if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
+ mDozeLog.traceCarModeStarted();
+ mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+ }
+ }
+
private void checkShouldImmediatelyEndDoze() {
String reason = null;
- if (mUiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_CAR) {
- reason = "car_mode";
- } else if (!mDozeHost.isProvisioned()) {
+ if (!mDozeHost.isProvisioned()) {
reason = "device_unprovisioned";
} else if (mBiometricUnlockControllerLazy.get().hasPendingAuthentication()) {
reason = "has_pending_auth";
@@ -141,6 +148,7 @@ public class DozeSuppressor implements DozeMachine.Part {
return;
}
IntentFilter filter = new IntentFilter(ACTION_ENTER_CAR_MODE);
+ filter.addAction(ACTION_EXIT_CAR_MODE);
mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter);
mBroadcastReceiverRegistered = true;
}
@@ -156,9 +164,14 @@ public class DozeSuppressor implements DozeMachine.Part {
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- if (ACTION_ENTER_CAR_MODE.equals(intent.getAction())) {
- mDozeLog.traceImmediatelyEndDoze("car_mode");
- mMachine.requestState(DozeMachine.State.FINISH);
+ String action = intent.getAction();
+ if (ACTION_ENTER_CAR_MODE.equals(action)) {
+ mDozeLog.traceCarModeStarted();
+ mMachine.requestState(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+ } else if (ACTION_EXIT_CAR_MODE.equals(action)) {
+ mDozeLog.traceCarModeEnded();
+ mMachine.requestState(mConfig.alwaysOnEnabled(UserHandle.USER_CURRENT)
+ ? DozeMachine.State.DOZE_AOD : DozeMachine.State.DOZE);
}
}
};
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 1cc5df5d04cf..0014d6bbaf24 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -16,6 +16,10 @@
package com.android.systemui.doze;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_SUSPEND_TRIGGERS;
+import static com.android.systemui.doze.DozeMachine.State.FINISH;
+import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
+
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -437,14 +441,18 @@ public class DozeTriggers implements DozeMachine.Part {
@Override
public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
+ if (oldState == DOZE_SUSPEND_TRIGGERS && (newState != FINISH
+ && newState != UNINITIALIZED)) {
+ // Register callbacks that were unregistered when we switched to
+ // DOZE_SUSPEND_TRIGGERS state.
+ registerCallbacks();
+ }
switch (newState) {
case INITIALIZED:
mAodInterruptRunnable = null;
sWakeDisplaySensorState = true;
- mBroadcastReceiver.register(mBroadcastDispatcher);
- mDockManager.addListener(mDockEventListener);
+ registerCallbacks();
mDozeSensors.requestTemporaryDisable();
- mDozeHost.addCallback(mHostCallback);
break;
case DOZE:
case DOZE_AOD:
@@ -472,21 +480,36 @@ public class DozeTriggers implements DozeMachine.Part {
case DOZE_PULSE_DONE:
mDozeSensors.requestTemporaryDisable();
break;
+ case DOZE_SUSPEND_TRIGGERS:
case FINISH:
- mBroadcastReceiver.unregister(mBroadcastDispatcher);
- mDozeHost.removeCallback(mHostCallback);
- mDockManager.removeListener(mDockEventListener);
- mDozeSensors.setListening(false, false);
- mDozeSensors.setProxListening(false);
- mWantSensors = false;
- mWantProxSensor = false;
- mWantTouchScreenSensors = false;
+ stopListeningToAllTriggers();
break;
default:
}
mDozeSensors.setListening(mWantSensors, mWantTouchScreenSensors);
}
+ private void registerCallbacks() {
+ mBroadcastReceiver.register(mBroadcastDispatcher);
+ mDockManager.addListener(mDockEventListener);
+ mDozeHost.addCallback(mHostCallback);
+ }
+
+ private void unregisterCallbacks() {
+ mBroadcastReceiver.unregister(mBroadcastDispatcher);
+ mDozeHost.removeCallback(mHostCallback);
+ mDockManager.removeListener(mDockEventListener);
+ }
+
+ private void stopListeningToAllTriggers() {
+ unregisterCallbacks();
+ mDozeSensors.setListening(false, false);
+ mDozeSensors.setProxListening(false);
+ mWantSensors = false;
+ mWantProxSensor = false;
+ mWantTouchScreenSensors = false;
+ }
+
@Override
public void onScreenState(int state) {
mDozeSensors.onScreenState(state);
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
index e568b8282856..7c816cec08f2 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeUi.java
@@ -26,8 +26,6 @@ import android.os.SystemClock;
import android.text.format.Formatter;
import android.util.Log;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.doze.dagger.DozeScope;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
@@ -44,8 +42,6 @@ import javax.inject.Inject;
*/
@DozeScope
public class DozeUi implements DozeMachine.Part {
- // if enabled, calls dozeTimeTick() whenever the time changes:
- private static final boolean BURN_IN_TESTING_ENABLED = false;
private static final long TIME_TICK_DEADLINE_MILLIS = 90 * 1000; // 1.5min
private final Context mContext;
private final DozeHost mHost;
@@ -57,26 +53,13 @@ public class DozeUi implements DozeMachine.Part {
private final DozeParameters mDozeParameters;
private final DozeLog mDozeLog;
private final StatusBarStateController mStatusBarStateController;
- private final KeyguardUpdateMonitorCallback mKeyguardVisibilityCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onTimeChanged() {
- if (BURN_IN_TESTING_ENABLED && mStatusBarStateController.isDozing()) {
- // update whenever the time changes for manual burn in testing
- mHost.dozeTimeTick();
-
- // Keep wakelock until a frame has been pushed.
- mHandler.post(mWakeLock.wrap(() -> {}));
- }
- }
- };
private long mLastTimeTickElapsed = 0;
@Inject
public DozeUi(Context context, AlarmManager alarmManager,
WakeLock wakeLock, DozeHost host, @Main Handler handler,
- DozeParameters params, KeyguardUpdateMonitor keyguardUpdateMonitor,
+ DozeParameters params,
StatusBarStateController statusBarStateController,
DozeLog dozeLog) {
mContext = context;
@@ -86,7 +69,6 @@ public class DozeUi implements DozeMachine.Part {
mCanAnimateTransition = !params.getDisplayNeedsBlanking();
mDozeParameters = params;
mTimeTicker = new AlarmTimeout(alarmManager, this::onTimeTick, "doze_time_tick", handler);
- keyguardUpdateMonitor.registerCallback(mKeyguardVisibilityCallback);
mDozeLog = dozeLog;
mStatusBarStateController = statusBarStateController;
}
@@ -139,6 +121,7 @@ public class DozeUi implements DozeMachine.Part {
break;
case DOZE:
case DOZE_AOD_PAUSED:
+ case DOZE_SUSPEND_TRIGGERS:
unscheduleTimeTick();
break;
case DOZE_REQUEST_PULSE:
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 74949d094e33..d7b7777559da 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -183,22 +183,22 @@ public class DreamOverlayContainerViewController extends ViewController<DreamOve
}
private void updateBurnInOffsets() {
- int burnInOffset = mMaxBurnInOffset;
-
// Make sure the offset starts at zero, to avoid a big jump in the overlay when it first
// appears.
- long millisSinceStart = System.currentTimeMillis() - mJitterStartTimeMillis;
+ final long millisSinceStart = System.currentTimeMillis() - mJitterStartTimeMillis;
+ final int burnInOffset;
if (millisSinceStart < mMillisUntilFullJitter) {
float lerpAmount = (float) millisSinceStart / (float) mMillisUntilFullJitter;
- burnInOffset = Math.round(MathUtils.lerp(0f, burnInOffset, lerpAmount));
+ burnInOffset = Math.round(MathUtils.lerp(0f, mMaxBurnInOffset, lerpAmount));
+ } else {
+ burnInOffset = mMaxBurnInOffset;
}
// These translation values change slowly, and the set translation methods are idempotent,
// so no translation occurs when the values don't change.
- int burnInOffsetX = getBurnInOffset(burnInOffset * 2, true)
- - burnInOffset;
- int burnInOffsetY = getBurnInOffset(burnInOffset * 2, false)
- - burnInOffset;
+ final int halfBurnInOffset = burnInOffset / 2;
+ final int burnInOffsetX = getBurnInOffset(burnInOffset, true) - halfBurnInOffset;
+ final int burnInOffsetY = getBurnInOffset(burnInOffset, false) - halfBurnInOffset;
mView.setTranslationX(burnInOffsetX);
mView.setTranslationY(burnInOffsetY);
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java
index 6589f26dbde2..f9fc1f3ca184 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayNotificationCountProvider.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
-import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
@@ -33,13 +32,10 @@ import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;
-import javax.inject.Inject;
-
/***
* {@link DreamOverlayNotificationCountProvider} provides the current notification count to
- * registered callbacks.
+ * registered callbacks. Ongoing notifications are not included in the count.
*/
-@SysUISingleton
public class DreamOverlayNotificationCountProvider
implements CallbackController<DreamOverlayNotificationCountProvider.Callback> {
private final Set<String> mNotificationKeys = new HashSet<>();
@@ -49,6 +45,10 @@ public class DreamOverlayNotificationCountProvider
@Override
public void onNotificationPosted(
StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) {
+ if (sbn.isOngoing()) {
+ // Don't count ongoing notifications.
+ return;
+ }
mNotificationKeys.add(sbn.getKey());
reportNotificationCountChanged();
}
@@ -78,7 +78,6 @@ public class DreamOverlayNotificationCountProvider
}
};
- @Inject
public DreamOverlayNotificationCountProvider(
NotificationListener notificationListener,
@Background Executor bgExecutor) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
index 994c63002ac2..99ca3c76cf8d 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.os.PatternMatcher;
import android.os.RemoteException;
@@ -31,7 +32,6 @@ import android.service.dreams.IDreamManager;
import android.util.Log;
import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import javax.inject.Inject;
@@ -66,24 +66,16 @@ public class DreamOverlayRegistrant extends CoreStartable {
final int enabledState =
packageManager.getComponentEnabledSetting(mOverlayServiceComponent);
+ // The overlay service is only registered when its component setting is enabled.
+ boolean register = false;
- // TODO(b/204626521): We should not have to set the component enabled setting if the
- // enabled config flag is properly applied based on the RRO.
- if (enabledState != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
- final int overlayState = mResources.getBoolean(R.bool.config_dreamOverlayServiceEnabled)
- ? PackageManager.COMPONENT_ENABLED_STATE_ENABLED
- : PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
-
- if (overlayState != enabledState) {
- packageManager
- .setComponentEnabledSetting(mOverlayServiceComponent, overlayState, 0);
- }
+ try {
+ register = packageManager.getServiceInfo(mOverlayServiceComponent,
+ PackageManager.GET_META_DATA).enabled;
+ } catch (NameNotFoundException e) {
+ Log.e(TAG, "could not find dream overlay service");
}
- // The overlay service is only registered when its component setting is enabled.
- boolean register = packageManager.getComponentEnabledSetting(mOverlayServiceComponent)
- == PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
-
if (mCurrentRegisteredState == register) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index fc71e2fb2329..69e41ba9b284 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -101,6 +101,9 @@ public class DreamOverlayStateController implements
public void addComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.add(complication)) {
+ if (DEBUG) {
+ Log.d(TAG, "addComplication: added " + complication);
+ }
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
@@ -112,6 +115,9 @@ public class DreamOverlayStateController implements
public void removeComplication(Complication complication) {
mExecutor.execute(() -> {
if (mComplications.remove(complication)) {
+ if (DEBUG) {
+ Log.d(TAG, "removeComplication: removed " + complication);
+ }
mCallbacks.stream().forEach(callback -> callback.onComplicationsChanged());
}
});
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
index 59a17bad5069..a25257d6cf42 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarView.java
@@ -43,6 +43,8 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
STATUS_ICON_NOTIFICATIONS,
STATUS_ICON_WIFI_UNAVAILABLE,
STATUS_ICON_ALARM_SET,
+ STATUS_ICON_CAMERA_DISABLED,
+ STATUS_ICON_MIC_DISABLED,
STATUS_ICON_MIC_CAMERA_DISABLED,
STATUS_ICON_PRIORITY_MODE_ON
})
@@ -50,8 +52,10 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
public static final int STATUS_ICON_NOTIFICATIONS = 0;
public static final int STATUS_ICON_WIFI_UNAVAILABLE = 1;
public static final int STATUS_ICON_ALARM_SET = 2;
- public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 3;
- public static final int STATUS_ICON_PRIORITY_MODE_ON = 4;
+ public static final int STATUS_ICON_CAMERA_DISABLED = 3;
+ public static final int STATUS_ICON_MIC_DISABLED = 4;
+ public static final int STATUS_ICON_MIC_CAMERA_DISABLED = 5;
+ public static final int STATUS_ICON_PRIORITY_MODE_ON = 6;
private final Map<Integer, View> mStatusIcons = new HashMap<>();
@@ -80,6 +84,10 @@ public class DreamOverlayStatusBarView extends ConstraintLayout {
fetchStatusIconForResId(R.id.dream_overlay_wifi_status));
mStatusIcons.put(STATUS_ICON_ALARM_SET,
fetchStatusIconForResId(R.id.dream_overlay_alarm_set));
+ mStatusIcons.put(STATUS_ICON_CAMERA_DISABLED,
+ fetchStatusIconForResId(R.id.dream_overlay_camera_off));
+ mStatusIcons.put(STATUS_ICON_MIC_DISABLED,
+ fetchStatusIconForResId(R.id.dream_overlay_mic_off));
mStatusIcons.put(STATUS_ICON_MIC_CAMERA_DISABLED,
fetchStatusIconForResId(R.id.dream_overlay_camera_mic_off));
mStatusIcons.put(STATUS_ICON_NOTIFICATIONS,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index e878b22f61ed..de7bf28c01d6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -49,6 +49,7 @@ import com.android.systemui.util.time.DateFormatUtil;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -65,7 +66,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
private final Resources mResources;
private final DateFormatUtil mDateFormatUtil;
private final IndividualSensorPrivacyController mSensorPrivacyController;
- private final DreamOverlayNotificationCountProvider mDreamOverlayNotificationCountProvider;
+ private final Optional<DreamOverlayNotificationCountProvider>
+ mDreamOverlayNotificationCountProvider;
private final ZenModeController mZenModeController;
private final Executor mMainExecutor;
@@ -125,7 +127,7 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
NextAlarmController nextAlarmController,
DateFormatUtil dateFormatUtil,
IndividualSensorPrivacyController sensorPrivacyController,
- DreamOverlayNotificationCountProvider dreamOverlayNotificationCountProvider,
+ Optional<DreamOverlayNotificationCountProvider> dreamOverlayNotificationCountProvider,
ZenModeController zenModeController,
StatusBarWindowStateController statusBarWindowStateController) {
super(view);
@@ -161,7 +163,9 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
mZenModeController.addCallback(mZenModeCallback);
updatePriorityModeStatusIcon();
- mDreamOverlayNotificationCountProvider.addCallback(mNotificationCountCallback);
+ mDreamOverlayNotificationCountProvider.ifPresent(
+ provider -> provider.addCallback(mNotificationCountCallback));
+
mTouchInsetSession.addViewToTracking(mView);
}
@@ -171,7 +175,8 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
mSensorPrivacyController.removeCallback(mSensorCallback);
mNextAlarmController.removeCallback(mNextAlarmCallback);
mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
- mDreamOverlayNotificationCountProvider.removeCallback(mNotificationCountCallback);
+ mDreamOverlayNotificationCountProvider.ifPresent(
+ provider -> provider.removeCallback(mNotificationCountCallback));
mTouchInsetSession.clear();
mIsAttached = false;
@@ -209,9 +214,17 @@ public class DreamOverlayStatusBarViewController extends ViewController<DreamOve
.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE);
final boolean cameraBlocked = mSensorPrivacyController
.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA);
- showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED,
- micBlocked && cameraBlocked);
+ @DreamOverlayStatusBarView.StatusIconType int iconType = Resources.ID_NULL;
+ if (micBlocked && cameraBlocked) {
+ iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED;
+ } else if (!micBlocked && cameraBlocked) {
+ iconType = DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED;
+ } else if (micBlocked && !cameraBlocked) {
+ iconType = DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED;
+ }
+ if (iconType != Resources.ID_NULL) {
+ showIcon(iconType, true);
+ }
}
private String buildNotificationsContentDescription(int notificationCount) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
index a83e006dfa2f..be94e5031917 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/SmartSpaceComplication.java
@@ -26,7 +26,7 @@ import com.android.systemui.CoreStartable;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.dreams.complication.ComplicationLayoutParams;
import com.android.systemui.dreams.complication.ComplicationViewModel;
-import com.android.systemui.dreams.smartspace.DreamsSmartspaceController;
+import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import java.util.List;
@@ -43,7 +43,7 @@ public class SmartSpaceComplication implements Complication {
* SystemUI.
*/
public static class Registrant extends CoreStartable {
- private final DreamsSmartspaceController mSmartSpaceController;
+ private final DreamSmartspaceController mSmartSpaceController;
private final DreamOverlayStateController mDreamOverlayStateController;
private final SmartSpaceComplication mComplication;
@@ -66,7 +66,7 @@ public class SmartSpaceComplication implements Complication {
public Registrant(Context context,
DreamOverlayStateController dreamOverlayStateController,
SmartSpaceComplication smartSpaceComplication,
- DreamsSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController) {
super(context);
mDreamOverlayStateController = dreamOverlayStateController;
mComplication = smartSpaceComplication;
@@ -82,6 +82,7 @@ public class SmartSpaceComplication implements Complication {
mSmartSpaceController.addListener(mSmartspaceListener);
} else {
mSmartSpaceController.removeListener(mSmartspaceListener);
+ mDreamOverlayStateController.removeComplication(mComplication);
}
}
});
@@ -89,25 +90,30 @@ public class SmartSpaceComplication implements Complication {
}
private static class SmartSpaceComplicationViewHolder implements ViewHolder {
+ private View mView = null;
private static final int SMARTSPACE_COMPLICATION_WEIGHT = 10;
- private final DreamsSmartspaceController mSmartSpaceController;
+ private final DreamSmartspaceController mSmartSpaceController;
private final Context mContext;
protected SmartSpaceComplicationViewHolder(
Context context,
- DreamsSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController) {
mSmartSpaceController = smartSpaceController;
mContext = context;
}
@Override
public View getView() {
+ if (mView != null) {
+ return mView;
+ }
final FrameLayout smartSpaceContainer = new FrameLayout(mContext);
smartSpaceContainer.addView(
mSmartSpaceController.buildAndConnectView(smartSpaceContainer),
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
+ mView = smartSpaceContainer;
return smartSpaceContainer;
}
@@ -120,12 +126,12 @@ public class SmartSpaceComplication implements Complication {
}
}
- private final DreamsSmartspaceController mSmartSpaceController;
+ private final DreamSmartspaceController mSmartSpaceController;
private final Context mContext;
@Inject
public SmartSpaceComplication(Context context,
- DreamsSmartspaceController smartSpaceController) {
+ DreamSmartspaceController smartSpaceController) {
mContext = context;
mSmartSpaceController = smartSpaceController;
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index 4c0154faeb5a..fd6cfc0700ad 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -21,6 +21,7 @@ import static com.android.systemui.dreams.complication.dagger.ComplicationModule
import android.graphics.Rect;
import android.graphics.Region;
+import android.os.Debug;
import android.util.Log;
import android.view.View;
@@ -44,7 +45,8 @@ import javax.inject.Named;
* a {@link ComplicationLayoutEngine}.
*/
public class ComplicationHostViewController extends ViewController<ConstraintLayout> {
- public static final String TAG = "ComplicationHostViewController";
+ private static final String TAG = "ComplicationHostVwCtrl";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private final ComplicationLayoutEngine mLayoutEngine;
private final LifecycleOwner mLifecycleOwner;
@@ -90,6 +92,11 @@ public class ComplicationHostViewController extends ViewController<ConstraintLay
}
private void updateComplications(Collection<ComplicationViewModel> complications) {
+ if (DEBUG) {
+ Log.d(TAG, "updateComplications called. Callers = " + Debug.getCallers(25));
+ Log.d(TAG, " mComplications = " + mComplications.toString());
+ Log.d(TAG, " complications = " + complications.toString());
+ }
final Collection<ComplicationId> ids = complications.stream()
.map(complicationViewModel -> complicationViewModel.getId())
.collect(Collectors.toSet());
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
index ded61a8b80d9..9cd149b9bfee 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationLayoutEngine.java
@@ -54,7 +54,7 @@ import javax.inject.Named;
*/
@DreamOverlayComponent.DreamOverlayScope
public class ComplicationLayoutEngine implements Complication.VisibilityController {
- public static final String TAG = "ComplicationLayoutEngine";
+ public static final String TAG = "ComplicationLayoutEng";
/**
* {@link ViewEntry} is an internal container, capturing information necessary for working with
@@ -529,7 +529,7 @@ public class ComplicationLayoutEngine implements Complication.VisibilityControll
*/
public void addComplication(ComplicationId id, View view,
ComplicationLayoutParams lp, @Complication.Category int category) {
- Log.d(TAG, "engine: " + this + " addComplication");
+ Log.d(TAG, "@" + Integer.toHexString(this.hashCode()) + " addComplication: " + id);
// If the complication is present, remove.
if (mEntries.containsKey(id)) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
index f0239371ee63..00cf58c0c665 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationViewModel.java
@@ -64,4 +64,9 @@ public class ComplicationViewModel extends ViewModel {
public void exitDream() {
mHost.requestExitDream();
}
+
+ @Override
+ public String toString() {
+ return mId + "=" + mComplication.toString();
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
deleted file mode 100644
index f5c5a434a077..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamWeatherComplication.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication;
-
-import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS;
-import static com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent.DreamWeatherComplicationModule.DREAM_WEATHER_COMPLICATION_VIEW;
-
-import android.app.smartspace.SmartspaceAction;
-import android.app.smartspace.SmartspaceTarget;
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
-import android.text.TextUtils;
-import android.widget.TextView;
-
-import com.android.systemui.CoreStartable;
-import com.android.systemui.R;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.dreams.complication.dagger.DreamWeatherComplicationComponent;
-import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener;
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-import com.android.systemui.util.ViewController;
-
-import javax.inject.Inject;
-import javax.inject.Named;
-
-/**
- * Weather Complication that produce Weather view holder.
- */
-public class DreamWeatherComplication implements Complication {
- DreamWeatherComplicationComponent.Factory mComponentFactory;
-
- /**
- * Default constructor for {@link DreamWeatherComplication}.
- */
- @Inject
- public DreamWeatherComplication(
- DreamWeatherComplicationComponent.Factory componentFactory) {
- mComponentFactory = componentFactory;
- }
-
- @Override
- public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_WEATHER;
- }
-
- /**
- * Create {@link DreamWeatherViewHolder}.
- */
- @Override
- public ViewHolder createView(ComplicationViewModel model) {
- return mComponentFactory.create().getViewHolder();
- }
-
- /**
- * {@link CoreStartable} for registering {@link DreamWeatherComplication} with SystemUI.
- */
- public static class Registrant extends CoreStartable {
- private final LockscreenSmartspaceController mSmartSpaceController;
- private final DreamOverlayStateController mDreamOverlayStateController;
- private final DreamWeatherComplication mComplication;
-
- /**
- * Default constructor to register {@link DreamWeatherComplication}.
- */
- @Inject
- public Registrant(Context context,
- LockscreenSmartspaceController smartspaceController,
- DreamOverlayStateController dreamOverlayStateController,
- DreamWeatherComplication dreamWeatherComplication) {
- super(context);
- mSmartSpaceController = smartspaceController;
- mDreamOverlayStateController = dreamOverlayStateController;
- mComplication = dreamWeatherComplication;
- }
-
- @Override
- public void start() {
- if (mSmartSpaceController.isEnabled()) {
- mDreamOverlayStateController.addComplication(mComplication);
- }
- }
- }
-
- /**
- * ViewHolder to contain value/logic associated with a Weather Complication View.
- */
- public static class DreamWeatherViewHolder implements ViewHolder {
- private final TextView mView;
- private final ComplicationLayoutParams mLayoutParams;
- private final DreamWeatherViewController mViewController;
-
- @Inject
- DreamWeatherViewHolder(
- @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
- DreamWeatherViewController controller,
- @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
- ComplicationLayoutParams layoutParams) {
- mView = view;
- mLayoutParams = layoutParams;
- mViewController = controller;
- mViewController.init();
- }
-
- @Override
- public TextView getView() {
- return mView;
- }
-
- @Override
- public ComplicationLayoutParams getLayoutParams() {
- return mLayoutParams;
- }
- }
-
- /**
- * ViewController to contain value/logic associated with a Weather Complication View.
- */
- static class DreamWeatherViewController extends ViewController<TextView> {
- private final LockscreenSmartspaceController mSmartSpaceController;
- private SmartspaceTargetListener mSmartspaceTargetListener;
-
- @Inject
- DreamWeatherViewController(
- @Named(DREAM_WEATHER_COMPLICATION_VIEW) TextView view,
- LockscreenSmartspaceController smartspaceController
- ) {
- super(view);
- mSmartSpaceController = smartspaceController;
- }
-
- @Override
- protected void onViewAttached() {
- mSmartspaceTargetListener = targets -> targets.forEach(
- t -> {
- if (t instanceof SmartspaceTarget
- && ((SmartspaceTarget) t).getFeatureType()
- == SmartspaceTarget.FEATURE_WEATHER) {
- final SmartspaceTarget target = (SmartspaceTarget) t;
- final SmartspaceAction headerAction = target.getHeaderAction();
- if (headerAction == null || TextUtils.isEmpty(
- headerAction.getTitle())) {
- return;
- }
-
- String temperature = headerAction.getTitle().toString();
- mView.setText(temperature);
- final Icon icon = headerAction.getIcon();
- if (icon != null) {
- final int iconSize =
- getResources().getDimensionPixelSize(
- R.dimen.smart_action_button_icon_size);
- final Drawable iconDrawable = icon.loadDrawable(getContext());
- iconDrawable.setBounds(0, 0, iconSize, iconSize);
- mView.setCompoundDrawables(iconDrawable, null, null, null);
- mView.setCompoundDrawablePadding(
- getResources().getDimensionPixelSize(
- R.dimen.smart_action_button_icon_padding));
-
- }
- }
- });
- mSmartSpaceController.addListener(mSmartspaceTargetListener);
- }
-
- @Override
- protected void onViewDetached() {
- mSmartSpaceController.removeListener(mSmartspaceTargetListener);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
index eb2fc5d1a93e..3ab26ceeb076 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamClockDateComplicationModule.java
@@ -41,7 +41,7 @@ public interface DreamClockDateComplicationModule {
"clock_date_complication_layout_params";
// Order weight of insert into parent container
//TODO(b/217199227): move to a single location.
- int INSERT_ORDER_WEIGHT = 2;
+ int INSERT_ORDER_WEIGHT = 3;
/**
* Provides the complication view.
@@ -66,6 +66,6 @@ public interface DreamClockDateComplicationModule {
ComplicationLayoutParams.POSITION_BOTTOM
| ComplicationLayoutParams.POSITION_START,
ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT);
+ INSERT_ORDER_WEIGHT, /* snapToGuide= */ true);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
deleted file mode 100644
index 536f3dcd2850..000000000000
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamWeatherComplicationComponent.java
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.dreams.complication.dagger;
-
-
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
-
-import android.view.LayoutInflater;
-import android.view.ViewGroup;
-import android.widget.TextView;
-
-import com.android.internal.util.Preconditions;
-import com.android.systemui.R;
-import com.android.systemui.dreams.complication.ComplicationLayoutParams;
-import com.android.systemui.dreams.complication.DreamWeatherComplication.DreamWeatherViewHolder;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.Retention;
-
-import javax.inject.Named;
-import javax.inject.Scope;
-
-import dagger.Module;
-import dagger.Provides;
-import dagger.Subcomponent;
-
-/**
- * {@link DreamWeatherComplicationComponent} is responsible for generating dependencies surrounding
- * the
- * Clock Date {@link com.android.systemui.dreams.complication.Complication}, such as the layout
- * details.
- */
-@Subcomponent(modules = {
- DreamWeatherComplicationComponent.DreamWeatherComplicationModule.class,
-})
-@DreamWeatherComplicationComponent.DreamWeatherComplicationScope
-public interface DreamWeatherComplicationComponent {
- /**
- * Creates {@link DreamWeatherViewHolder}.
- */
- DreamWeatherViewHolder getViewHolder();
-
- @Documented
- @Retention(RUNTIME)
- @Scope
- @interface DreamWeatherComplicationScope {
- }
-
- /**
- * Generates {@link DreamWeatherComplicationComponent}.
- */
- @Subcomponent.Factory
- interface Factory {
- DreamWeatherComplicationComponent create();
- }
-
- /**
- * Scoped values for {@link DreamWeatherComplicationComponent}.
- */
- @Module
- interface DreamWeatherComplicationModule {
- String DREAM_WEATHER_COMPLICATION_VIEW = "weather_complication_view";
- String DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS =
- "weather_complication_layout_params";
- // Order weight of insert into parent container
- int INSERT_ORDER_WEIGHT = 1;
-
- /**
- * Provides the complication view.
- */
- @Provides
- @DreamWeatherComplicationScope
- @Named(DREAM_WEATHER_COMPLICATION_VIEW)
- static TextView provideComplicationView(LayoutInflater layoutInflater) {
- return Preconditions.checkNotNull((TextView)
- layoutInflater.inflate(R.layout.dream_overlay_complication_weather,
- null, false),
- "R.layout.dream_overlay_complication_weather did not properly inflated");
- }
-
- /**
- * Provides the layout parameters for the complication view.
- */
- @Provides
- @DreamWeatherComplicationScope
- @Named(DREAM_WEATHER_COMPLICATION_LAYOUT_PARAMS)
- static ComplicationLayoutParams provideLayoutParams() {
- return new ComplicationLayoutParams(0,
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ComplicationLayoutParams.POSITION_BOTTOM
- | ComplicationLayoutParams.POSITION_START,
- ComplicationLayoutParams.DIRECTION_END,
- INSERT_ORDER_WEIGHT);
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
index 62a4140c6745..e45437dedd6e 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java
@@ -27,9 +27,6 @@ import dagger.Module;
@Module(includes = {
DreamClockDateComplicationModule.class,
DreamClockTimeComplicationModule.class,
- },
- subcomponents = {
- DreamWeatherComplicationComponent.class,
})
public interface RegisteredComplicationsModule {
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
index c1dff248818f..2dd2098a78b9 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamModule.java
@@ -17,10 +17,18 @@
package com.android.systemui.dreams.dagger;
import android.content.Context;
+import android.content.res.Resources;
import com.android.settingslib.dream.DreamBackend;
+import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.DreamOverlayNotificationCountProvider;
import com.android.systemui.dreams.complication.dagger.RegisteredComplicationsModule;
+import java.util.Optional;
+
+import javax.inject.Named;
+
import dagger.Module;
import dagger.Provides;
@@ -34,6 +42,10 @@ import dagger.Provides;
DreamOverlayComponent.class,
})
public interface DreamModule {
+ String DREAM_ONLY_ENABLED_FOR_SYSTEM_USER = "dream_only_enabled_for_system_user";
+
+ String DREAM_SUPPORTED = "dream_supported";
+
/**
* Provides an instance of the dream backend.
*/
@@ -41,4 +53,31 @@ public interface DreamModule {
static DreamBackend providesDreamBackend(Context context) {
return DreamBackend.getInstance(context);
}
+
+ /**
+ * Provides an instance of a {@link DreamOverlayNotificationCountProvider}.
+ */
+ @SysUISingleton
+ @Provides
+ static Optional<DreamOverlayNotificationCountProvider>
+ providesDreamOverlayNotificationCountProvider() {
+ // If we decide to bring this back, we should gate it on a config that can be changed in
+ // an overlay.
+ return Optional.empty();
+ }
+
+ /** */
+ @Provides
+ @Named(DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
+ static boolean providesDreamOnlyEnabledForSystemUser(@Main Resources resources) {
+ return resources.getBoolean(
+ com.android.internal.R.bool.config_dreamsOnlyEnabledForSystemUser);
+ }
+
+ /** */
+ @Provides
+ @Named(DREAM_SUPPORTED)
+ static boolean providesDreamSupported(@Main Resources resources) {
+ return resources.getBoolean(com.android.internal.R.bool.config_dreamsSupported);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
index a3095472783b..63f63a5093d2 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamsSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/smartspace/DreamSmartspaceController.kt
@@ -19,6 +19,7 @@ package com.android.systemui.dreams.smartspace
import android.app.smartspace.SmartspaceConfig
import android.app.smartspace.SmartspaceManager
import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
import android.content.Context
import android.graphics.Color
import android.util.Log
@@ -46,7 +47,7 @@ import javax.inject.Named
* Controller for managing the smartspace view on the dream
*/
@SysUISingleton
-class DreamsSmartspaceController @Inject constructor(
+class DreamSmartspaceController @Inject constructor(
private val context: Context,
private val smartspaceManager: SmartspaceManager,
private val execution: Execution,
@@ -58,7 +59,7 @@ class DreamsSmartspaceController @Inject constructor(
@Named(DREAM_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>
) {
companion object {
- private const val TAG = "DreamsSmartspaceCtrlr"
+ private const val TAG = "DreamSmartspaceCtrlr"
}
private var session: SmartspaceSession? = null
@@ -66,7 +67,9 @@ class DreamsSmartspaceController @Inject constructor(
private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
// A shadow copy of listeners is maintained to track whether the session should remain open.
- private var listeners = mutableSetOf<BcSmartspaceDataPlugin.SmartspaceTargetListener>()
+ private var listeners = mutableSetOf<SmartspaceTargetListener>()
+
+ private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>()
// Smartspace can be used on multiple displays, such as when the user casts their screen
private var smartspaceViews = mutableSetOf<SmartspaceView>()
@@ -113,6 +116,7 @@ class DreamsSmartspaceController @Inject constructor(
private val sessionListener = SmartspaceSession.OnTargetsAvailableListener { targets ->
execution.assertIsMainThread()
+ onTargetsAvailableUnfiltered(targets)
val filteredTargets = targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
plugin?.onTargetsAvailable(filteredTargets)
}
@@ -137,13 +141,10 @@ class DreamsSmartspaceController @Inject constructor(
private fun buildView(parent: ViewGroup): View? {
return if (plugin != null) {
var view = smartspaceViewComponentFactory.create(parent, plugin, stateChangeListener)
- .getView()
+ .getView()
if (view !is View) {
return null
}
-
- view.setIsDreaming(true)
-
return view
} else {
null
@@ -151,7 +152,8 @@ class DreamsSmartspaceController @Inject constructor(
}
private fun hasActiveSessionListeners(): Boolean {
- return smartspaceViews.isNotEmpty() || listeners.isNotEmpty()
+ return smartspaceViews.isNotEmpty() || listeners.isNotEmpty() ||
+ unfilteredListeners.isNotEmpty()
}
private fun connectSession() {
@@ -164,13 +166,15 @@ class DreamsSmartspaceController @Inject constructor(
}
val newSession = smartspaceManager.createSmartspaceSession(
- SmartspaceConfig.Builder(context, "dream").build())
+ SmartspaceConfig.Builder(context, "dream").build()
+ )
Log.d(TAG, "Starting smartspace session for dream")
newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
this.session = newSession
plugin.registerSmartspaceEventNotifier {
- e -> session?.notifySmartspaceEvent(e)
+ e ->
+ session?.notifySmartspaceEvent(e)
}
reloadSmartspace()
@@ -218,4 +222,22 @@ class DreamsSmartspaceController @Inject constructor(
private fun reloadSmartspace() {
session?.requestSmartspaceUpdate()
}
+
+ private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) {
+ unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
+ }
+
+ /**
+ * Adds a listener for the raw, unfiltered list of smartspace targets. This should be used
+ * carefully, as it doesn't filter out targets which the user may not want shown.
+ */
+ fun addUnfilteredListener(listener: SmartspaceTargetListener) {
+ unfilteredListeners.add(listener)
+ connectSession()
+ }
+
+ fun removeUnfilteredListener(listener: SmartspaceTargetListener) {
+ unfilteredListeners.remove(listener)
+ disconnect()
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index fbca7b1d86d3..f769a2355409 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -44,6 +44,7 @@ import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.wm.shell.animation.FlingAnimationUtils;
import java.util.Optional;
+
import javax.inject.Inject;
import javax.inject.Named;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
index 4965c9dfd00b..3087cdfd0cc0 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/HideComplicationTouchHandler.java
@@ -25,6 +25,7 @@ import android.view.View;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
import com.google.common.util.concurrent.ListenableFuture;
@@ -50,6 +51,7 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
private final Complication.VisibilityController mVisibilityController;
private final int mRestoreTimeout;
+ private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final Handler mHandler;
private final Executor mExecutor;
private final TouchInsetManager mTouchInsetManager;
@@ -65,10 +67,12 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
HideComplicationTouchHandler(Complication.VisibilityController visibilityController,
@Named(COMPLICATIONS_RESTORE_TIMEOUT) int restoreTimeout,
TouchInsetManager touchInsetManager,
+ StatusBarKeyguardViewManager statusBarKeyguardViewManager,
@Main Executor executor,
@Main Handler handler) {
mVisibilityController = visibilityController;
mRestoreTimeout = restoreTimeout;
+ mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mHandler = handler;
mTouchInsetManager = touchInsetManager;
mExecutor = executor;
@@ -80,10 +84,13 @@ public class HideComplicationTouchHandler implements DreamTouchHandler {
Log.d(TAG, "onSessionStart");
}
+ final boolean bouncerShowing = mStatusBarKeyguardViewManager.isBouncerShowing();
+
// If other sessions are interested in this touch, do not fade out elements.
- if (session.getActiveSessionCount() > 1) {
+ if (session.getActiveSessionCount() > 1 || bouncerShowing) {
if (DEBUG) {
- Log.d(TAG, "multiple active touch sessions, not fading");
+ Log.d(TAG, "not fading. Active session count: " + session.getActiveSessionCount()
+ + ". Bouncer showing: " + bouncerShowing);
}
session.pop();
return;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
index b9436f96c74f..9c22dc61e67b 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java
@@ -25,7 +25,7 @@ import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler;
import com.android.systemui.dreams.touch.DreamTouchHandler;
-import com.android.systemui.statusbar.phone.PanelViewController;
+import com.android.systemui.shade.PanelViewController;
import com.android.wm.shell.animation.FlingAnimationUtils;
import javax.inject.Named;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
index 9d6e3c295100..2cee2520ce55 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlags.kt
@@ -29,6 +29,9 @@ interface FeatureFlags : FlagListenable {
fun isEnabled(flag: ResourceBooleanFlag): Boolean
/** Returns a boolean value for the given flag. */
+ fun isEnabled(flag: DeviceConfigBooleanFlag): Boolean
+
+ /** Returns a boolean value for the given flag. */
fun isEnabled(flag: SysPropBooleanFlag): Boolean
/** Returns a string value for the given flag. */
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index c4531b573d22..c5221cd9641b 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -44,6 +44,7 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.commandline.Command;
import com.android.systemui.statusbar.commandline.CommandRegistry;
+import com.android.systemui.util.DeviceConfigProxy;
import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
@@ -81,6 +82,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
private final SecureSettings mSecureSettings;
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
+ private final DeviceConfigProxy mDeviceConfigProxy;
private final Map<Integer, Flag<?>> mAllFlags;
private final Map<Integer, Boolean> mBooleanFlagCache = new TreeMap<>();
private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
@@ -94,6 +96,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
SystemPropertiesHelper systemProperties,
@Main Resources resources,
DumpManager dumpManager,
+ DeviceConfigProxy deviceConfigProxy,
@Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
CommandRegistry commandRegistry,
IStatusBarService barService) {
@@ -101,6 +104,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
mSecureSettings = secureSettings;
mResources = resources;
mSystemProperties = systemProperties;
+ mDeviceConfigProxy = deviceConfigProxy;
mAllFlags = allFlags;
mBarService = barService;
@@ -138,6 +142,18 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
}
@Override
+ public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
+ int id = flag.getId();
+ if (!mBooleanFlagCache.containsKey(id)) {
+ boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
+ flag.getName(), flag.getDefault());
+ mBooleanFlagCache.put(id, readFlagValue(id, deviceConfigValue));
+ }
+
+ return mBooleanFlagCache.get(id);
+ }
+
+ @Override
public boolean isEnabled(@NonNull SysPropBooleanFlag flag) {
int id = flag.getId();
if (!mBooleanFlagCache.containsKey(id)) {
@@ -180,7 +196,7 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
/** Specific override for Boolean flags that checks against the teamfood list.*/
private boolean readFlagValue(int id, boolean defaultValue) {
- Boolean result = readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
+ Boolean result = readBooleanFlagOverride(id);
// Only check for teamfood if the default is false.
if (!defaultValue && result == null && id != Flags.TEAMFOOD.getId()) {
if (mAllFlags.containsKey(id) && mAllFlags.get(id).getTeamfood()) {
@@ -191,6 +207,10 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
return result == null ? defaultValue : result;
}
+ private Boolean readBooleanFlagOverride(int id) {
+ return readFlagValueInternal(id, BooleanFlagSerializer.INSTANCE);
+ }
+
@NonNull
private <T> T readFlagValue(int id, @NonNull T defaultValue, FlagSerializer<T> serializer) {
requireNonNull(defaultValue, "defaultValue");
@@ -293,6 +313,8 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof ResourceBooleanFlag) {
setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
+ } else if (flag instanceof DeviceConfigBooleanFlag) {
+ setFlagValue(flag.getId(), value, BooleanFlagSerializer.INSTANCE);
} else if (flag instanceof SysPropBooleanFlag) {
// Store SysProp flags in SystemProperties where they can read by outside parties.
mSystemProperties.setBoolean(((SysPropBooleanFlag) flag).getName(), value);
@@ -388,16 +410,30 @@ public class FeatureFlagsDebug implements FeatureFlags, Dumpable {
@Nullable
private ParcelableFlag<?> toParcelableFlag(Flag<?> f) {
if (f instanceof BooleanFlag) {
- return new BooleanFlag(f.getId(), isEnabled((BooleanFlag) f), f.getTeamfood());
+ return new BooleanFlag(
+ f.getId(),
+ isEnabled((BooleanFlag) f),
+ f.getTeamfood(),
+ readBooleanFlagOverride(f.getId()) != null);
}
if (f instanceof ResourceBooleanFlag) {
return new BooleanFlag(
- f.getId(), isEnabled((ResourceBooleanFlag) f), f.getTeamfood());
+ f.getId(),
+ isEnabled((ResourceBooleanFlag) f),
+ f.getTeamfood(),
+ readBooleanFlagOverride(f.getId()) != null);
+ }
+ if (f instanceof DeviceConfigBooleanFlag) {
+ return new BooleanFlag(
+ f.getId(), isEnabled((DeviceConfigBooleanFlag) f), f.getTeamfood());
}
if (f instanceof SysPropBooleanFlag) {
// TODO(b/223379190): Teamfood not supported for sysprop flags yet.
return new BooleanFlag(
- f.getId(), isEnabled((SysPropBooleanFlag) f), false);
+ f.getId(),
+ ((SysPropBooleanFlag) f).getDefault(),
+ false,
+ !mSystemProperties.get(((SysPropBooleanFlag) f).getName()).isEmpty());
}
// TODO: add support for other flag types.
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index cacef161a862..1492a2bc0ceb 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -19,6 +19,7 @@ package com.android.systemui.flags;
import static java.util.Objects.requireNonNull;
import android.content.res.Resources;
+import android.provider.DeviceConfig;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -28,6 +29,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.DeviceConfigProxy;
import java.io.PrintWriter;
import java.util.Map;
@@ -44,6 +46,7 @@ import javax.inject.Inject;
public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
private final Resources mResources;
private final SystemPropertiesHelper mSystemProperties;
+ private final DeviceConfigProxy mDeviceConfigProxy;
SparseBooleanArray mBooleanCache = new SparseBooleanArray();
SparseArray<String> mStringCache = new SparseArray<>();
@@ -51,9 +54,11 @@ public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
public FeatureFlagsRelease(
@Main Resources resources,
SystemPropertiesHelper systemProperties,
+ DeviceConfigProxy deviceConfigProxy,
DumpManager dumpManager) {
mResources = resources;
mSystemProperties = systemProperties;
+ mDeviceConfigProxy = deviceConfigProxy;
dumpManager.registerDumpable("SysUIFlags", this);
}
@@ -79,6 +84,18 @@ public class FeatureFlagsRelease implements FeatureFlags, Dumpable {
}
@Override
+ public boolean isEnabled(@NonNull DeviceConfigBooleanFlag flag) {
+ int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
+ if (cacheIndex < 0) {
+ boolean deviceConfigValue = mDeviceConfigProxy.getBoolean(flag.getNamespace(),
+ flag.getName(), flag.getDefault());
+ return isEnabled(flag.getId(), deviceConfigValue);
+ }
+
+ return mBooleanCache.valueAt(cacheIndex);
+ }
+
+ @Override
public boolean isEnabled(SysPropBooleanFlag flag) {
int cacheIndex = mBooleanCache.indexOfKey(flag.getId());
if (cacheIndex < 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
index 2c798307c196..958a2192d159 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java
@@ -16,6 +16,8 @@
package com.android.systemui.flags;
+import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
+
import com.android.internal.annotations.Keep;
import com.android.systemui.R;
@@ -43,9 +45,6 @@ public class Flags {
/***************************************/
// 100 - notification
- public static final BooleanFlag NEW_NOTIFICATION_PIPELINE_RENDERING =
- new BooleanFlag(101, true);
-
public static final BooleanFlag NOTIFICATION_PIPELINE_DEVELOPER_LOGGING =
new BooleanFlag(103, false);
@@ -61,6 +60,12 @@ public class Flags {
public static final ResourceBooleanFlag NOTIFICATION_DRAG_TO_CONTENTS =
new ResourceBooleanFlag(108, R.bool.config_notificationToContents);
+ public static final BooleanFlag REMOVE_UNRANKED_NOTIFICATIONS =
+ new BooleanFlag(109, false, true);
+
+ public static final BooleanFlag FSI_REQUIRES_KEYGUARD =
+ new BooleanFlag(110, false, true);
+
/***************************************/
// 200 - keyguard/lockscreen
@@ -82,6 +87,11 @@ public class Flags {
public static final ResourceBooleanFlag FACE_SCANNING_ANIM =
new ResourceBooleanFlag(205, R.bool.config_enableFaceScanningAnimation);
+ /**
+ * Whether the KeyguardBottomArea(View|Controller) should use the modern architecture or the old
+ * one.
+ */
+ public static final BooleanFlag MODERN_BOTTOM_AREA = new BooleanFlag(206, false);
/***************************************/
// 300 - power menu
@@ -109,7 +119,7 @@ public class Flags {
new BooleanFlag(500, true);
public static final BooleanFlag COMBINED_QS_HEADERS =
- new BooleanFlag(501, false);
+ new BooleanFlag(501, false, true);
public static final ResourceBooleanFlag PEOPLE_TILE =
new ResourceBooleanFlag(502, R.bool.flag_conversations);
@@ -123,7 +133,7 @@ public class Flags {
@Deprecated
public static final BooleanFlag NEW_FOOTER = new BooleanFlag(504, true);
- public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false);
+ public static final BooleanFlag NEW_HEADER = new BooleanFlag(505, false, true);
public static final ResourceBooleanFlag FULL_SCREEN_USER_SWITCHER =
new ResourceBooleanFlag(506, R.bool.config_enableFullscreenUserSwitcher);
@@ -135,6 +145,11 @@ public class Flags {
public static final ResourceBooleanFlag STATUS_BAR_USER_SWITCHER =
new ResourceBooleanFlag(602, R.bool.flag_user_switcher_chip);
+ public static final BooleanFlag STATUS_BAR_LETTERBOX_APPEARANCE =
+ new BooleanFlag(603, false);
+
+ public static final BooleanFlag NEW_STATUS_BAR_PIPELINE = new BooleanFlag(604, false);
+
/***************************************/
// 700 - dialer/calls
public static final BooleanFlag ONGOING_CALL_STATUS_BAR_CHIP =
@@ -153,7 +168,7 @@ public class Flags {
/***************************************/
// 900 - media
- public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, false);
+ public static final BooleanFlag MEDIA_TAP_TO_TRANSFER = new BooleanFlag(900, true);
public static final BooleanFlag MEDIA_SESSION_ACTIONS = new BooleanFlag(901, false);
public static final BooleanFlag MEDIA_NEARBY_DEVICES = new BooleanFlag(903, true);
public static final BooleanFlag MEDIA_MUTE_AWAIT = new BooleanFlag(904, true);
@@ -161,12 +176,26 @@ public class Flags {
// 1000 - dock
public static final BooleanFlag SIMULATE_DOCK_THROUGH_CHARGING =
new BooleanFlag(1000, true);
+ public static final BooleanFlag DOCK_SETUP_ENABLED = new BooleanFlag(1001, true);
+
// 1100 - windowing
@Keep
public static final SysPropBooleanFlag WM_ENABLE_SHELL_TRANSITIONS =
new SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false);
+ /**
+ * b/170163464: animate bubbles expanded view collapse with home gesture
+ */
+ @Keep
+ public static final SysPropBooleanFlag BUBBLES_HOME_GESTURE =
+ new SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true);
+
+ @Keep
+ public static final DeviceConfigBooleanFlag WM_ENABLE_PARTIAL_SCREEN_SHARING =
+ new DeviceConfigBooleanFlag(1102, "record_task_content",
+ NAMESPACE_WINDOW_MANAGER, false, true);
+
// 1200 - predictive back
@Keep
public static final SysPropBooleanFlag WM_ENABLE_PREDICTIVE_BACK = new SysPropBooleanFlag(
@@ -178,6 +207,9 @@ public class Flags {
public static final SysPropBooleanFlag WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
new SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false);
+ public static final BooleanFlag NEW_BACK_AFFORDANCE =
+ new BooleanFlag(1203, false /* default */, true /* teamfood */);
+
// Pay no attention to the reflection behind the curtain.
// ========================== Curtain ==========================
// | |
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
index acb080a2eaaa..ab30db297fce 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialogLite.java
@@ -54,6 +54,7 @@ import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -99,6 +100,7 @@ import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.colorextraction.ColorExtractor.GradientColors;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
@@ -110,6 +112,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.MultiListLayout;
import com.android.systemui.MultiListLayout.MultiListAdapter;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.broadcast.BroadcastDispatcher;
@@ -154,6 +157,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
private static final String TAG = "GlobalActionsDialogLite";
+ private static final String INTERACTION_JANK_TAG = "global_actions";
+
private static final boolean SHOW_SILENT_TOGGLE = true;
/* Valid settings for global actions keys.
@@ -499,7 +504,9 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
mDialog.getWindow().addFlags(FLAG_ALT_FOCUSABLE_IM);
if (view != null) {
- mDialogLaunchAnimator.showFromView(mDialog, view);
+ mDialogLaunchAnimator.showFromView(mDialog, view,
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
} else {
mDialog.show();
}
@@ -1038,7 +1045,8 @@ public class GlobalActionsDialogLite implements DialogInterface.OnDismissListene
@Override
public boolean showBeforeProvisioning() {
- return false;
+ return Build.isDebuggable() && mGlobalSettings.getInt(
+ Settings.Global.BUGREPORT_IN_POWER_MENU, 0) != 0;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
index a724d87e5c08..3eb3c80f4081 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardService.java
@@ -55,6 +55,7 @@ import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.Trace;
+import android.util.ArrayMap;
import android.util.Log;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
@@ -157,7 +158,7 @@ public class KeyguardService extends Service {
Rect localBounds = new Rect(change.getEndAbsBounds());
localBounds.offsetTo(change.getEndRelOffset().x, change.getEndRelOffset().y);
- out.add(new RemoteAnimationTarget(
+ final RemoteAnimationTarget target = new RemoteAnimationTarget(
taskId,
newModeToLegacyMode(change.getMode()),
change.getLeash(),
@@ -168,7 +169,15 @@ public class KeyguardService extends Service {
info.getChanges().size() - i,
new Point(), localBounds, new Rect(change.getEndAbsBounds()),
windowConfiguration, isNotInRecents, null /* startLeash */,
- change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */));
+ change.getStartAbsBounds(), taskInfo, false /* allowEnterPip */);
+ // Use hasAnimatingParent to mark the anything below root task
+ if (taskId != -1 && change.getParent() != null) {
+ final TransitionInfo.Change parentChange = info.getChange(change.getParent());
+ if (parentChange != null && parentChange.getTaskInfo() != null) {
+ target.hasAnimatingParent = true;
+ }
+ }
+ out.add(target);
}
return out.toArray(new RemoteAnimationTarget[out.size()]);
}
@@ -189,8 +198,12 @@ public class KeyguardService extends Service {
}
}
+ // Wrap Keyguard going away animation
private static IRemoteTransition wrap(IRemoteAnimationRunner runner) {
return new IRemoteTransition.Stub() {
+ final ArrayMap<IBinder, IRemoteTransitionFinishedCallback> mFinishCallbacks =
+ new ArrayMap<>();
+
@Override
public void startAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IRemoteTransitionFinishedCallback finishCallback)
@@ -200,16 +213,37 @@ public class KeyguardService extends Service {
final RemoteAnimationTarget[] wallpapers = wrap(info, true /* wallpapers */);
final RemoteAnimationTarget[] nonApps = new RemoteAnimationTarget[0];
- // TODO: Remove this, and update alpha value in the IAnimationRunner.
- for (TransitionInfo.Change change : info.getChanges()) {
- t.setAlpha(change.getLeash(), 1.0f);
+ // Sets the alpha to 0 for the opening root task for fade in animation. And since
+ // the fade in animation can only apply on the first opening app, so set alpha to 1
+ // for anything else.
+ boolean foundOpening = false;
+ for (RemoteAnimationTarget target : apps) {
+ if (target.taskId != -1
+ && target.mode == RemoteAnimationTarget.MODE_OPENING
+ && !target.hasAnimatingParent) {
+ if (foundOpening) {
+ Log.w(TAG, "More than one opening target");
+ t.setAlpha(target.leash, 1.0f);
+ continue;
+ }
+ t.setAlpha(target.leash, 0.0f);
+ foundOpening = true;
+ } else {
+ t.setAlpha(target.leash, 1.0f);
+ }
}
t.apply();
+ synchronized (mFinishCallbacks) {
+ mFinishCallbacks.put(transition, finishCallback);
+ }
runner.onAnimationStart(getTransitionOldType(info.getType(), info.getFlags(), apps),
apps, wallpapers, nonApps,
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() throws RemoteException {
+ synchronized (mFinishCallbacks) {
+ if (mFinishCallbacks.remove(transition) == null) return;
+ }
Slog.d(TAG, "Finish IRemoteAnimationRunner.");
finishCallback.onTransitionFinished(null /* wct */, null /* t */);
}
@@ -220,6 +254,20 @@ public class KeyguardService extends Service {
public void mergeAnimation(IBinder transition, TransitionInfo info,
SurfaceControl.Transaction t, IBinder mergeTarget,
IRemoteTransitionFinishedCallback finishCallback) {
+ try {
+ final IRemoteTransitionFinishedCallback origFinishCB;
+ synchronized (mFinishCallbacks) {
+ origFinishCB = mFinishCallbacks.remove(transition);
+ }
+ if (origFinishCB == null) {
+ // already finished (or not started yet), so do nothing.
+ return;
+ }
+ runner.onAnimationCancelled(false /* isKeyguardOccluded */);
+ origFinishCB.onTransitionFinished(null /* wct */, null /* t */);
+ } catch (RemoteException e) {
+ // nothing, we'll just let it finish on its own I guess.
+ }
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
index 99b57200a397..382323f3aed9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt
@@ -26,6 +26,7 @@ import android.os.Handler
import android.os.RemoteException
import android.util.Log
import android.view.RemoteAnimationTarget
+import android.view.SurfaceControl
import android.view.SyncRtSurfaceTransactionApplier
import android.view.View
import androidx.annotation.VisibleForTesting
@@ -293,6 +294,8 @@ class KeyguardUnlockAnimationController @Inject constructor(
private val handler = Handler()
+ private val tmpFloat = FloatArray(9)
+
init {
with(surfaceBehindAlphaAnimator) {
duration = SURFACE_BEHIND_SWIPE_FADE_DURATION_MS
@@ -723,13 +726,27 @@ class KeyguardUnlockAnimationController @Inject constructor(
if (keyguardStateController.isSnappingKeyguardBackAfterSwipe) amount
else surfaceBehindAlpha
- applyParamsToSurface(
- SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
- surfaceBehindRemoteAnimationTarget!!.leash)
- .withMatrix(surfaceBehindMatrix)
- .withCornerRadius(roundedCornerRadius)
- .withAlpha(animationAlpha)
- .build())
+ // SyncRtSurfaceTransactionApplier cannot apply transaction when the target view is unable
+ // to draw
+ val sc: SurfaceControl? = surfaceBehindRemoteAnimationTarget?.leash
+ if (keyguardViewController.viewRootImpl.view?.visibility != View.VISIBLE &&
+ sc?.isValid == true) {
+ with(SurfaceControl.Transaction()) {
+ setMatrix(sc, surfaceBehindMatrix, tmpFloat)
+ setCornerRadius(sc, roundedCornerRadius)
+ setAlpha(sc, animationAlpha)
+ apply()
+ }
+ } else {
+ applyParamsToSurface(
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder(
+ surfaceBehindRemoteAnimationTarget!!.leash)
+ .withMatrix(surfaceBehindMatrix)
+ .withCornerRadius(roundedCornerRadius)
+ .withAlpha(animationAlpha)
+ .build()
+ )
+ }
}
/**
@@ -744,8 +761,11 @@ class KeyguardUnlockAnimationController @Inject constructor(
handler.removeCallbacksAndMessages(null)
// Make sure we made the surface behind fully visible, just in case. It should already be
- // fully visible. If the launcher is doing its own animation, let it continue without
- // forcing it to 1f.
+ // fully visible. The exit animation is finished, and we should not hold the leash anymore,
+ // so forcing it to 1f.
+ surfaceBehindAlphaAnimator.cancel()
+ surfaceBehindEntryAnimator.cancel()
+ surfaceBehindAlpha = 1f
setSurfaceBehindAppearAmount(1f)
launcherUnlockController?.setUnlockAmount(1f, false /* forceIfAnimating */)
@@ -910,4 +930,4 @@ class KeyguardUnlockAnimationController @Inject constructor(
return context.resources.getIntArray(R.array.config_foldedDeviceStates).isNotEmpty()
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index 59c8aa0f20ad..e913d2b86306 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -40,6 +40,7 @@ import android.app.ActivityTaskManager;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.app.StatusBarManager;
+import android.app.WindowConfiguration;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
@@ -121,6 +122,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.dagger.KeyguardModule;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeDepthController;
@@ -130,7 +132,6 @@ import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -555,15 +556,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
@Override
- public void onUserInfoChanged(int userId) {
- }
-
- @Override
- public void onClockVisibilityChanged() {
- adjustStatusBarLocked();
- }
-
- @Override
public void onDeviceProvisioned() {
sendUserPresentBroadcast();
synchronized (KeyguardViewMediator.this) {
@@ -933,6 +925,8 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
}
final RemoteAnimationTarget primary = apps[0];
+ final boolean isDream = (apps[0].taskInfo.topActivityType
+ == WindowConfiguration.ACTIVITY_TYPE_DREAM);
final SyncRtSurfaceTransactionApplier applier =
new SyncRtSurfaceTransactionApplier(
@@ -954,20 +948,24 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
final float surfaceHeight = primary.screenSpaceBounds.height();
- mUnoccludeMatrix.setTranslate(
- 0f,
- (1f - animatedValue)
- * surfaceHeight
- * UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT);
-
- SyncRtSurfaceTransactionApplier.SurfaceParams params =
+ // Fade for all types of activities.
+ SyncRtSurfaceTransactionApplier.SurfaceParams.Builder
+ paramsBuilder =
new SyncRtSurfaceTransactionApplier.SurfaceParams
.Builder(primary.leash)
- .withMatrix(mUnoccludeMatrix)
- .withCornerRadius(mWindowCornerRadius)
- .withAlpha(animatedValue)
- .build();
- applier.scheduleApply(params);
+ .withAlpha(animatedValue);
+ // Set translate if the occluding activity isn't Dream.
+ if (!isDream) {
+ mUnoccludeMatrix.setTranslate(
+ 0f,
+ (1f - animatedValue)
+ * surfaceHeight
+ * UNOCCLUDE_TRANSLATE_DISTANCE_PERCENT);
+
+ paramsBuilder.withMatrix(mUnoccludeMatrix).withCornerRadius(
+ mWindowCornerRadius);
+ }
+ applier.scheduleApply(paramsBuilder.build());
});
mUnoccludeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -1190,6 +1188,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
mSystemReady = true;
doKeyguardLocked(null);
mUpdateMonitor.registerCallback(mUpdateCallback);
+ adjustStatusBarLocked();
mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
}
// Most services aren't available until the system reaches the ready state, so we
@@ -2497,10 +2496,18 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable,
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("DismissPanel"));
+ // Apply the opening animation on root task if exists
+ RemoteAnimationTarget aniTarget = apps[0];
+ for (RemoteAnimationTarget tmpTarget : apps) {
+ if (tmpTarget.taskId != -1 && !tmpTarget.hasAnimatingParent) {
+ aniTarget = tmpTarget;
+ break;
+ }
+ }
// Pass the surface and metadata to the unlock animation controller.
mKeyguardUnlockAnimationControllerLazy.get()
.notifyStartSurfaceBehindRemoteAnimation(
- apps[0], startTime, mSurfaceBehindRemoteAnimationRequested);
+ aniTarget, startTime, mSurfaceBehindRemoteAnimationRequested);
} else {
mInteractionJankMonitor.begin(
createInteractionJankMonitorConf("RemoteAnimationDisabled"));
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
new file mode 100644
index 000000000000..55c7ac9fb0cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwner.kt
@@ -0,0 +1,114 @@
+package com.android.systemui.lifecycle
+
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+
+/**
+ * [LifecycleOwner] for Window-added Views.
+ *
+ * These are [View] instances that are added to a `Window` using the `WindowManager` API.
+ *
+ * This implementation goes to:
+ * * The <b>CREATED</b> `Lifecycle.State` when the view gets attached to the window but the window
+ * is not yet visible
+ * * The <b>STARTED</b> `Lifecycle.State` when the view is attached to the window and the window is
+ * visible
+ * * The <b>RESUMED</b> `Lifecycle.State` when the view is attached to the window and the window is
+ * visible and the window receives focus
+ *
+ * In table format:
+ * ```
+ * | ----------------------------------------------------------------------------- |
+ * | View attached to window | Window visible | Window has focus | Lifecycle state |
+ * | ----------------------------------------------------------------------------- |
+ * | not attached | Any | INITIALIZED |
+ * | ----------------------------------------------------------------------------- |
+ * | | not visible | Any | CREATED |
+ * | ----------------------------------------------------- |
+ * | attached | | not focused | STARTED |
+ * | | is visible |----------------------------------- |
+ * | | | has focus | RESUMED |
+ * | ----------------------------------------------------------------------------- |
+ * ```
+ * ### Notes
+ * * [dispose] must be invoked when the [LifecycleOwner] is done and won't be reused
+ * * It is always better for [LifecycleOwner] implementations to be more explicit than just
+ * listening to the state of the `Window`. E.g. if the code that added the `View` to the `Window`
+ * already has access to the correct state to know when that `View` should become visible and when
+ * it is ready to receive interaction from the user then it already knows when to move to `STARTED`
+ * and `RESUMED`, respectively. In that case, it's better to implement your own `LifecycleOwner`
+ * instead of relying on the `Window` callbacks.
+ */
+class WindowAddedViewLifecycleOwner
+@JvmOverloads
+constructor(
+ private val view: View,
+ registryFactory: (LifecycleOwner) -> LifecycleRegistry = { LifecycleRegistry(it) },
+) : LifecycleOwner {
+
+ private val windowAttachListener =
+ object : ViewTreeObserver.OnWindowAttachListener {
+ override fun onWindowAttached() {
+ updateCurrentState()
+ }
+
+ override fun onWindowDetached() {
+ updateCurrentState()
+ }
+ }
+ private val windowFocusListener =
+ ViewTreeObserver.OnWindowFocusChangeListener { updateCurrentState() }
+ private val windowVisibilityListener =
+ ViewTreeObserver.OnWindowVisibilityChangeListener { updateCurrentState() }
+
+ private val registry = registryFactory(this)
+
+ init {
+ setCurrentState(Lifecycle.State.INITIALIZED)
+
+ with(view.viewTreeObserver) {
+ addOnWindowAttachListener(windowAttachListener)
+ addOnWindowVisibilityChangeListener(windowVisibilityListener)
+ addOnWindowFocusChangeListener(windowFocusListener)
+ }
+
+ updateCurrentState()
+ }
+
+ override fun getLifecycle(): Lifecycle {
+ return registry
+ }
+
+ /**
+ * Disposes of this [LifecycleOwner], performing proper clean-up.
+ *
+ * <p>Invoke this when the instance is finished and won't be reused.
+ */
+ fun dispose() {
+ with(view.viewTreeObserver) {
+ removeOnWindowAttachListener(windowAttachListener)
+ removeOnWindowVisibilityChangeListener(windowVisibilityListener)
+ removeOnWindowFocusChangeListener(windowFocusListener)
+ }
+ }
+
+ private fun updateCurrentState() {
+ val state =
+ when {
+ !view.isAttachedToWindow -> Lifecycle.State.INITIALIZED
+ view.windowVisibility != View.VISIBLE -> Lifecycle.State.CREATED
+ !view.hasWindowFocus() -> Lifecycle.State.STARTED
+ else -> Lifecycle.State.RESUMED
+ }
+ setCurrentState(state)
+ }
+
+ private fun setCurrentState(state: Lifecycle.State) {
+ if (registry.currentState != state) {
+ registry.currentState = state
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index eff025f771a5..d0da18aaba05 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -27,6 +27,8 @@ import com.android.systemui.log.LogBufferFactory;
import com.android.systemui.log.LogcatEchoTracker;
import com.android.systemui.log.LogcatEchoTrackerDebug;
import com.android.systemui.log.LogcatEchoTrackerProd;
+import com.android.systemui.statusbar.notification.NotifPipelineFlags;
+import com.android.systemui.util.Compile;
import dagger.Module;
import dagger.Provides;
@@ -48,11 +50,17 @@ public class LogModule {
@Provides
@SysUISingleton
@NotificationLog
- public static LogBuffer provideNotificationsLogBuffer(LogBufferFactory factory) {
- return factory.create("NotifLog", 1000 /* maxSize */, false /* systrace */);
+ public static LogBuffer provideNotificationsLogBuffer(
+ LogBufferFactory factory,
+ NotifPipelineFlags notifPipelineFlags) {
+ int maxSize = 1000;
+ if (Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()) {
+ maxSize *= 10;
+ }
+ return factory.create("NotifLog", maxSize, false /* systrace */);
}
- /** Provides a logging buffer for all logs related to the data layer of notifications. */
+ /** Provides a logging buffer for logs related to heads up presentation of notifications. */
@Provides
@SysUISingleton
@NotificationHeadsUpLog
@@ -60,6 +68,14 @@ public class LogModule {
return factory.create("NotifHeadsUpLog", 1000);
}
+ /** Provides a logging buffer for notification interruption calculations. */
+ @Provides
+ @SysUISingleton
+ @NotificationInterruptLog
+ public static LogBuffer provideNotificationInterruptLogBuffer(LogBufferFactory factory) {
+ return factory.create("NotifInterruptLog", 100);
+ }
+
/** Provides a logging buffer for all logs for lockscreen to shade transition events. */
@Provides
@SysUISingleton
@@ -251,4 +267,14 @@ public class LogModule {
return new LogcatEchoTrackerProd();
}
}
+
+ /**
+ * Provides a {@link LogBuffer} for use by the status bar network controller.
+ */
+ @Provides
+ @SysUISingleton
+ @StatusBarNetworkControllerLog
+ public static LogBuffer provideStatusBarNetworkControllerBuffer(LogBufferFactory factory) {
+ return factory.create("StatusBarNetworkControllerLog", 20);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
new file mode 100644
index 000000000000..760fbf3928b6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotificationInterruptLog.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/** A {@link LogBuffer} for notification interruption logging. */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotificationInterruptLog {
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
index 60123ab97fd7..f26b3164f488 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/hidedisplaycutout/HideDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/StatusBarNetworkControllerLog.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,23 +14,22 @@
* limitations under the License.
*/
-package com.android.wm.shell.hidedisplaycutout;
+package com.android.systemui.log.dagger;
-import android.content.res.Configuration;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
-import androidx.annotation.NonNull;
+import com.android.systemui.log.LogBuffer;
-import com.android.wm.shell.common.annotations.ExternalThread;
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
-import java.io.PrintWriter;
+import javax.inject.Qualifier;
/**
- * Interface to engage hide display cutout feature.
+ * A {@link LogBuffer} for {@link com.android.systemui.statusbar.connectivity.NetworkController}
*/
-@ExternalThread
-public interface HideDisplayCutout {
- /**
- * Notifies {@link Configuration} changed.
- */
- void onConfigurationChanged(Configuration newConfig);
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface StatusBarNetworkControllerLog {
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
index f1d5e94bfbf3..d0826553ad2c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/ColorSchemeTransition.kt
@@ -17,20 +17,17 @@
package com.android.systemui.media
import android.animation.ArgbEvaluator
-import android.animation.ValueAnimator.AnimatorUpdateListener
import android.animation.ValueAnimator
+import android.animation.ValueAnimator.AnimatorUpdateListener
import android.content.Context
import android.content.res.ColorStateList
-import android.graphics.Color
-import android.graphics.drawable.GradientDrawable
-import android.graphics.drawable.RippleDrawable
import android.content.res.Configuration
import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.graphics.drawable.RippleDrawable
import com.android.internal.R
import com.android.internal.annotations.VisibleForTesting
import com.android.settingslib.Utils
import com.android.systemui.monet.ColorScheme
-import com.android.systemui.util.getColorWithAlpha
/**
* A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -106,7 +103,6 @@ class ColorSchemeTransition internal constructor(
constructor(context: Context, mediaViewHolder: MediaViewHolder) :
this(context, mediaViewHolder, ::AnimatingColorTransition)
- private var isGradientEnabled = true
val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
val surfaceColor = animatingColorTransitionFactory(
bgColor,
@@ -187,16 +183,6 @@ class ColorSchemeTransition internal constructor(
mediaViewHolder.seekBar.progressBackgroundTintList = ColorStateList.valueOf(textTertiary)
}
- val bgGradientStart = animatingColorTransitionFactory(
- bgColor,
- albumGradientPicker(::backgroundStartFromScheme, 0.25f)
- ) { _ -> updateAlbumGradient() }
-
- val bgGradientEnd = animatingColorTransitionFactory(
- bgColor,
- albumGradientPicker(::backgroundEndFromScheme, 0.9f)
- ) { _ -> updateAlbumGradient() }
-
val colorTransitions = arrayOf(
surfaceColor,
colorSeamless,
@@ -206,37 +192,13 @@ class ColorSchemeTransition internal constructor(
textPrimaryInverse,
textSecondary,
textTertiary,
- bgGradientStart,
- bgGradientEnd
)
- private fun updateAlbumGradient() {
- val gradient = mediaViewHolder.albumView.foreground?.mutate()
- if (gradient is GradientDrawable) {
- gradient.colors = intArrayOf(
- bgGradientStart?.currentColor ?: 0,
- bgGradientEnd?.currentColor ?: 0)
- }
- }
-
- private fun albumGradientPicker(
- inner: (ColorScheme) -> Int,
- targetAlpha: Float
- ): (ColorScheme) -> Int {
- return { scheme ->
- if (isGradientEnabled)
- getColorWithAlpha(inner(scheme), targetAlpha)
- else
- Color.TRANSPARENT
- }
- }
-
private fun loadDefaultColor(id: Int): Int {
return Utils.getColorAttr(context, id).defaultColor
}
- fun updateColorScheme(colorScheme: ColorScheme?, enableGradient: Boolean) {
- isGradientEnabled = enableGradient
+ fun updateColorScheme(colorScheme: ColorScheme?) {
colorTransitions.forEach { it.updateColorScheme(colorScheme) }
colorScheme?.let { mediaViewHolder.gutsViewHolder.colorScheme = colorScheme }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index 159784575b69..012d76651b23 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -38,7 +38,9 @@ import android.graphics.Rect;
import android.graphics.drawable.Animatable;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.Icon;
+import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.media.session.MediaController;
import android.media.session.MediaSession;
@@ -70,6 +72,7 @@ import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.GhostedViewLaunchAnimatorController;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.bluetooth.BroadcastDialogController;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -81,6 +84,7 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.ColorUtilKt;
import com.android.systemui.util.animation.TransitionLayout;
import com.android.systemui.util.time.SystemClock;
@@ -188,6 +192,11 @@ public class MediaControlPanel {
private final SeekBarViewModel.EnabledChangeListener mEnabledChangeListener =
this::setIsSeekBarEnabled;
+ private final BroadcastDialogController mBroadcastDialogController;
+ private boolean mIsCurrentBroadcastedApp = false;
+ private boolean mShowBroadcastDialogButton = false;
+ private String mSwitchBroadcastApp;
+
/**
* Initialize a new control panel
*
@@ -213,7 +222,8 @@ public class MediaControlPanel {
MediaUiEventLogger logger,
KeyguardStateController keyguardStateController,
ActivityIntentHelper activityIntentHelper,
- NotificationLockscreenUserManager lockscreenUserManager) {
+ NotificationLockscreenUserManager lockscreenUserManager,
+ BroadcastDialogController broadcastDialogController) {
mContext = context;
mBackgroundExecutor = backgroundExecutor;
mMainExecutor = mainExecutor;
@@ -230,6 +240,7 @@ public class MediaControlPanel {
mKeyguardStateController = keyguardStateController;
mActivityIntentHelper = activityIntentHelper;
mLockscreenUserManager = lockscreenUserManager;
+ mBroadcastDialogController = broadcastDialogController;
mSeekBarViewModel.setLogSeek(() -> {
if (mPackageName != null && mInstanceId != null) {
@@ -449,7 +460,10 @@ public class MediaControlPanel {
final MediaController controller = getController();
mBackgroundExecutor.execute(() -> mSeekBarViewModel.updateController(controller));
- bindOutputSwitcherChip(data);
+ // Show the broadcast dialog button only when the le audio is enabled.
+ mShowBroadcastDialogButton =
+ data.getDevice() != null && data.getDevice().getShowBroadcastButton();
+ bindOutputSwitcherAndBroadcastButton(mShowBroadcastDialogButton, data);
bindGutsMenuForPlayer(data);
bindPlayerContentDescription(data);
bindScrubbingTime(data);
@@ -467,21 +481,40 @@ public class MediaControlPanel {
Trace.endSection();
}
- private void bindOutputSwitcherChip(MediaData data) {
- // Output switcher chip
+ private void bindOutputSwitcherAndBroadcastButton(boolean showBroadcastButton, MediaData data) {
ViewGroup seamlessView = mMediaViewHolder.getSeamless();
seamlessView.setVisibility(View.VISIBLE);
ImageView iconView = mMediaViewHolder.getSeamlessIcon();
TextView deviceName = mMediaViewHolder.getSeamlessText();
final MediaDeviceData device = data.getDevice();
- // Disable clicking on output switcher for invalid devices and resumption controls
- final boolean seamlessDisabled = (device != null && !device.getEnabled())
- || data.getResumption();
- final float seamlessAlpha = seamlessDisabled ? DISABLED_ALPHA : 1.0f;
- mMediaViewHolder.getSeamlessButton().setAlpha(seamlessAlpha);
- seamlessView.setEnabled(!seamlessDisabled);
- CharSequence deviceString = mContext.getString(R.string.media_seamless_other_device);
+ final boolean enabled;
+ final boolean seamlessDisabled;
+ final int iconResource;
+ CharSequence deviceString;
+ if (showBroadcastButton) {
+ // TODO(b/233698402): Use the package name instead of app label to avoid the
+ // unexpected result.
+ mIsCurrentBroadcastedApp = device != null
+ && TextUtils.equals(device.getName(),
+ MediaDataUtils.getAppLabel(mContext, mPackageName, mContext.getString(
+ R.string.bt_le_audio_broadcast_dialog_unknown_name)));
+ seamlessDisabled = !mIsCurrentBroadcastedApp;
+ // Always be enabled if the broadcast button is shown
+ enabled = true;
+ deviceString = mContext.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name);
+ iconResource = R.drawable.settings_input_antenna;
+ } else {
+ // Disable clicking on output switcher for invalid devices and resumption controls
+ seamlessDisabled = (device != null && !device.getEnabled()) || data.getResumption();
+ enabled = !seamlessDisabled;
+ deviceString = mContext.getString(R.string.media_seamless_other_device);
+ iconResource = R.drawable.ic_media_home_devices;
+ }
+
+ mMediaViewHolder.getSeamlessButton().setAlpha(seamlessDisabled ? DISABLED_ALPHA : 1.0f);
+ seamlessView.setEnabled(enabled);
+
if (device != null) {
Drawable icon = device.getIcon();
if (icon instanceof AdaptiveIcon) {
@@ -494,7 +527,7 @@ public class MediaControlPanel {
deviceString = device.getName();
} else {
// Set to default icon
- iconView.setImageResource(R.drawable.ic_media_home_devices);
+ iconView.setImageResource(iconResource);
}
deviceName.setText(deviceString);
seamlessView.setContentDescription(deviceString);
@@ -503,21 +536,39 @@ public class MediaControlPanel {
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return;
}
- mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
- if (device.getIntent() != null) {
- if (device.getIntent().isActivity()) {
- mActivityStarter.startActivity(
- device.getIntent().getIntent(), true);
+
+ if (showBroadcastButton) {
+ // If the current media app is not broadcasted and users press the outputer
+ // button, we should pop up the broadcast dialog to check do they want to
+ // switch broadcast to the other media app, otherwise we still pop up the
+ // media output dialog.
+ if (!mIsCurrentBroadcastedApp) {
+ mLogger.logOpenBroadcastDialog(mUid, mPackageName, mInstanceId);
+ mSwitchBroadcastApp = device.getName().toString();
+ mBroadcastDialogController.createBroadcastDialog(mSwitchBroadcastApp,
+ mPackageName, true, mMediaViewHolder.getSeamlessButton());
} else {
- try {
- device.getIntent().send();
- } catch (PendingIntent.CanceledException e) {
- Log.e(TAG, "Device pending intent was canceled");
- }
+ mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
+ mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaViewHolder.getSeamlessButton());
}
} else {
- mMediaOutputDialogFactory.create(mPackageName, true,
- mMediaViewHolder.getSeamlessButton());
+ mLogger.logOpenOutputSwitcher(mUid, mPackageName, mInstanceId);
+ if (device.getIntent() != null) {
+ if (device.getIntent().isActivity()) {
+ mActivityStarter.startActivity(
+ device.getIntent().getIntent(), true);
+ } else {
+ try {
+ device.getIntent().send();
+ } catch (PendingIntent.CanceledException e) {
+ Log.e(TAG, "Device pending intent was canceled");
+ }
+ }
+ } else {
+ mMediaOutputDialogFactory.create(mPackageName, true,
+ mMediaViewHolder.getSeamlessButton());
+ }
}
});
}
@@ -648,7 +699,18 @@ public class MediaControlPanel {
}
if (wallpaperColors != null) {
mutableColorScheme = new ColorScheme(wallpaperColors, true, Style.CONTENT);
- artwork = getScaledBackground(artworkIcon, width, height);
+ Drawable albumArt = getScaledBackground(artworkIcon, width, height);
+ GradientDrawable gradient = (GradientDrawable) mContext
+ .getDrawable(R.drawable.qs_media_scrim);
+ gradient.setColors(new int[] {
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundStartFromScheme(mutableColorScheme),
+ 0.25f),
+ ColorUtilKt.getColorWithAlpha(
+ MediaColorSchemesKt.backgroundEndFromScheme(mutableColorScheme),
+ 0.9f),
+ });
+ artwork = new LayerDrawable(new Drawable[] { albumArt, gradient });
isArtworkBound = true;
} else {
// If there's no artwork, use colors from the app icon
@@ -700,7 +762,7 @@ public class MediaControlPanel {
}
// Transition Colors to current color scheme
- mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound);
+ mColorSchemeTransition.updateColorScheme(colorScheme);
// App icon - use notification icon
ImageView appIconView = mMediaViewHolder.getAppIcon();
@@ -956,16 +1018,13 @@ public class MediaControlPanel {
private void bindScrubbingTime(MediaData data) {
ConstraintSet expandedSet = mMediaViewController.getExpandedLayout();
- ConstraintSet collapsedSet = mMediaViewController.getCollapsedLayout();
int elapsedTimeId = mMediaViewHolder.getScrubbingElapsedTimeView().getId();
int totalTimeId = mMediaViewHolder.getScrubbingTotalTimeView().getId();
boolean visible = scrubbingTimeViewsEnabled(data.getSemanticActions()) && mIsScrubbing;
setVisibleAndAlpha(expandedSet, elapsedTimeId, visible);
setVisibleAndAlpha(expandedSet, totalTimeId, visible);
- // Never show in collapsed
- setVisibleAndAlpha(collapsedSet, elapsedTimeId, false);
- setVisibleAndAlpha(collapsedSet, totalTimeId, false);
+ // Collapsed view is always GONE as set in XML, so doesn't need to be updated dynamically
}
private boolean scrubbingTimeViewsEnabled(@Nullable MediaButton semanticActions) {
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
index aeeef6b24a1c..5b2cda038bbd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaData.kt
@@ -215,7 +215,10 @@ data class MediaDeviceData
val intent: PendingIntent? = null,
/** Unique id for this device */
- val id: String? = null
+ val id: String? = null,
+
+ /** Whether or not to show the broadcast button */
+ val showBroadcastButton: Boolean
) {
/**
* Check whether [MediaDeviceData] objects are equal in all fields except the icon. The icon
@@ -230,6 +233,7 @@ data class MediaDeviceData
return enabled == other.enabled &&
name == other.name &&
intent == other.intent &&
- id == other.id
+ id == other.id &&
+ showBroadcastButton == other.showBroadcastButton
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
index 3cfd95bc3e10..88a1b17e37fe 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataManager.kt
@@ -686,7 +686,8 @@ class MediaDataManager(
val enabled = deviceIntent != null && deviceIntent.isActivity
val deviceDrawable = Icon.createWithResource(sbn.packageName, deviceIcon)
.loadDrawable(sbn.getPackageContext(context))
- device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent)
+ device = MediaDeviceData(enabled, deviceDrawable, deviceName, deviceIntent,
+ showBroadcastButton = false)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java b/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java
new file mode 100644
index 000000000000..b8185b9de7e8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDataUtils.java
@@ -0,0 +1,43 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media;
+
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+
+public class MediaDataUtils {
+
+ public static String getAppLabel(Context context, String packageName, String unknownName) {
+ if (TextUtils.isEmpty(packageName)) {
+ return null;
+ }
+ final PackageManager packageManager = context.getPackageManager();
+ ApplicationInfo applicationInfo;
+ try {
+ applicationInfo = packageManager.getApplicationInfo(packageName, 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ applicationInfo = null;
+ }
+ final String applicationName =
+ (String) (applicationInfo != null
+ ? packageManager.getApplicationLabel(applicationInfo)
+ : unknownName);
+ return applicationName;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
index baed1c4286e9..83050503a18f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaDeviceManager.kt
@@ -16,15 +16,23 @@
package com.android.systemui.media
+import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.content.Context
import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.session.MediaController
+import android.text.TextUtils
+import android.util.Log
import androidx.annotation.AnyThread
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothManager
import com.android.settingslib.media.LocalMediaManager
import com.android.settingslib.media.MediaDevice
import com.android.systemui.Dumpable
+import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
@@ -36,16 +44,20 @@ import java.util.concurrent.Executor
import javax.inject.Inject
private const val PLAYBACK_TYPE_UNKNOWN = 0
+private const val TAG = "MediaDeviceManager"
+private const val DEBUG = true
/**
* Provides information about the route (ie. device) where playback is occurring.
*/
class MediaDeviceManager @Inject constructor(
+ private val context: Context,
private val controllerFactory: MediaControllerFactory,
private val localMediaManagerFactory: LocalMediaManagerFactory,
private val mr2manager: MediaRouter2Manager,
private val muteAwaitConnectionManagerFactory: MediaMuteAwaitConnectionManagerFactory,
private val configurationController: ConfigurationController,
+ private val localBluetoothManager: LocalBluetoothManager?,
@Main private val fgExecutor: Executor,
@Background private val bgExecutor: Executor,
dumpManager: DumpManager
@@ -147,7 +159,8 @@ class MediaDeviceManager @Inject constructor(
val controller: MediaController?,
val localMediaManager: LocalMediaManager,
val muteAwaitConnectionManager: MediaMuteAwaitConnectionManager?
- ) : LocalMediaManager.DeviceCallback, MediaController.Callback() {
+ ) : LocalMediaManager.DeviceCallback, MediaController.Callback(),
+ BluetoothLeBroadcast.Callback {
val token
get() = controller?.sessionToken
@@ -166,7 +179,7 @@ class MediaDeviceManager @Inject constructor(
// A device that is not yet connected but is expected to connect imminently. Because it's
// expected to connect imminently, it should be displayed as the current device.
private var aboutToConnectDeviceOverride: AboutToConnectDevice? = null
-
+ private var broadcastDescription: String? = null
private val configListener = object : ConfigurationController.ConfigurationListener {
override fun onLocaleListChanged() {
updateCurrent()
@@ -238,7 +251,11 @@ class MediaDeviceManager @Inject constructor(
) {
aboutToConnectDeviceOverride = AboutToConnectDevice(
fullMediaDevice = localMediaManager.getMediaDeviceById(deviceAddress),
- backupMediaDeviceData = MediaDeviceData(enabled = true, deviceIcon, deviceName)
+ backupMediaDeviceData = MediaDeviceData(
+ /* enabled */ enabled = true,
+ /* icon */ deviceIcon,
+ /* name */ deviceName,
+ /* showBroadcastButton */ showBroadcastButton = false)
)
updateCurrent()
}
@@ -248,23 +265,127 @@ class MediaDeviceManager @Inject constructor(
updateCurrent()
}
+
+ override fun onBroadcastStarted(reason: Int, broadcastId: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStarted(), reason = $reason , broadcastId = $broadcastId")
+ }
+ updateCurrent()
+ }
+
+ override fun onBroadcastStartFailed(reason: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStartFailed(), reason = $reason")
+ }
+ }
+
+ override fun onBroadcastMetadataChanged(broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = $broadcastId , " +
+ "metadata = $metadata")
+ }
+ updateCurrent()
+ }
+
+ override fun onBroadcastStopped(reason: Int, broadcastId: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStopped(), reason = $reason , broadcastId = $broadcastId")
+
+ }
+ updateCurrent()
+ }
+
+ override fun onBroadcastStopFailed(reason: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastStopFailed(), reason = $reason")
+ }
+ }
+
+ override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastUpdated(), reason = $reason , broadcastId = $broadcastId")
+ }
+ updateCurrent()
+ }
+
+ override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {
+ if (DEBUG) {
+ Log.d(TAG, "onBroadcastUpdateFailed(), reason = $reason , " +
+ "broadcastId = $broadcastId")
+ }
+ }
+
+ override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+
+ override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+
@WorkerThread
private fun updateCurrent() {
- val aboutToConnect = aboutToConnectDeviceOverride
- if (aboutToConnect != null &&
- aboutToConnect.fullMediaDevice == null &&
- aboutToConnect.backupMediaDeviceData != null) {
+ if (isLeAudioBroadcastEnabled()) {
+ current = MediaDeviceData(
+ /* enabled */ true,
+ /* icon */ context.getDrawable(R.drawable.settings_input_antenna),
+ /* name */ broadcastDescription,
+ /* intent */ null,
+ /* showBroadcastButton */ showBroadcastButton = true)
+ } else {
+ val aboutToConnect = aboutToConnectDeviceOverride
+ if (aboutToConnect != null &&
+ aboutToConnect.fullMediaDevice == null &&
+ aboutToConnect.backupMediaDeviceData != null) {
// Only use [backupMediaDeviceData] when we don't have [fullMediaDevice].
current = aboutToConnect.backupMediaDeviceData
return
+ }
+ val device = aboutToConnect?.fullMediaDevice
+ ?: localMediaManager.currentConnectedDevice
+ val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
+
+ // If we have a controller but get a null route, then don't trust the device
+ val enabled = device != null && (controller == null || route != null)
+ val name = route?.name?.toString() ?: device?.name
+ current = MediaDeviceData(enabled, device?.iconWithoutBackground, name,
+ id = device?.id, showBroadcastButton = false)
}
- val device = aboutToConnect?.fullMediaDevice ?: localMediaManager.currentConnectedDevice
- val route = controller?.let { mr2manager.getRoutingSessionForMediaController(it) }
+ }
- // If we have a controller but get a null route, then don't trust the device
- val enabled = device != null && (controller == null || route != null)
- val name = route?.name?.toString() ?: device?.name
- current = MediaDeviceData(enabled, device?.iconWithoutBackground, name, id = device?.id)
+ private fun isLeAudioBroadcastEnabled(): Boolean {
+ if (localBluetoothManager != null) {
+ val profileManager = localBluetoothManager.profileManager
+ if (profileManager != null) {
+ val bluetoothLeBroadcast = profileManager.leAudioBroadcastProfile
+ if (bluetoothLeBroadcast != null && bluetoothLeBroadcast.isEnabled(null)) {
+ getBroadcastingInfo(bluetoothLeBroadcast)
+ return true
+ } else if (DEBUG) {
+ Log.d(TAG, "Can not get LocalBluetoothLeBroadcast")
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "Can not get LocalBluetoothProfileManager")
+ }
+ } else if (DEBUG) {
+ Log.d(TAG, "Can not get LocalBluetoothManager")
+ }
+ return false
+ }
+
+ private fun getBroadcastingInfo(bluetoothLeBroadcast: LocalBluetoothLeBroadcast) {
+ var currentBroadcastedApp = bluetoothLeBroadcast.appSourceName
+ // TODO(b/233698402): Use the package name instead of app label to avoid the
+ // unexpected result.
+ // Check the current media app's name is the same with current broadcast app's name
+ // or not.
+ var mediaApp = MediaDataUtils.getAppLabel(
+ context, localMediaManager.packageName,
+ context.getString(R.string.bt_le_audio_broadcast_dialog_unknown_name))
+ var isCurrentBroadcastedApp = TextUtils.equals(mediaApp, currentBroadcastedApp)
+ if (isCurrentBroadcastedApp) {
+ broadcastDescription = context.getString(
+ R.string.broadcasting_description_is_broadcasting)
+ } else {
+ broadcastDescription = currentBroadcastedApp
+ }
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
index ed5c1933af25..458ed4059b3b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaHierarchyManager.kt
@@ -23,6 +23,7 @@ import android.annotation.IntDef
import android.content.Context
import android.content.res.Configuration
import android.graphics.Rect
+import android.util.Log
import android.util.MathUtils
import android.view.View
import android.view.ViewGroup
@@ -48,6 +49,8 @@ import com.android.systemui.util.animation.UniqueObjectHostView
import com.android.systemui.util.traceSection
import javax.inject.Inject
+private val TAG: String = MediaHierarchyManager::class.java.simpleName
+
/**
* Similarly to isShown but also excludes views that have 0 alpha
*/
@@ -141,8 +144,7 @@ class MediaHierarchyManager @Inject constructor(
animatedFraction)
// When crossfading, let's keep the bounds at the right location during fading
boundsProgress = if (animationCrossFadeProgress < 0.5f) 0.0f else 1.0f
- currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress,
- instantlyShowAtEnd = false)
+ currentAlpha = calculateAlphaFromCrossFade(animationCrossFadeProgress)
} else {
// If we're not crossfading, let's interpolate from the start alpha to 1.0f
currentAlpha = MathUtils.lerp(animationStartAlpha, 1.0f, animatedFraction)
@@ -273,7 +275,7 @@ class MediaHierarchyManager @Inject constructor(
if (value >= 0) {
updateTargetState()
// Setting the alpha directly, as the below call will use it to update the alpha
- carouselAlpha = calculateAlphaFromCrossFade(field, instantlyShowAtEnd = true)
+ carouselAlpha = calculateAlphaFromCrossFade(field)
applyTargetStateIfNotAnimating()
}
}
@@ -411,18 +413,10 @@ class MediaHierarchyManager @Inject constructor(
* @param crossFadeProgress The current cross fade progress. 0.5f means it's just switching
* between the start and the end location and the content is fully faded, while 0.75f means
* that we're halfway faded in again in the target state.
- *
- * @param instantlyShowAtEnd should the view be instantly shown at the end. This is needed
- * to avoid fadinging in when the target was hidden anyway.
*/
- private fun calculateAlphaFromCrossFade(
- crossFadeProgress: Float,
- instantlyShowAtEnd: Boolean
- ): Float {
+ private fun calculateAlphaFromCrossFade(crossFadeProgress: Float): Float {
if (crossFadeProgress <= 0.5f) {
return 1.0f - crossFadeProgress / 0.5f
- } else if (instantlyShowAtEnd) {
- return 1.0f
} else {
return (crossFadeProgress - 0.5f) / 0.5f
}
@@ -964,6 +958,14 @@ class MediaHierarchyManager @Inject constructor(
top,
left + currentBounds.width(),
top + currentBounds.height())
+
+ if (mediaFrame.childCount > 0) {
+ val child = mediaFrame.getChildAt(0)
+ if (mediaFrame.height < child.height) {
+ Log.wtf(TAG, "mediaFrame height is too small for child: " +
+ "${mediaFrame.height} vs ${child.height}")
+ }
+ }
}
if (isCrossFadeAnimatorRunning) {
// When cross-fading with an animation, we only notify the media carousel of the
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
new file mode 100644
index 000000000000..6802da347c93
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.media
+
+import android.app.ActivityOptions
+import android.content.Intent
+import android.media.projection.IMediaProjection
+import android.media.projection.MediaProjectionManager.EXTRA_MEDIA_PROJECTION
+import android.os.Binder
+import android.os.Bundle
+import android.os.IBinder
+import android.view.View
+import com.android.internal.app.ChooserActivity
+import com.android.internal.app.chooser.NotSelectableTargetInfo
+import com.android.internal.app.chooser.TargetInfo
+import com.android.systemui.util.AsyncActivityLauncher
+import com.android.systemui.R;
+import javax.inject.Inject
+
+class MediaProjectionAppSelectorActivity @Inject constructor(
+ private val activityLauncher: AsyncActivityLauncher
+) : ChooserActivity() {
+
+ public override fun onCreate(bundle: Bundle?) {
+ val queryIntent = Intent(Intent.ACTION_MAIN)
+ .addCategory(Intent.CATEGORY_LAUNCHER)
+ intent.putExtra(Intent.EXTRA_INTENT, queryIntent)
+
+ // TODO(b/235465652) Use resource lexeme
+ intent.putExtra(Intent.EXTRA_TITLE, "Record or cast an app")
+
+ super.onCreate(bundle)
+
+ // TODO(b/235465652) we should update VisD of the title and add an icon
+ findViewById<View>(R.id.title)?.visibility = View.VISIBLE
+ }
+
+ override fun appliedThemeResId(): Int =
+ R.style.Theme_SystemUI_MediaProjectionAppSelector
+
+ override fun startSelected(which: Int, always: Boolean, filtered: Boolean) {
+ val currentListAdapter = mChooserMultiProfilePagerAdapter.activeListAdapter
+ val targetInfo = currentListAdapter.targetInfoForPosition(which, filtered) ?: return
+ if (targetInfo is NotSelectableTargetInfo) return
+
+ val intent = createIntent(targetInfo)
+
+ val launchToken: IBinder = Binder("media_projection_launch_token")
+ val activityOptions = ActivityOptions.makeBasic()
+ activityOptions.launchCookie = launchToken
+
+ val userHandle = mMultiProfilePagerAdapter.activeListAdapter.userHandle
+
+ // Launch activity asynchronously and wait for the result, launching of an activity
+ // is typically very fast, so we don't show any loaders.
+ // We wait for the activity to be launched to make sure that the window of the activity
+ // is created and ready to be captured.
+ val activityStarted = activityLauncher
+ .startActivityAsUser(intent, userHandle, activityOptions.toBundle()) {
+ onTargetActivityLaunched(launchToken)
+ }
+
+ // Rely on the ActivityManager to pop up a dialog regarding app suspension
+ // and return false if suspended
+ if (!targetInfo.isSuspended && activityStarted) {
+ // TODO(b/222078415) track activity launch
+ }
+ }
+
+ private fun createIntent(target: TargetInfo): Intent {
+ val intent = Intent(target.resolvedIntent)
+
+ // Launch the app in a new task, so it won't be in the host's app task
+ intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK
+
+ // Remove activity forward result flag as this activity will
+ // return the media projection session
+ intent.flags = intent.flags and Intent.FLAG_ACTIVITY_FORWARD_RESULT.inv()
+
+ return intent
+ }
+
+ override fun onDestroy() {
+ activityLauncher.destroy()
+ super.onDestroy()
+ }
+
+ override fun onActivityStarted(cti: TargetInfo) {
+ // do nothing
+ }
+
+ private fun onTargetActivityLaunched(launchToken: IBinder) {
+ val mediaProjectionBinder = intent.getIBinderExtra(EXTRA_MEDIA_PROJECTION)
+ val projection = IMediaProjection.Stub.asInterface(mediaProjectionBinder)
+
+ projection.launchCookie = launchToken
+
+ val intent = Intent()
+ intent.putExtra(EXTRA_MEDIA_PROJECTION, projection.asBinder())
+ setResult(RESULT_OK, intent)
+ setForceSendResultForMediaProjection()
+ finish()
+ }
+
+ override fun shouldGetOnlyDefaultActivities() = false
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 38604091c409..397bffcaa64c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -40,7 +40,10 @@ import android.text.style.StyleSpan;
import android.util.Log;
import android.view.Window;
+import com.android.systemui.Dependency;
import com.android.systemui.R;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.statusbar.phone.SystemUIDialog;
import com.android.systemui.util.Utils;
@@ -53,6 +56,7 @@ public class MediaProjectionPermissionActivity extends Activity
private String mPackageName;
private int mUid;
private IMediaProjectionManager mService;
+ private FeatureFlags mFeatureFlags;
private AlertDialog mDialog;
@@ -60,6 +64,7 @@ public class MediaProjectionPermissionActivity extends Activity
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
+ mFeatureFlags = Dependency.get(FeatureFlags.class);
mPackageName = getCallingPackage();
IBinder b = ServiceManager.getService(MEDIA_PROJECTION_SERVICE);
mService = IMediaProjectionManager.Stub.asInterface(b);
@@ -141,14 +146,22 @@ public class MediaProjectionPermissionActivity extends Activity
dialogTitle = getString(R.string.media_projection_dialog_title, appName);
}
- mDialog = new AlertDialog.Builder(this, R.style.Theme_SystemUI_Dialog)
+ AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this,
+ R.style.Theme_SystemUI_Dialog)
.setTitle(dialogTitle)
.setIcon(R.drawable.ic_media_projection_permission)
.setMessage(dialogText)
.setPositiveButton(R.string.media_projection_action_text, this)
.setNeutralButton(android.R.string.cancel, this)
- .setOnCancelListener(this)
- .create();
+ .setOnCancelListener(this);
+
+ if (isPartialScreenSharingEnabled()) {
+ // This is a temporary entry point before we have a new permission dialog
+ // TODO(b/233183090): this activity should be redesigned to have a dropdown selector
+ dialogBuilder.setNegativeButton("App", this);
+ }
+
+ mDialog = dialogBuilder.create();
SystemUIDialog.registerDismissListener(mDialog);
SystemUIDialog.applyFlags(mDialog);
@@ -177,6 +190,15 @@ public class MediaProjectionPermissionActivity extends Activity
if (which == AlertDialog.BUTTON_POSITIVE) {
setResult(RESULT_OK, getMediaProjectionIntent(mUid, mPackageName));
}
+
+ if (isPartialScreenSharingEnabled() && which == AlertDialog.BUTTON_NEGATIVE) {
+ IMediaProjection projection = createProjection(mUid, mPackageName);
+ final Intent intent = new Intent(this, MediaProjectionAppSelectorActivity.class);
+ intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION,
+ projection.asBinder());
+ intent.setFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+ startActivity(intent);
+ }
} catch (RemoteException e) {
Log.e(TAG, "Error granting projection permission", e);
setResult(RESULT_CANCELED);
@@ -188,10 +210,14 @@ public class MediaProjectionPermissionActivity extends Activity
}
}
+ private IMediaProjection createProjection(int uid, String packageName) throws RemoteException {
+ return mService.createProjection(uid, packageName,
+ MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+ }
+
private Intent getMediaProjectionIntent(int uid, String packageName)
throws RemoteException {
- IMediaProjection projection = mService.createProjection(uid, packageName,
- MediaProjectionManager.TYPE_SCREEN_CAPTURE, false /* permanentGrant */);
+ IMediaProjection projection = createProjection(uid, packageName);
Intent intent = new Intent();
intent.putExtra(MediaProjectionManager.EXTRA_MEDIA_PROJECTION, projection.asBinder());
return intent;
@@ -201,4 +227,8 @@ public class MediaProjectionPermissionActivity extends Activity
public void onCancel(DialogInterface dialog) {
finish();
}
+
+ private boolean isPartialScreenSharingEnabled() {
+ return mFeatureFlags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
index 52f5cc568ba4..0baf01e7476f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaUiEventLogger.kt
@@ -176,6 +176,11 @@ class MediaUiEventLogger @Inject constructor(private val logger: UiEventLogger)
logger.logWithInstanceId(MediaUiEvent.MEDIA_RECOMMENDATION_CARD_TAP, 0, packageName,
instanceId)
}
+
+ fun logOpenBroadcastDialog(uid: Int, packageName: String, instanceId: InstanceId) {
+ logger.logWithInstanceId(MediaUiEvent.MEDIA_OPEN_BROADCAST_DIALOG, uid, packageName,
+ instanceId)
+ }
}
enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
@@ -273,7 +278,10 @@ enum class MediaUiEvent(val metricId: Int) : UiEventLogger.UiEventEnum {
MEDIA_RECOMMENDATION_ITEM_TAP(1044),
@UiEvent(doc = "User tapped on a media recommendation card")
- MEDIA_RECOMMENDATION_CARD_TAP(1045);
+ MEDIA_RECOMMENDATION_CARD_TAP(1045),
+
+ @UiEvent(doc = "User opened the broadcast dialog from a media control")
+ MEDIA_OPEN_BROADCAST_DIALOG(1079);
override fun getId() = metricId
} \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
new file mode 100644
index 000000000000..e33a1b909d48
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/media/dagger/MediaProjectionModule.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.media.dagger
+
+import android.app.Activity
+import com.android.systemui.media.MediaProjectionAppSelectorActivity
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+abstract class MediaProjectionModule {
+
+ @Binds
+ @IntoMap
+ @ClassKey(MediaProjectionAppSelectorActivity::class)
+ abstract fun provideMediaProjectionAppSelectorActivity(
+ activity: MediaProjectionAppSelectorActivity): Activity
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
index f2f275323d58..e9b6af44ddf3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputAdapter.java
@@ -42,14 +42,11 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
private static final String TAG = "MediaOutputAdapter";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
- private final MediaOutputDialog mMediaOutputDialog;
private ViewGroup mConnectedItem;
private boolean mIncludeDynamicGroup;
- public MediaOutputAdapter(MediaOutputController controller,
- MediaOutputDialog mediaOutputDialog) {
+ public MediaOutputAdapter(MediaOutputController controller) {
super(controller);
- mMediaOutputDialog = mediaOutputDialog;
setHasStableIds(true);
}
@@ -63,7 +60,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
final int size = mController.getMediaDevices().size();
- if (position == size && mController.isZeroMode()) {
+ if (position == size) {
viewHolder.onBind(CUSTOMIZED_ITEM_PAIR_NEW, false /* topMargin */,
true /* bottomMargin */);
} else if (position < size) {
@@ -78,7 +75,7 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public long getItemId(int position) {
final int size = mController.getMediaDevices().size();
- if (position == size && mController.isZeroMode()) {
+ if (position == size) {
return -1;
} else if (position < size) {
return ((List<MediaDevice>) (mController.getMediaDevices()))
@@ -91,11 +88,8 @@ public class MediaOutputAdapter extends MediaOutputBaseAdapter {
@Override
public int getItemCount() {
- if (mController.isZeroMode()) {
- // Add extra one for "pair new" or dynamic group
- return mController.getMediaDevices().size() + 1;
- }
- return mController.getMediaDevices().size();
+ // Add extra one for "pair new"
+ return mController.getMediaDevices().size() + 1;
}
class MediaDeviceViewHolder extends MediaDeviceBaseViewHolder {
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
index e5913061ef3f..0d5cab688d72 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java
@@ -91,6 +91,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
private TextView mHeaderSubtitle;
private ImageView mHeaderIcon;
private ImageView mAppResourceIcon;
+ private ImageView mBroadcastIcon;
private RecyclerView mDevicesRecyclerView;
private LinearLayout mDeviceListLayout;
private LinearLayout mCastAppLayout;
@@ -239,6 +240,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mAppButton = mDialogView.requireViewById(R.id.launch_app_button);
mAppResourceIcon = mDialogView.requireViewById(R.id.app_source_icon);
mCastAppLayout = mDialogView.requireViewById(R.id.cast_app_section);
+ mBroadcastIcon = mDialogView.requireViewById(R.id.broadcast_icon);
mDeviceListLayout.getViewTreeObserver().addOnGlobalLayoutListener(
mDeviceListLayoutListener);
@@ -366,6 +368,9 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
mStopButton.setEnabled(true);
mStopButton.setText(getStopButtonText());
mStopButton.setOnClickListener(v -> onStopButtonClick());
+
+ mBroadcastIcon.setVisibility(getBroadcastIconVisibility());
+ mBroadcastIcon.setOnClickListener(v -> onBroadcastIconClick());
}
private void updateButtonBackgroundColorFilter() {
@@ -490,6 +495,14 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements
dismiss();
}
+ public int getBroadcastIconVisibility() {
+ return View.GONE;
+ }
+
+ public void onBroadcastIconClick() {
+ // Do nothing.
+ }
+
public boolean isBroadcastSupported() {
return false;
}
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 8f065461c22d..310469dd5415 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBroadcastDialog.java
@@ -74,7 +74,7 @@ public class MediaOutputBroadcastDialog extends MediaOutputBaseDialog {
MediaOutputBroadcastDialog(Context context, boolean aboveStatusbar,
BroadcastSender broadcastSender, MediaOutputController mediaOutputController) {
super(context, broadcastSender, mediaOutputController);
- mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
+ mAdapter = new MediaOutputAdapter(mMediaOutputController);
// TODO(b/226710953): Move the part to MediaOutputBaseDialog for every class
// that extends MediaOutputBaseDialog
if (!aboveStatusbar) {
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 f8b5edcbd11f..7e3275da8c31 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -16,7 +16,7 @@
package com.android.systemui.media.dialog;
-import static android.provider.Settings.ACTION_BLUETOOTH_PAIRING_SETTINGS;
+import static android.provider.Settings.ACTION_BLUETOOTH_SETTINGS;
import android.annotation.CallbackExecutor;
import android.app.AlertDialog;
@@ -300,6 +300,9 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return;
}
try {
+ synchronized (mMediaDevicesLock) {
+ mMediaDevices.removeIf(MediaDevice::isMutingExpectedDevice);
+ }
mAudioManager.cancelMuteAwaitConnection(mAudioManager.getMutingExpectedDevice());
} catch (Exception e) {
Log.d(TAG, "Unable to cancel mute await connection");
@@ -711,22 +714,6 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
return false;
}
- boolean isZeroMode() {
- synchronized (mMediaDevicesLock) {
- if (mMediaDevices.size() == 1) {
- final MediaDevice device = mMediaDevices.iterator().next();
- // Add "pair new" only when local output device exists
- final int type = device.getDeviceType();
- if (type == MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE
- || type == MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE
- || type == MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE) {
- return true;
- }
- }
- return false;
- }
- }
-
void launchBluetoothPairing(View view) {
ActivityLaunchAnimator.Controller controller =
mDialogLaunchAnimator.createActivityLaunchController(view);
@@ -736,7 +723,7 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
}
Intent launchIntent =
- new Intent(ACTION_BLUETOOTH_PAIRING_SETTINGS)
+ new Intent(ACTION_BLUETOOTH_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
final Intent deepLinkIntent =
new Intent(Settings.ACTION_SETTINGS_EMBED_DEEP_LINK_ACTIVITY);
@@ -858,6 +845,10 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback,
|| features.contains(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK));
}
+ boolean isBluetoothLeDevice(@NonNull MediaDevice device) {
+ return device.isBLEDevice();
+ }
+
boolean isBroadcastSupported() {
LocalBluetoothLeBroadcast broadcast =
mLocalBluetoothManager.getProfileManager().getLeAudioBroadcastProfile();
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
index 9248433a4a90..fc4773d3c6dd 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java
@@ -42,7 +42,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
MediaOutputController mediaOutputController, UiEventLogger uiEventLogger) {
super(context, broadcastSender, mediaOutputController);
mUiEventLogger = uiEventLogger;
- mAdapter = new MediaOutputAdapter(mMediaOutputController, this);
+ mAdapter = new MediaOutputAdapter(mMediaOutputController);
if (!aboveStatusbar) {
getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
}
@@ -99,7 +99,12 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
@Override
public boolean isBroadcastSupported() {
- return mMediaOutputController.isBroadcastSupported();
+ boolean isBluetoothLeDevice = false;
+ if (mMediaOutputController.getCurrentConnectedMediaDevice() != null) {
+ isBluetoothLeDevice = mMediaOutputController.isBluetoothLeDevice(
+ mMediaOutputController.getCurrentConnectedMediaDevice());
+ }
+ return mMediaOutputController.isBroadcastSupported() && isBluetoothLeDevice;
}
@Override
@@ -129,6 +134,17 @@ public class MediaOutputDialog extends MediaOutputBaseDialog {
}
}
+ @Override
+ public int getBroadcastIconVisibility() {
+ return (isBroadcastSupported() && mMediaOutputController.isBluetoothLeBroadcastEnabled())
+ ? View.VISIBLE : View.GONE;
+ }
+
+ @Override
+ public void onBroadcastIconClick() {
+ startLeBroadcastDialog();
+ }
+
@VisibleForTesting
public enum MediaOutputEvent implements UiEventLogger.UiEventEnum {
@UiEvent(doc = "The MediaOutput dialog became visible on the screen.")
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index 5581fb861e6a..8249a7c0779c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -21,8 +21,10 @@ import android.media.AudioManager
import android.media.session.MediaSessionManager
import android.os.PowerExemptionManager
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.nearby.NearbyMediaDevicesManager
@@ -48,6 +50,7 @@ class MediaOutputDialogFactory @Inject constructor(
private val powerExemptionManager: PowerExemptionManager
) {
companion object {
+ private const val INTERACTION_JANK_TAG = "media_output"
var mediaOutputDialog: MediaOutputDialog? = null
}
@@ -67,7 +70,13 @@ class MediaOutputDialogFactory @Inject constructor(
// Show the dialog.
if (view != null) {
- dialogLaunchAnimator.showFromView(dialog, view)
+ dialogLaunchAnimator.showFromView(
+ dialog, view,
+ cuj = DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
} else {
dialog.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
deleted file mode 100644
index ba2f006fc58e..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupAdapter.java
+++ /dev/null
@@ -1,172 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dialog;
-
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.Drawable;
-import android.text.TextUtils;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-
-import androidx.annotation.NonNull;
-
-import com.android.settingslib.media.MediaDevice;
-import com.android.systemui.R;
-
-import java.util.List;
-
-/**
- * Adapter for media output dynamic group dialog.
- */
-//TODO: clear this class after new UI updated
-public class MediaOutputGroupAdapter extends MediaOutputBaseAdapter {
-
- private static final String TAG = "MediaOutputGroupAdapter";
- private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
- private final List<MediaDevice> mGroupMediaDevices;
-
- public MediaOutputGroupAdapter(MediaOutputController controller) {
- super(controller);
- mGroupMediaDevices = controller.getGroupMediaDevices();
- }
-
- @Override
- public MediaDeviceBaseViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup,
- int viewType) {
- super.onCreateViewHolder(viewGroup, viewType);
-
- return new GroupViewHolder(mHolderView);
- }
-
- @Override
- public void onBindViewHolder(@NonNull MediaDeviceBaseViewHolder viewHolder, int position) {
- // Add "Group"
- if (position == 0) {
- viewHolder.onBind(CUSTOMIZED_ITEM_GROUP, true /* topMargin */,
- false /* bottomMargin */);
- return;
- }
- // Add available devices
- final int newPosition = position - 1;
- final int size = mGroupMediaDevices.size();
- if (newPosition < size) {
- viewHolder.onBind(mGroupMediaDevices.get(newPosition), false /* topMargin */,
- newPosition == (size - 1) /* bottomMargin */, position);
- return;
- }
- if (DEBUG) {
- Log.d(TAG, "Incorrect position: " + position);
- }
- }
-
- @Override
- public int getItemCount() {
- // Require extra item for group volume operation
- return mGroupMediaDevices.size() + 1;
- }
-
- @Override
- CharSequence getItemTitle(MediaDevice device) {
- return super.getItemTitle(device);
- }
-
- class GroupViewHolder extends MediaDeviceBaseViewHolder {
-
- GroupViewHolder(View view) {
- super(view);
- }
-
- @Override
- void onBind(MediaDevice device, boolean topMargin, boolean bottomMargin, int position) {
- super.onBind(device, topMargin, bottomMargin, position);
- mCheckBox.setVisibility(View.VISIBLE);
- mCheckBox.setOnCheckedChangeListener((buttonView, isChecked) -> {
- onCheckBoxClicked(isChecked, device);
- });
- boolean isCurrentSeekbarInvisible = mSeekBar.getVisibility() == View.GONE;
- setTwoLineLayout(device, false /* bFocused */, true /* showSeekBar */,
- false /* showProgressBar */, false /* showSubtitle*/);
- initSeekbar(device, isCurrentSeekbarInvisible);
- final List<MediaDevice> selectedDevices = mController.getSelectedMediaDevice();
- if (isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
- mCheckBox.setButtonDrawable(R.drawable.ic_check_box);
- mCheckBox.setChecked(false);
- mCheckBox.setEnabled(true);
- } else if (isDeviceIncluded(selectedDevices, device)) {
- if (selectedDevices.size() == 1 || !isDeviceIncluded(
- mController.getDeselectableMediaDevice(), device)) {
- mCheckBox.setButtonDrawable(getDisabledCheckboxDrawable());
- mCheckBox.setChecked(true);
- mCheckBox.setEnabled(false);
- } else {
- mCheckBox.setButtonDrawable(R.drawable.ic_check_box);
- mCheckBox.setChecked(true);
- mCheckBox.setEnabled(true);
- }
- }
- }
-
- @Override
- void onBind(int customizedItem, boolean topMargin, boolean bottomMargin) {
- if (customizedItem == CUSTOMIZED_ITEM_GROUP) {
- setTwoLineLayout(mContext.getText(R.string.media_output_dialog_group),
- true /* bFocused */, true /* showSeekBar */, false /* showProgressBar */,
- false /* showSubtitle*/);
- mTitleIcon.setImageDrawable(getSpeakerDrawable());
- mCheckBox.setVisibility(View.GONE);
- initSessionSeekbar();
- }
- }
-
- private void onCheckBoxClicked(boolean isChecked, MediaDevice device) {
- if (isChecked && isDeviceIncluded(mController.getSelectableMediaDevice(), device)) {
- mController.addDeviceToPlayMedia(device);
- } else if (!isChecked && isDeviceIncluded(mController.getDeselectableMediaDevice(),
- device)) {
- mController.removeDeviceFromPlayMedia(device);
- }
- }
-
- private Drawable getDisabledCheckboxDrawable() {
- final Drawable drawable = mContext.getDrawable(R.drawable.ic_check_box_blue_24dp)
- .mutate();
- final Bitmap checkbox = Bitmap.createBitmap(drawable.getIntrinsicWidth(),
- drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
- final Canvas canvas = new Canvas(checkbox);
- TypedValue value = new TypedValue();
- mContext.getTheme().resolveAttribute(android.R.attr.disabledAlpha, value, true);
- drawable.setAlpha((int) (value.getFloat() * 255));
- drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
- drawable.draw(canvas);
-
- return drawable;
- }
-
- private boolean isDeviceIncluded(List<MediaDevice> deviceList, MediaDevice targetDevice) {
- for (MediaDevice device : deviceList) {
- if (TextUtils.equals(device.getId(), targetDevice.getId())) {
- return true;
- }
- }
- return false;
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
deleted file mode 100644
index bb3f969c86df..000000000000
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputGroupDialog.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dialog;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.core.graphics.drawable.IconCompat;
-
-import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastSender;
-
-/**
- * Dialog for media output group.
- */
-// TODO(b/203073091): Remove this class once group logic been implemented.
-public class MediaOutputGroupDialog extends MediaOutputBaseDialog {
-
- MediaOutputGroupDialog(Context context, boolean aboveStatusbar, BroadcastSender broadcastSender,
- MediaOutputController mediaOutputController) {
- super(context, broadcastSender, mediaOutputController);
- mMediaOutputController.resetGroupMediaDevices();
- mAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
- if (!aboveStatusbar) {
- getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
- }
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- }
-
- @Override
- int getHeaderIconRes() {
- return R.drawable.ic_arrow_back;
- }
-
- @Override
- IconCompat getHeaderIcon() {
- return null;
- }
-
- @Override
- int getHeaderIconSize() {
- return mContext.getResources().getDimensionPixelSize(
- R.dimen.media_output_dialog_header_back_icon_size);
- }
-
- @Override
- CharSequence getHeaderText() {
- return mContext.getString(R.string.media_output_dialog_add_output);
- }
-
- @Override
- CharSequence getHeaderSubtitle() {
- final int size = mMediaOutputController.getSelectedMediaDevice().size();
- if (size == 1) {
- return mContext.getText(R.string.media_output_dialog_single_device);
- }
- return mContext.getString(R.string.media_output_dialog_multiple_devices, size);
- }
-
- @Override
- Drawable getAppSourceIcon() {
- return null;
- }
-
- @Override
- int getStopButtonVisibility() {
- return View.VISIBLE;
- }
-
- @Override
- void onHeaderIconClick() {
- // Given that we launched the media output group dialog from the media output dialog,
- // dismissing this dialog will show the media output dialog again.
- dismiss();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
index 7c0481049098..2c35db337cda 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dream/MediaDreamComplication.java
@@ -37,11 +37,6 @@ public class MediaDreamComplication implements Complication {
}
@Override
- public int getRequiredTypeAvailability() {
- return COMPLICATION_TYPE_CAST_INFO;
- }
-
- @Override
public ViewHolder createView(ComplicationViewModel model) {
return mComponentFactory.create().getViewHolder();
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
index 3cc99a8ef77e..e95976f555f8 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/ChipInfoCommon.kt
@@ -27,4 +27,4 @@ interface ChipInfoCommon {
fun getTimeoutMs(): Long
}
-const val DEFAULT_TIMEOUT_MILLIS = 3000L
+const val DEFAULT_TIMEOUT_MILLIS = 4000L
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
index 7cc52e428218..c9fce794f57f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommon.kt
@@ -25,13 +25,14 @@ import android.graphics.drawable.Drawable
import android.os.PowerManager
import android.os.SystemClock
import android.util.Log
-import android.view.Gravity
import android.view.LayoutInflater
import android.view.MotionEvent
-import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
-import android.widget.LinearLayout
+import android.view.accessibility.AccessibilityManager
+import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_CONTROLS
+import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_ICONS
+import android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT
import com.android.internal.widget.CachingIconView
import com.android.settingslib.Utils
import com.android.systemui.R
@@ -53,19 +54,23 @@ import com.android.systemui.util.view.ViewUtil
abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
internal val context: Context,
internal val logger: MediaTttLogger,
- private val windowManager: WindowManager,
+ internal val windowManager: WindowManager,
private val viewUtil: ViewUtil,
@Main private val mainExecutor: DelayableExecutor,
+ private val accessibilityManager: AccessibilityManager,
private val tapGestureDetector: TapGestureDetector,
private val powerManager: PowerManager,
@LayoutRes private val chipLayoutRes: Int
) {
- /** The window layout parameters we'll use when attaching the view to a window. */
+
+ /**
+ * Window layout params that will be used as a starting point for the [windowLayoutParams] of
+ * all subclasses.
+ */
@SuppressLint("WrongConstant") // We're allowed to use TYPE_VOLUME_OVERLAY
- private val windowLayoutParams = WindowManager.LayoutParams().apply {
+ internal val commonWindowLayoutParams = WindowManager.LayoutParams().apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
- gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
type = WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
title = WINDOW_TITLE
@@ -73,6 +78,14 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
setTrustedOverlay()
}
+ /**
+ * The window layout parameters we'll use when attaching the view to a window.
+ *
+ * Subclasses must override this to provide their specific layout params, and they should use
+ * [commonWindowLayoutParams] as part of their layout params.
+ */
+ internal abstract val windowLayoutParams: WindowManager.LayoutParams
+
/** The chip view currently being displayed. Null if the chip is not being displayed. */
private var chipView: ViewGroup? = null
@@ -110,10 +123,16 @@ abstract class MediaTttChipControllerCommon<T : ChipInfoCommon>(
}
// Cancel and re-set the chip timeout each time we get a new state.
+ val timeout = accessibilityManager.getRecommendedTimeoutMillis(
+ chipInfo.getTimeoutMs().toInt(),
+ // Not all chips have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
+ // include it just to be safe.
+ FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
+ )
cancelChipViewTimeout?.run()
cancelChipViewTimeout = mainExecutor.executeDelayed(
{ removeChip(MediaTttRemovalReason.REASON_TIMEOUT) },
- chipInfo.getTimeoutMs()
+ timeout.toLong()
)
}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index 072263fcf38c..99a5b8b2d450 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -16,16 +16,23 @@
package com.android.systemui.media.taptotransfer.receiver
+import android.annotation.SuppressLint
import android.app.StatusBarManager
import android.content.Context
+import android.graphics.PointF
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.media.MediaRoute2Info
import android.os.Handler
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
+import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
+import androidx.core.graphics.ColorUtils
+import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
@@ -35,6 +42,7 @@ import com.android.systemui.media.taptotransfer.common.MediaTttChipControllerCom
import com.android.systemui.media.taptotransfer.common.MediaTttLogger
import com.android.systemui.statusbar.CommandQueue
import com.android.systemui.statusbar.gesture.TapGestureDetector
+import com.android.systemui.util.animation.AnimationUtil.Companion.frames
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.view.ViewUtil
import javax.inject.Inject
@@ -52,6 +60,7 @@ class MediaTttChipControllerReceiver @Inject constructor(
windowManager: WindowManager,
viewUtil: ViewUtil,
mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager,
@Main private val mainHandler: Handler,
@@ -62,10 +71,22 @@ class MediaTttChipControllerReceiver @Inject constructor(
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip_receiver
) {
+ @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.BOTTOM.or(Gravity.CENTER_HORIZONTAL)
+ // Params below are needed for the ripple to work correctly
+ width = WindowManager.LayoutParams.MATCH_PARENT
+ height = WindowManager.LayoutParams.MATCH_PARENT
+ layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+ fitInsetsTypes = 0 // Ignore insets from all system bars
+ flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+ }
+
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
override fun updateMediaTapToTransferReceiverDisplay(
@StatusBarManager.MediaTransferReceiverState displayState: Int,
@@ -128,6 +149,19 @@ class MediaTttChipControllerReceiver @Inject constructor(
)
}
+ override fun animateChipIn(chipView: ViewGroup) {
+ val appIconView = chipView.requireViewById<View>(R.id.app_icon)
+ appIconView.animate()
+ .translationYBy(-1 * getTranslationAmount().toFloat())
+ .setDuration(30.frames)
+ .start()
+ appIconView.animate()
+ .alpha(1f)
+ .setDuration(5.frames)
+ .start()
+ startRipple(chipView.requireViewById(R.id.ripple))
+ }
+
override fun getIconSize(isAppIcon: Boolean): Int? =
context.resources.getDimensionPixelSize(
if (isAppIcon) {
@@ -136,6 +170,45 @@ class MediaTttChipControllerReceiver @Inject constructor(
R.dimen.media_ttt_generic_icon_size_receiver
}
)
+
+ /** Returns the amount that the chip will be translated by in its intro animation. */
+ private fun getTranslationAmount(): Int {
+ return context.resources.getDimensionPixelSize(R.dimen.media_ttt_receiver_vert_translation)
+ }
+
+ private fun startRipple(rippleView: ReceiverChipRippleView) {
+ if (rippleView.rippleInProgress) {
+ // Skip if ripple is still playing
+ return
+ }
+ rippleView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+ override fun onViewDetachedFromWindow(view: View?) {}
+
+ override fun onViewAttachedToWindow(view: View?) {
+ if (view == null) {
+ return
+ }
+ val attachedRippleView = view as ReceiverChipRippleView
+ layoutRipple(attachedRippleView)
+ attachedRippleView.startRipple()
+ attachedRippleView.removeOnAttachStateChangeListener(this)
+ }
+ })
+ }
+
+ private fun layoutRipple(rippleView: ReceiverChipRippleView) {
+ val windowBounds = windowManager.currentWindowMetrics.bounds
+ val height = windowBounds.height()
+ val width = windowBounds.width()
+
+ rippleView.radius = height / 5f
+ // Center the ripple on the bottom of the screen in the middle.
+ rippleView.origin = PointF(/* x= */ width / 2f, /* y= */ height.toFloat())
+
+ val color = Utils.getColorAttrDefaultColor(context, R.attr.wallpaperTextColorAccent)
+ val colorWithAlpha = ColorUtils.setAlphaComponent(color, 70)
+ rippleView.setColor(colorWithAlpha)
+ }
}
data class ChipReceiverInfo(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
index 46e4299f99fa..fed546bf52c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/stagesplit/ISplitScreenListener.aidl
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/ReceiverChipRippleView.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,20 +14,20 @@
* limitations under the License.
*/
-package com.android.wm.shell.stagesplit;
+package com.android.systemui.media.taptotransfer.receiver
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.ripple.RippleView
/**
- * Listener interface that Launcher attaches to SystemUI to get split-screen callbacks.
+ * An expanding ripple effect for the media tap-to-transfer receiver chip.
*/
-oneway interface ISplitScreenListener {
-
- /**
- * Called when the stage position changes.
- */
- void onStagePositionChanged(int stage, int position);
-
- /**
- * Called when a task changes stages.
- */
- void onTaskStageChanged(int taskId, int stage, boolean visible);
-} \ No newline at end of file
+class ReceiverChipRippleView(
+ context: Context?, attrs: AttributeSet?
+) : RippleView(context, attrs) {
+ init {
+ setRippleFill(true)
+ duration = 3000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
index 54b4380e2443..797a7701413b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSender.kt
@@ -21,9 +21,11 @@ import android.content.Context
import android.media.MediaRoute2Info
import android.os.PowerManager
import android.util.Log
+import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.TextView
import com.android.internal.statusbar.IUndoMediaTransferCallback
import com.android.systemui.R
@@ -53,6 +55,7 @@ class MediaTttChipControllerSender @Inject constructor(
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager,
private val uiEventLogger: MediaTttSenderUiEventLogger
@@ -62,10 +65,15 @@ class MediaTttChipControllerSender @Inject constructor(
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip
) {
+ override val windowLayoutParams = commonWindowLayoutParams.apply {
+ gravity = Gravity.TOP.or(Gravity.CENTER_HORIZONTAL)
+ }
+
private var currentlyDisplayedChipState: ChipStateSender? = null
private val commandQueueCallbacks = object : CommandQueue.Callbacks {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
index 779292c43340..6bc50a6b2406 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavBarHelper.java
@@ -17,11 +17,19 @@
package com.android.systemui.navigationbar;
import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU;
+import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
+import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON;
import static com.android.systemui.accessibility.SystemActions.SYSTEM_ACTION_ID_ACCESSIBILITY_BUTTON_CHOOSER;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
+import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import android.content.ContentResolver;
import android.content.Context;
@@ -50,6 +58,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.shared.system.QuickStepContract;
+import com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import java.io.PrintWriter;
@@ -325,6 +334,23 @@ public final class NavBarHelper implements
void updateAssistantAvailable(boolean available);
}
+ static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
+ final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS;
+ if (isTransient) {
+ return MODE_SEMI_TRANSPARENT;
+ } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
+ return MODE_LIGHTS_OUT;
+ } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) {
+ return MODE_LIGHTS_OUT_TRANSPARENT;
+ } else if ((appearance & APPEARANCE_OPAQUE_NAVIGATION_BARS) != 0) {
+ return MODE_OPAQUE;
+ } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS) != 0) {
+ return MODE_SEMI_TRANSPARENT;
+ } else {
+ return MODE_TRANSPARENT;
+ }
+ }
+
@Override
public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
pw.println("NavbarTaskbarFriendster");
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index aa38b781229c..281ef9495d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -27,9 +27,6 @@ import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
import static android.view.InsetsState.containsType;
-import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_NAVIGATION_BARS;
-import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
@@ -39,6 +36,7 @@ import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NAV_BAR_HANDLE_FORCE_OPAQUE;
+import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import static com.android.systemui.shared.recents.utilities.Utilities.isTablet;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
@@ -48,11 +46,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_I
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_SEMI_TRANSPARENT;
-import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE;
import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions;
@@ -86,7 +80,7 @@ import android.util.Log;
import android.view.Display;
import android.view.Gravity;
import android.view.HapticFeedbackConstants;
-import android.view.InsetsState;
+import android.view.InsetsFrameProvider;
import android.view.InsetsState.InternalInsetsType;
import android.view.InsetsVisibilities;
import android.view.KeyEvent;
@@ -111,6 +105,7 @@ import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.util.LatencyTracker;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Gefingerpoken;
@@ -215,8 +210,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
private final UiEventLogger mUiEventLogger;
private final NavBarHelper mNavBarHelper;
private final NotificationShadeDepthController mNotificationShadeDepthController;
- private final UserContextProvider mUserContextProvider;
private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
+ private final UserContextProvider mUserContextProvider;
private final RegionSamplingHelper mRegionSamplingHelper;
private final int mNavColorSampleMargin;
private NavigationBarFrame mFrame;
@@ -631,6 +626,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
}
}, mainExecutor, bgExecutor);
+ mView.setBackgroundExecutor(bgExecutor);
mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
}
@@ -1075,7 +1071,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
}
@@ -1152,23 +1149,6 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
return false;
}
- private static @TransitionMode int transitionMode(boolean isTransient, int appearance) {
- final int lightsOutOpaque = APPEARANCE_LOW_PROFILE_BARS | APPEARANCE_OPAQUE_NAVIGATION_BARS;
- if (isTransient) {
- return MODE_SEMI_TRANSPARENT;
- } else if ((appearance & lightsOutOpaque) == lightsOutOpaque) {
- return MODE_LIGHTS_OUT;
- } else if ((appearance & APPEARANCE_LOW_PROFILE_BARS) != 0) {
- return MODE_LIGHTS_OUT_TRANSPARENT;
- } else if ((appearance & APPEARANCE_OPAQUE_NAVIGATION_BARS) != 0) {
- return MODE_OPAQUE;
- } else if ((appearance & APPEARANCE_SEMI_TRANSPARENT_NAVIGATION_BARS) != 0) {
- return MODE_SEMI_TRANSPARENT;
- } else {
- return MODE_TRANSPARENT;
- }
- }
-
@Override
public void disable(int displayId, int state1, int state2, boolean animate) {
if (displayId != mDisplayId) {
@@ -1654,12 +1634,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements
| WindowManager.LayoutParams.FLAG_SLIPPERY,
PixelFormat.TRANSLUCENT);
lp.gravity = gravity;
- lp.providedInternalInsets = new Insets[InsetsState.SIZE];
if (insetsHeight != -1) {
- lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] =
- Insets.of(0, height - insetsHeight, 0, 0);
+ lp.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR, Insets.of(0, 0, 0, insetsHeight))
+ };
} else {
- lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] = null;
+ lp.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
+ };
}
lp.token = new Binder();
lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
index d756af77d2a9..2a7c8716f036 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarController.java
@@ -69,7 +69,6 @@ import java.util.Optional;
import javax.inject.Inject;
-
/** A controller to handle navigation bars. */
@SysUISingleton
public class NavigationBarController implements
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
index 3fc9afe6ea94..9e1ba5f723a3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java
@@ -72,6 +72,7 @@ import com.android.systemui.navigationbar.buttons.NearestTouchFrame;
import com.android.systemui.navigationbar.buttons.RotationContextButton;
import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
import com.android.systemui.recents.Recents;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton.RotationButtonUpdatesCallback;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -81,13 +82,13 @@ import com.android.systemui.shared.system.WindowManagerWrapper;
import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.wm.shell.back.BackAnimation;
import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.Executor;
import java.util.function.Consumer;
/** */
@@ -97,6 +98,8 @@ public class NavigationBarView extends FrameLayout {
final static boolean ALTERNATE_CAR_MODE_UI = false;
+ private Executor mBgExecutor;
+
// The current view is one of mHorizontal or mVertical depending on the current configuration
View mCurrentView = null;
private View mVertical;
@@ -349,6 +352,10 @@ public class NavigationBarView extends FrameLayout {
notifyVerticalChangedListener(mIsVertical);
}
+ public void setBackgroundExecutor(Executor bgExecutor) {
+ mBgExecutor = bgExecutor;
+ }
+
public void setTouchHandler(Gefingerpoken touchHandler) {
mTouchHandler = touchHandler;
}
@@ -768,8 +775,8 @@ public class NavigationBarView extends FrameLayout {
updateSlippery();
reloadNavIcons();
updateNavButtonIcons();
- WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(
- !mShowSwipeUpUi);
+ mBgExecutor.execute(() -> WindowManagerWrapper.getInstance()
+ .setNavBarVirtualKeyHapticFeedbackEnabled(!mShowSwipeUpUi));
getHomeButton().setAccessibilityDelegate(
mShowSwipeUpUi ? mQuickStepAccessibilityDelegate : null);
}
@@ -812,6 +819,7 @@ public class NavigationBarView extends FrameLayout {
mImeDrawsImeNavBar = imeDrawsImeNavBar;
mBarTransitions.onNavigationModeChanged(mNavBarMode);
mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
+ mRotationButtonController.onNavigationModeChanged(mNavBarMode);
updateRotationButton();
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 8ee16a92a9bd..9e0c49641e72 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -24,6 +24,7 @@ import static android.view.InsetsState.containsType;
import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
@@ -35,6 +36,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_I
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
+import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
import android.app.StatusBarManager;
import android.app.StatusBarManager.WindowVisibleState;
@@ -50,10 +52,12 @@ import android.util.Log;
import android.view.Display;
import android.view.InsetsVisibilities;
import android.view.View;
+import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
import androidx.annotation.NonNull;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
@@ -114,6 +118,9 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
};
private int mDisabledFlags;
private @WindowVisibleState int mTaskBarWindowState = WINDOW_STATE_SHOWING;
+
+ private @TransitionMode int mTransitionMode;
+ private @Appearance int mAppearance;
private @Behavior int mBehavior;
private final Context mContext;
private final DisplayManager mDisplayManager;
@@ -146,19 +153,12 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
}
};
- private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> {
- if (visible) {
- mAutoHideController.touchAutoHide();
- }
- };
private BackAnimation mBackAnimation;
@Inject
- public TaskbarDelegate(
- Context context,
+ public TaskbarDelegate(Context context,
EdgeBackGestureHandler.Factory edgeBackGestureHandlerFactory,
- LightBarTransitionsController.Factory lightBarTransitionsControllerFactory
- ) {
+ LightBarTransitionsController.Factory lightBarTransitionsControllerFactory) {
mLightBarTransitionsControllerFactory = lightBarTransitionsControllerFactory;
mEdgeBackGestureHandler = edgeBackGestureHandlerFactory.create(context);
@@ -358,11 +358,18 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
@Override
public void onSystemBarAttributesChanged(int displayId, int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
- InsetsVisibilities requestedVisibilities, String packageName) {
+ InsetsVisibilities requestedVisibilities, String packageName,
+ LetterboxDetails[] letterboxDetails) {
mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
- if (mLightBarController != null && displayId == mDisplayId) {
- mLightBarController.onNavigationBarAppearanceChanged(appearance, false/*nbModeChanged*/,
- BarTransitions.MODE_TRANSPARENT /*navigationBarMode*/, navbarColorManagedByIme);
+ boolean nbModeChanged = false;
+ if (mAppearance != appearance) {
+ mAppearance = appearance;
+ nbModeChanged = updateTransitionMode(
+ transitionMode(mTaskbarTransientShowing, appearance));
+ }
+ if (displayId == mDisplayId) {
+ mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
+ BarTransitions.MODE_TRANSPARENT, navbarColorManagedByIme);
}
if (mBehavior != behavior) {
mBehavior = behavior;
@@ -413,6 +420,22 @@ public class TaskbarDelegate implements CommandQueue.Callbacks,
private void onTransientStateChanged() {
mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTaskbarTransientShowing);
+
+ final int transitionMode = transitionMode(mTaskbarTransientShowing, mAppearance);
+ if (updateTransitionMode(transitionMode)) {
+ mLightBarController.onNavigationBarModeChanged(transitionMode);
+ }
+ }
+
+ private boolean updateTransitionMode(int barMode) {
+ if (mTransitionMode != barMode) {
+ mTransitionMode = barMode;
+ if (mAutoHideController != null) {
+ mAutoHideController.touchAutoHide();
+ }
+ return true;
+ }
+ return false;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
new file mode 100644
index 000000000000..282243563952
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanel.kt
@@ -0,0 +1,397 @@
+package com.android.systemui.navigationbar.gestural
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.graphics.Path
+import android.graphics.RectF
+import android.view.View
+import androidx.dynamicanimation.animation.FloatPropertyCompat
+import androidx.dynamicanimation.animation.SpringAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.util.LatencyTracker
+import com.android.settingslib.Utils
+import com.android.systemui.navigationbar.gestural.BackPanelController.DelayedOnAnimationEndListener
+
+private const val TAG = "BackPanel"
+private const val DEBUG = false
+
+class BackPanel(context: Context, private val latencyTracker: LatencyTracker) : View(context) {
+
+ var arrowsPointLeft = false
+ set(value) {
+ if (field != value) {
+ invalidate()
+ field = value
+ }
+ }
+
+ // Arrow color and shape
+ private val arrowPath = Path()
+ private val arrowPaint = Paint()
+
+ // Arrow background color and shape
+ private var arrowBackgroundRect = RectF()
+ private var arrowBackgroundPaint = Paint()
+
+ // True if the panel is currently on the left of the screen
+ var isLeftPanel = false
+
+ /**
+ * Used to track back arrow latency from [android.view.MotionEvent.ACTION_DOWN] to [onDraw]
+ */
+ private var trackingBackArrowLatency = false
+
+ /**
+ * The length of the arrow measured horizontally. Used for animating [arrowPath]
+ */
+ private var arrowLength = AnimatedFloat("arrowLength", SpringForce())
+
+ /**
+ * The height of the arrow measured vertically from its center to its top (i.e. half the total
+ * height). Used for animating [arrowPath]
+ */
+ private var arrowHeight = AnimatedFloat("arrowHeight", SpringForce())
+
+ private val backgroundWidth = AnimatedFloat(
+ name = "backgroundWidth",
+ SpringForce().apply {
+ stiffness = 600f
+ dampingRatio = 0.65f
+ }
+ )
+
+ private val backgroundHeight = AnimatedFloat(
+ name = "backgroundHeight",
+ SpringForce().apply {
+ stiffness = 600f
+ dampingRatio = 0.65f
+ }
+ )
+
+ /**
+ * Corners of the background closer to the edge of the screen (where the arrow appeared from).
+ * Used for animating [arrowBackgroundRect]
+ */
+ private val backgroundEdgeCornerRadius = AnimatedFloat(
+ name = "backgroundEdgeCornerRadius",
+ SpringForce().apply {
+ stiffness = 400f
+ dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
+ }
+ )
+
+ /**
+ * Corners of the background further from the edge of the screens (toward the direction the
+ * arrow is being dragged). Used for animating [arrowBackgroundRect]
+ */
+ private val backgroundFarCornerRadius = AnimatedFloat(
+ name = "backgroundDragCornerRadius",
+ SpringForce().apply {
+ stiffness = 2200f
+ dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
+ }
+ )
+
+ /**
+ * Left/right position of the background relative to the canvas. Also corresponds with the
+ * background's margin relative to the screen edge. The arrow will be centered within the
+ * background.
+ */
+ private var horizontalTranslation = AnimatedFloat("horizontalTranslation", SpringForce())
+
+ private val currentAlpha: FloatPropertyCompat<BackPanel> =
+ object : FloatPropertyCompat<BackPanel>("currentAlpha") {
+ override fun setValue(panel: BackPanel, value: Float) {
+ panel.alpha = value
+ }
+
+ override fun getValue(panel: BackPanel): Float = panel.alpha
+ }
+
+ private val alphaAnimation = SpringAnimation(this, currentAlpha)
+ .setSpring(
+ SpringForce()
+ .setStiffness(60f)
+ .setDampingRatio(SpringForce.DAMPING_RATIO_NO_BOUNCY)
+ )
+
+ /**
+ * Canvas vertical translation. How far up/down the arrow and background appear relative to the
+ * canvas.
+ */
+ private var verticalTranslation: AnimatedFloat = AnimatedFloat(
+ name = "verticalTranslation",
+ SpringForce().apply {
+ stiffness = SpringForce.STIFFNESS_MEDIUM
+ }
+ )
+
+ /**
+ * Use for drawing debug info. Can only be set if [DEBUG]=true
+ */
+ var drawDebugInfo: ((canvas: Canvas) -> Unit)? = null
+ set(value) {
+ if (DEBUG) field = value
+ }
+
+ internal fun updateArrowPaint(arrowThickness: Float) {
+ // Arrow constants
+ arrowPaint.strokeWidth = arrowThickness
+
+ arrowPaint.color =
+ Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
+ arrowBackgroundPaint.color = Utils.getColorAccentDefaultColor(context)
+ }
+
+ private inner class AnimatedFloat(name: String, springForce: SpringForce) {
+ // The resting position when not stretched by a touch drag
+ private var restingPosition = 0f
+
+ // The current position as updated by the SpringAnimation
+ var pos = 0f
+ set(v) {
+ if (field != v) {
+ field = v
+ invalidate()
+ }
+ }
+
+ val animation: SpringAnimation
+
+ init {
+ val floatProp = object : FloatPropertyCompat<AnimatedFloat>(name) {
+ override fun setValue(animatedFloat: AnimatedFloat, value: Float) {
+ animatedFloat.pos = value
+ }
+
+ override fun getValue(animatedFloat: AnimatedFloat): Float = animatedFloat.pos
+ }
+ animation = SpringAnimation(this, floatProp)
+ animation.spring = springForce
+ }
+
+ fun snapTo(newPosition: Float) {
+ animation.cancel()
+ restingPosition = newPosition
+ animation.spring.finalPosition = newPosition
+ pos = newPosition
+ }
+
+ fun stretchTo(stretchAmount: Float) {
+ animation.animateToFinalPosition(restingPosition + stretchAmount)
+ }
+
+ /**
+ * Animates to a new position ([finalPosition]) that is the given fraction ([amount])
+ * between the existing [restingPosition] and the new [finalPosition].
+ *
+ * The [restingPosition] will remain unchanged. Only the animation is updated.
+ */
+ fun stretchBy(finalPosition: Float, amount: Float) {
+ val stretchedAmount = amount * (finalPosition - restingPosition)
+ animation.animateToFinalPosition(restingPosition + stretchedAmount)
+ }
+
+ fun updateRestingPosition(pos: Float, animated: Boolean) {
+ restingPosition = pos
+ if (animated)
+ animation.animateToFinalPosition(restingPosition)
+ else
+ snapTo(restingPosition)
+ }
+ }
+
+ init {
+ visibility = GONE
+ arrowPaint.apply {
+ style = Paint.Style.STROKE
+ strokeCap = Paint.Cap.SQUARE
+ }
+ arrowBackgroundPaint.apply {
+ style = Paint.Style.FILL
+ strokeJoin = Paint.Join.ROUND
+ strokeCap = Paint.Cap.ROUND
+ }
+ }
+
+ private fun calculateArrowPath(dx: Float, dy: Float): Path {
+ arrowPath.reset()
+ arrowPath.moveTo(dx, -dy)
+ arrowPath.lineTo(0f, 0f)
+ arrowPath.lineTo(dx, dy)
+ arrowPath.moveTo(dx, -dy)
+ return arrowPath
+ }
+
+ fun addEndListener(endListener: DelayedOnAnimationEndListener): Boolean {
+ return if (alphaAnimation.isRunning) {
+ alphaAnimation.addEndListener(endListener)
+ true
+ } else if (horizontalTranslation.animation.isRunning) {
+ horizontalTranslation.animation.addEndListener(endListener)
+ true
+ } else {
+ endListener.runNow()
+ false
+ }
+ }
+
+ fun setStretch(
+ horizontalTranslationStretchAmount: Float,
+ arrowStretchAmount: Float,
+ backgroundWidthStretchAmount: Float,
+ fullyStretchedDimens: EdgePanelParams.BackIndicatorDimens
+ ) {
+ horizontalTranslation.stretchBy(
+ finalPosition = fullyStretchedDimens.horizontalTranslation,
+ amount = horizontalTranslationStretchAmount
+ )
+ arrowLength.stretchBy(
+ finalPosition = fullyStretchedDimens.arrowDimens.length,
+ amount = arrowStretchAmount
+ )
+ arrowHeight.stretchBy(
+ finalPosition = fullyStretchedDimens.arrowDimens.height,
+ amount = arrowStretchAmount
+ )
+ backgroundWidth.stretchBy(
+ finalPosition = fullyStretchedDimens.backgroundDimens.width,
+ amount = backgroundWidthStretchAmount
+ )
+ }
+
+ fun resetStretch() {
+ horizontalTranslation.stretchTo(0f)
+ arrowLength.stretchTo(0f)
+ arrowHeight.stretchTo(0f)
+ backgroundWidth.stretchTo(0f)
+ backgroundHeight.stretchTo(0f)
+ backgroundEdgeCornerRadius.stretchTo(0f)
+ backgroundFarCornerRadius.stretchTo(0f)
+ }
+
+ /**
+ * Updates resting arrow and background size not accounting for stretch
+ */
+ internal fun setRestingDimens(
+ restingParams: EdgePanelParams.BackIndicatorDimens,
+ animate: Boolean
+ ) {
+ horizontalTranslation.updateRestingPosition(restingParams.horizontalTranslation, animate)
+ arrowLength.updateRestingPosition(restingParams.arrowDimens.length, animate)
+ arrowHeight.updateRestingPosition(restingParams.arrowDimens.height, animate)
+ backgroundWidth.updateRestingPosition(restingParams.backgroundDimens.width, animate)
+ backgroundHeight.updateRestingPosition(restingParams.backgroundDimens.height, animate)
+ backgroundEdgeCornerRadius.updateRestingPosition(
+ restingParams.backgroundDimens.edgeCornerRadius,
+ animate
+ )
+ backgroundFarCornerRadius.updateRestingPosition(
+ restingParams.backgroundDimens.farCornerRadius,
+ animate
+ )
+ }
+
+ fun animateVertically(yPos: Float) = verticalTranslation.stretchTo(yPos)
+
+ fun setArrowStiffness(arrowStiffness: Float, arrowDampingRatio: Float) {
+ arrowLength.animation.spring.apply {
+ stiffness = arrowStiffness
+ dampingRatio = arrowDampingRatio
+ }
+ arrowHeight.animation.spring.apply {
+ stiffness = arrowStiffness
+ dampingRatio = arrowDampingRatio
+ }
+ }
+
+ override fun hasOverlappingRendering() = false
+
+ override fun onDraw(canvas: Canvas) {
+ var edgeCorner = backgroundEdgeCornerRadius.pos
+ val farCorner = backgroundFarCornerRadius.pos
+ val halfHeight = backgroundHeight.pos / 2
+
+ canvas.save()
+
+ if (!isLeftPanel) canvas.scale(-1f, 1f, width / 2.0f, 0f)
+
+ canvas.translate(
+ horizontalTranslation.pos,
+ height * 0.5f + verticalTranslation.pos
+ )
+
+ val arrowBackground = arrowBackgroundRect.apply {
+ left = 0f
+ top = -halfHeight
+ right = backgroundWidth.pos
+ bottom = halfHeight
+ }.toPathWithRoundCorners(
+ topLeft = edgeCorner,
+ bottomLeft = edgeCorner,
+ topRight = farCorner,
+ bottomRight = farCorner
+ )
+ canvas.drawPath(arrowBackground, arrowBackgroundPaint)
+
+ val dx = arrowLength.pos
+ val dy = arrowHeight.pos
+
+ // How far the arrow bounding box should be from the edge of the screen. Measured from
+ // either the tip or the back of the arrow, whichever is closer
+ var arrowOffset = (backgroundWidth.pos - dx) / 2
+ canvas.translate(
+ /* dx= */ arrowOffset,
+ /* dy= */ 0f /* pass 0 for the y position since the canvas was already translated */
+ )
+
+ val arrowPointsAwayFromEdge = !arrowsPointLeft.xor(isLeftPanel)
+ if (arrowPointsAwayFromEdge) {
+ canvas.apply {
+ scale(-1f, 1f, 0f, 0f)
+ translate(-dx, 0f)
+ }
+ }
+
+ val arrowPath = calculateArrowPath(dx = dx, dy = dy)
+ canvas.drawPath(arrowPath, arrowPaint)
+ canvas.restore()
+
+ if (trackingBackArrowLatency) {
+ latencyTracker.onActionEnd(LatencyTracker.ACTION_SHOW_BACK_ARROW)
+ trackingBackArrowLatency = false
+ }
+
+ if (DEBUG) drawDebugInfo?.invoke(canvas)
+ }
+
+ fun startTrackingShowBackArrowLatency() {
+ latencyTracker.onActionStart(LatencyTracker.ACTION_SHOW_BACK_ARROW)
+ trackingBackArrowLatency = true
+ }
+
+ private fun RectF.toPathWithRoundCorners(
+ topLeft: Float = 0f,
+ topRight: Float = 0f,
+ bottomRight: Float = 0f,
+ bottomLeft: Float = 0f
+ ): Path = Path().apply {
+ val corners = floatArrayOf(
+ topLeft, topLeft,
+ topRight, topRight,
+ bottomRight, bottomRight,
+ bottomLeft, bottomLeft
+ )
+ addRoundRect(this@toPathWithRoundCorners, corners, Path.Direction.CW)
+ }
+
+ fun cancelAlphaAnimations() {
+ alphaAnimation.cancel()
+ alpha = 1f
+ }
+
+ fun fadeOut() {
+ alphaAnimation.animateToFinalPosition(0f)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
new file mode 100644
index 000000000000..b05e75ea1c97
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.navigationbar.gestural
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.Paint
+import android.graphics.Point
+import android.os.Handler
+import android.os.SystemClock
+import android.os.VibrationEffect
+import android.util.Log
+import android.util.MathUtils.constrain
+import android.util.MathUtils.saturate
+import android.view.Gravity
+import android.view.MotionEvent
+import android.view.VelocityTracker
+import android.view.View
+import android.view.ViewConfiguration
+import android.view.WindowManager
+import android.view.animation.DecelerateInterpolator
+import android.view.animation.PathInterpolator
+import androidx.dynamicanimation.animation.DynamicAnimation
+import androidx.dynamicanimation.animation.SpringForce
+import com.android.internal.util.LatencyTracker
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.NavigationEdgeBackPlugin
+import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.ViewController
+import com.android.wm.shell.back.BackAnimation
+import java.io.PrintWriter
+import javax.inject.Inject
+import kotlin.math.abs
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.sign
+
+private const val TAG = "BackPanelController"
+private const val DEBUG = false
+
+private const val ENABLE_FAILSAFE = true
+
+private const val FAILSAFE_DELAY_MS: Long = 350
+
+/**
+ * The time required between the arrow-appears vibration effect and the back-committed vibration
+ * effect. If the arrow is flung quickly, the phone only vibrates once. However, if the arrow is
+ * held on the screen for a long time, it will vibrate a second time when the back gesture is
+ * committed.
+ */
+private const val GESTURE_DURATION_FOR_CLICK_MS = 400
+
+/**
+ * The min duration arrow remains on screen during a fling event.
+ */
+private const val FLING_MIN_APPEARANCE_DURATION = 235L
+
+/**
+ * The min duration arrow remains on screen during a fling event.
+ */
+private const val MIN_FLING_VELOCITY = 3000
+
+/**
+ * The amount of rubber banding we do for the vertical translation
+ */
+private const val RUBBER_BAND_AMOUNT = 15
+
+private const val ARROW_APPEAR_STIFFNESS = 600f
+private const val ARROW_APPEAR_DAMPING_RATIO = 0.4f
+private const val ARROW_DISAPPEAR_STIFFNESS = 1200f
+private const val ARROW_DISAPPEAR_DAMPING_RATIO = SpringForce.DAMPING_RATIO_NO_BOUNCY
+
+/**
+ * The interpolator used to rubber band
+ */
+private val RUBBER_BAND_INTERPOLATOR = PathInterpolator(1.0f / 5.0f, 1.0f, 1.0f, 1.0f)
+
+private val DECELERATE_INTERPOLATOR = DecelerateInterpolator()
+
+private val DECELERATE_INTERPOLATOR_SLOW = DecelerateInterpolator(0.7f)
+
+class BackPanelController private constructor(
+ context: Context,
+ private val windowManager: WindowManager,
+ private val viewConfiguration: ViewConfiguration,
+ @Main private val mainHandler: Handler,
+ private val vibratorHelper: VibratorHelper,
+ private val configurationController: ConfigurationController,
+ latencyTracker: LatencyTracker
+) : ViewController<BackPanel>(BackPanel(context, latencyTracker)), NavigationEdgeBackPlugin {
+
+ /**
+ * Injectable instance to create a new BackPanelController.
+ *
+ * Necessary because EdgeBackGestureHandler sometimes needs to create new instances of
+ * BackPanelController, and we need to match EdgeBackGestureHandler's context.
+ */
+ class Factory @Inject constructor(
+ private val windowManager: WindowManager,
+ private val viewConfiguration: ViewConfiguration,
+ @Main private val mainHandler: Handler,
+ private val vibratorHelper: VibratorHelper,
+ private val configurationController: ConfigurationController,
+ private val latencyTracker: LatencyTracker
+ ) {
+ /** Construct a [BackPanelController]. */
+ fun create(context: Context, backAnimation: BackAnimation?): BackPanelController {
+ val backPanelController = BackPanelController(
+ context,
+ windowManager,
+ viewConfiguration,
+ mainHandler,
+ vibratorHelper,
+ configurationController,
+ latencyTracker
+ )
+ backPanelController.init()
+ return backPanelController
+ }
+ }
+
+ private var params: EdgePanelParams = EdgePanelParams(resources)
+ private var currentState: GestureState = GestureState.GONE
+ private var previousState: GestureState = GestureState.GONE
+
+ // Phone should only vibrate the first time the arrow is activated
+ private var hasHapticPlayed = false
+
+ // Screen attributes
+ private lateinit var layoutParams: WindowManager.LayoutParams
+ private val displaySize = Point()
+
+ private lateinit var backCallback: NavigationEdgeBackPlugin.BackCallback
+
+ private var previousXTranslation = 0f
+ private var totalTouchDelta = 0f
+ private var velocityTracker: VelocityTracker? = null
+ set(value) {
+ if (field != value) field?.recycle()
+ field = value
+ }
+ get() {
+ if (field == null) field = VelocityTracker.obtain()
+ return field
+ }
+
+ // The x,y position of the first touch event
+ private var startX = 0f
+ private var startY = 0f
+
+ private var gestureStartTime = 0L
+
+ // Whether the current gesture has moved a sufficiently large amount,
+ // so that we can unambiguously start showing the ENTRY animation
+ private var hasPassedDragSlop = false
+
+ private val failsafeRunnable = Runnable { onFailsafe() }
+
+ private enum class GestureState {
+ /* Arrow is off the screen and invisible */
+ GONE,
+
+ /* Arrow is animating in */
+ ENTRY,
+
+ /* could be entry, neutral, or stretched, releasing will commit back */
+ ACTIVE,
+
+ /* releasing will cancel back */
+ INACTIVE,
+
+ /* like committed, but animation takes longer */
+ FLUNG,
+
+ /* back action currently occurring, arrow soon to be GONE */
+ COMMITTED,
+
+ /* back action currently cancelling, arrow soon to be GONE */
+ CANCELLED;
+
+ /**
+ * @return true if the current state responds to touch move events in some way (e.g. by
+ * stretching the back indicator)
+ */
+ fun isInteractive(): Boolean {
+ return when (this) {
+ ENTRY, ACTIVE, INACTIVE -> true
+ GONE, FLUNG, COMMITTED, CANCELLED -> false
+ }
+ }
+ }
+
+ /**
+ * Wrapper around OnAnimationEndListener which runs the given runnable after a delay. The
+ * runnable is not called if the animation is cancelled
+ */
+ inner class DelayedOnAnimationEndListener internal constructor(
+ private val handler: Handler,
+ private val runnable: Runnable,
+ private val minDuration: Long
+ ) : DynamicAnimation.OnAnimationEndListener {
+ override fun onAnimationEnd(
+ animation: DynamicAnimation<*>,
+ canceled: Boolean,
+ value: Float,
+ velocity: Float
+ ) {
+ animation.removeEndListener(this)
+ if (!canceled) {
+ // Total elapsed time of the gesture and the animation
+ val totalElapsedTime = SystemClock.uptimeMillis() - gestureStartTime
+ // The delay between finishing this animation and starting the runnable
+ val delay = max(0, minDuration - totalElapsedTime)
+ handler.postDelayed(runnable, delay)
+ }
+ }
+
+ internal fun runNow() {
+ runnable.run()
+ }
+ }
+
+ private val setCommittedEndListener =
+ DelayedOnAnimationEndListener(
+ mainHandler,
+ { updateArrowState(GestureState.COMMITTED) },
+ minDuration = FLING_MIN_APPEARANCE_DURATION
+ )
+
+ private val setGoneEndListener =
+ DelayedOnAnimationEndListener(
+ mainHandler,
+ {
+ cancelFailsafe()
+ updateArrowState(GestureState.GONE)
+ },
+ minDuration = 0
+ )
+
+ // Vibration
+ private var vibrationTime: Long = 0
+
+ // Minimum of the screen's width or the predefined threshold
+ private var fullyStretchedThreshold = 0f
+
+ /**
+ * Used for initialization and configuration changes
+ */
+ private fun updateConfiguration() {
+ params.update(resources)
+ mView.updateArrowPaint(params.arrowThickness)
+ }
+
+ private val configurationListener = object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateConfiguration()
+ }
+
+ override fun onLayoutDirectionChanged(isLayoutRtl: Boolean) {
+ updateArrowDirection(isLayoutRtl)
+ }
+ }
+
+ override fun onViewAttached() {
+ updateConfiguration()
+ updateArrowDirection(configurationController.isLayoutRtl)
+ updateArrowState(GestureState.GONE, force = true)
+ updateRestingArrowDimens(animated = false, currentState)
+ configurationController.addCallback(configurationListener)
+ }
+
+ /** Update the arrow direction. The arrow should point the same way for both panels. */
+ private fun updateArrowDirection(isLayoutRtl: Boolean) {
+ mView.arrowsPointLeft = isLayoutRtl
+ }
+
+ override fun onViewDetached() {
+ configurationController.removeCallback(configurationListener)
+ }
+
+ override fun onMotionEvent(event: MotionEvent) {
+ velocityTracker!!.addMovement(event)
+ when (event.actionMasked) {
+ MotionEvent.ACTION_DOWN -> {
+ resetOnDown()
+ startX = event.x
+ startY = event.y
+ gestureStartTime = SystemClock.uptimeMillis()
+ }
+ MotionEvent.ACTION_MOVE -> {
+ // only go to the ENTRY state after some minimum motion has occurred
+ if (dragSlopExceeded(event.x, startX)) {
+ handleMoveEvent(event)
+ }
+ }
+ MotionEvent.ACTION_UP -> {
+ if (currentState == GestureState.ACTIVE) {
+ updateArrowState(if (isFlung()) GestureState.FLUNG else GestureState.COMMITTED)
+ } else if (currentState != GestureState.GONE) { // if invisible, skip animation
+ updateArrowState(GestureState.CANCELLED)
+ }
+ velocityTracker = null
+ }
+ MotionEvent.ACTION_CANCEL -> {
+ // Receiving a CANCEL implies that something else intercepted
+ // the gesture, i.e., the user did not cancel their gesture.
+ // Therefore, disappear immediately, with minimum fanfare.
+ updateArrowState(GestureState.GONE)
+ velocityTracker = null
+ }
+ }
+ }
+
+ /**
+ * Returns false until the current gesture exceeds the touch slop threshold,
+ * and returns true thereafter (we reset on the subsequent back gesture).
+ * The moment it switches from false -> true is important,
+ * because that's when we switch state, from GONE -> ENTRY.
+ * @return whether the current gesture has moved past a minimum threshold.
+ */
+ private fun dragSlopExceeded(curX: Float, startX: Float): Boolean {
+ if (hasPassedDragSlop) return true
+
+ if (abs(curX - startX) > viewConfiguration.scaledTouchSlop) {
+ // Reset the arrow to the side
+ updateArrowState(GestureState.ENTRY)
+
+ windowManager.updateViewLayout(mView, layoutParams)
+ mView.startTrackingShowBackArrowLatency()
+
+ hasPassedDragSlop = true
+ }
+ return hasPassedDragSlop
+ }
+
+ private fun updateArrowStateOnMove(yTranslation: Float, xTranslation: Float) {
+ if (!currentState.isInteractive())
+ return
+
+ when (currentState) {
+ // Check if we should transition from ENTRY to ACTIVE
+ GestureState.ENTRY ->
+ if (xTranslation > params.swipeTriggerThreshold) {
+ updateArrowState(GestureState.ACTIVE)
+ }
+
+ // Abort if we had continuous motion toward the edge for a while, OR the direction
+ // in Y is bigger than X * 2
+ GestureState.ACTIVE ->
+ if ((totalTouchDelta < 0 && -totalTouchDelta > params.minDeltaForSwitch) ||
+ (yTranslation > xTranslation * 2)
+ ) {
+ updateArrowState(GestureState.INACTIVE)
+ }
+
+ // Re-activate if we had continuous motion away from the edge for a while
+ GestureState.INACTIVE ->
+ if (totalTouchDelta > 0 && totalTouchDelta > params.minDeltaForSwitch) {
+ updateArrowState(GestureState.ACTIVE)
+ }
+
+ // By default assume the current direction is kept
+ else -> {}
+ }
+ }
+
+ private fun handleMoveEvent(event: MotionEvent) {
+ if (!currentState.isInteractive())
+ return
+
+ val x = event.x
+ val y = event.y
+
+ val yOffset = y - startY
+
+ // How far in the y direction we are from the original touch
+ val yTranslation = abs(yOffset)
+
+ // How far in the x direction we are from the original touch ignoring motion that
+ // occurs between the screen edge and the touch start.
+ val xTranslation = max(0f, if (mView.isLeftPanel) x - startX else startX - x)
+
+ // Compared to last time, how far we moved in the x direction. If <0, we are moving closer
+ // to the edge. If >0, we are moving further from the edge
+ val xDelta = xTranslation - previousXTranslation
+ previousXTranslation = xTranslation
+
+ if (abs(xDelta) > 0) {
+ if (sign(xDelta) == sign(totalTouchDelta)) {
+ // Direction has NOT changed, so keep counting the delta
+ totalTouchDelta += xDelta
+ } else {
+ // Direction has changed, so reset the delta
+ totalTouchDelta = xDelta
+ }
+ }
+
+ updateArrowStateOnMove(yTranslation, xTranslation)
+ when (currentState) {
+ GestureState.ACTIVE ->
+ stretchActiveBackIndicator(fullScreenStretchProgress(xTranslation))
+ GestureState.ENTRY ->
+ stretchEntryBackIndicator(preThresholdStretchProgress(xTranslation))
+ GestureState.INACTIVE ->
+ mView.resetStretch()
+ }
+
+ // set y translation
+ setVerticalTranslation(yOffset)
+ }
+
+ private fun setVerticalTranslation(yOffset: Float) {
+ val yTranslation = abs(yOffset)
+ val maxYOffset = (mView.height - params.entryIndicator.backgroundDimens.height) / 2f
+ val yProgress = saturate(yTranslation / (maxYOffset * RUBBER_BAND_AMOUNT))
+ mView.animateVertically(
+ RUBBER_BAND_INTERPOLATOR.getInterpolation(yProgress) * maxYOffset *
+ sign(yOffset)
+ )
+ }
+
+ /**
+ * @return the relative position of the drag from the time after the arrow is activated until
+ * the arrow is fully stretched (between 0.0 - 1.0f)
+ */
+ private fun fullScreenStretchProgress(xTranslation: Float): Float {
+ return saturate(
+ (xTranslation - params.swipeTriggerThreshold) /
+ (fullyStretchedThreshold - params.swipeTriggerThreshold)
+ )
+ }
+
+ /**
+ * Tracks the relative position of the drag from the entry until the threshold where the arrow
+ * activates (between 0.0 - 1.0f)
+ */
+ private fun preThresholdStretchProgress(xTranslation: Float): Float {
+ return saturate(xTranslation / params.swipeTriggerThreshold)
+ }
+
+ private fun stretchActiveBackIndicator(progress: Float) {
+ val rubberBandIterpolation = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress)
+ mView.setStretch(
+ horizontalTranslationStretchAmount = rubberBandIterpolation,
+ arrowStretchAmount = rubberBandIterpolation,
+ backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR_SLOW.getInterpolation(progress),
+ params.fullyStretchedIndicator
+ )
+ }
+
+ private fun stretchEntryBackIndicator(progress: Float) {
+ mView.setStretch(
+ horizontalTranslationStretchAmount = 0f,
+ arrowStretchAmount = RUBBER_BAND_INTERPOLATOR.getInterpolation(progress),
+ backgroundWidthStretchAmount = DECELERATE_INTERPOLATOR.getInterpolation(progress),
+ params.preThresholdIndicator
+ )
+ }
+
+ override fun onDestroy() {
+ cancelFailsafe()
+ windowManager.removeView(mView)
+ }
+
+ override fun setIsLeftPanel(isLeftPanel: Boolean) {
+ mView.isLeftPanel = isLeftPanel
+ layoutParams.gravity = if (isLeftPanel) {
+ Gravity.LEFT or Gravity.TOP
+ } else {
+ Gravity.RIGHT or Gravity.TOP
+ }
+ }
+
+ override fun setInsets(insetLeft: Int, insetRight: Int) {
+ }
+
+ override fun setBackCallback(callback: NavigationEdgeBackPlugin.BackCallback) {
+ backCallback = callback
+ }
+
+ override fun setLayoutParams(layoutParams: WindowManager.LayoutParams) {
+ this.layoutParams = layoutParams
+ windowManager.addView(mView, layoutParams)
+ }
+
+ private fun isFlung() = velocityTracker!!.run {
+ computeCurrentVelocity(1000)
+ abs(xVelocity) > MIN_FLING_VELOCITY
+ }
+
+ private fun playFlingBackAnimation() {
+ playAnimation(setCommittedEndListener)
+ }
+
+ private fun playCommitBackAnimation() {
+ // Check if we should vibrate again
+ if (previousState != GestureState.FLUNG) {
+ backCallback.triggerBack()
+ velocityTracker!!.computeCurrentVelocity(1000)
+ val isSlow = abs(velocityTracker!!.xVelocity) < 500
+ val hasNotVibratedRecently =
+ SystemClock.uptimeMillis() - vibrationTime >= GESTURE_DURATION_FOR_CLICK_MS
+ if (isSlow || hasNotVibratedRecently) {
+ vibratorHelper.vibrate(VibrationEffect.EFFECT_CLICK)
+ }
+ }
+ playAnimation(setGoneEndListener)
+ }
+
+ private fun playCancelBackAnimation() {
+ backCallback.cancelBack()
+ playAnimation(setGoneEndListener)
+ }
+
+ /**
+ * @return true if the animation is running, false otherwise. Some transitions don't animate
+ */
+ private fun playAnimation(endListener: DelayedOnAnimationEndListener) {
+ updateRestingArrowDimens(animated = true, currentState)
+
+ if (!mView.addEndListener(endListener)) {
+ scheduleFailsafe()
+ }
+ }
+
+ private fun resetOnDown() {
+ hasPassedDragSlop = false
+ hasHapticPlayed = false
+ totalTouchDelta = 0f
+ vibrationTime = 0
+ cancelFailsafe()
+ }
+
+ private fun updateYPosition(touchY: Float) {
+ var yPosition = touchY - params.fingerOffset
+ yPosition = max(yPosition, params.minArrowYPosition.toFloat())
+ yPosition -= layoutParams.height / 2.0f
+ layoutParams.y = constrain(yPosition.toInt(), 0, displaySize.y)
+ }
+
+ override fun setDisplaySize(displaySize: Point) {
+ this.displaySize.set(displaySize.x, displaySize.y)
+ fullyStretchedThreshold = min(displaySize.x.toFloat(), params.swipeProgressThreshold)
+ }
+
+ /**
+ * Updates resting arrow and background size not accounting for stretch
+ */
+ private fun updateRestingArrowDimens(animated: Boolean, currentState: GestureState) {
+ if (animated) {
+ when (currentState) {
+ GestureState.ENTRY, GestureState.ACTIVE, GestureState.FLUNG ->
+ mView.setArrowStiffness(ARROW_APPEAR_STIFFNESS, ARROW_APPEAR_DAMPING_RATIO)
+ GestureState.CANCELLED -> mView.fadeOut()
+ else ->
+ mView.setArrowStiffness(
+ ARROW_DISAPPEAR_STIFFNESS,
+ ARROW_DISAPPEAR_DAMPING_RATIO
+ )
+ }
+ }
+ mView.setRestingDimens(
+ restingParams = EdgePanelParams.BackIndicatorDimens(
+ horizontalTranslation = when (currentState) {
+ GestureState.GONE -> -params.activeIndicator.backgroundDimens.width
+ // Position the committed arrow slightly further off the screen so we do not
+ // see part of it bouncing
+ GestureState.COMMITTED ->
+ -params.activeIndicator.backgroundDimens.width * 1.5f
+ GestureState.FLUNG -> params.fullyStretchedIndicator.horizontalTranslation
+ GestureState.ACTIVE -> params.activeIndicator.horizontalTranslation
+ GestureState.ENTRY, GestureState.INACTIVE, GestureState.CANCELLED ->
+ params.entryIndicator.horizontalTranslation
+ },
+ arrowDimens = when (currentState) {
+ GestureState.ACTIVE, GestureState.INACTIVE,
+ GestureState.COMMITTED, GestureState.FLUNG -> params.activeIndicator.arrowDimens
+ GestureState.CANCELLED -> params.cancelledArrowDimens
+ GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.arrowDimens
+ },
+ backgroundDimens = when (currentState) {
+ GestureState.GONE, GestureState.ENTRY -> params.entryIndicator.backgroundDimens
+ else ->
+ params.activeIndicator.backgroundDimens.copy(
+ edgeCornerRadius =
+ if (currentState == GestureState.INACTIVE ||
+ currentState == GestureState.CANCELLED
+ )
+ params.cancelledEdgeCornerRadius
+ else
+ params.activeIndicator.backgroundDimens.edgeCornerRadius
+ )
+ }
+ ),
+ animate = animated
+ )
+ }
+
+ /**
+ * Update arrow state. If state has not changed, this is a no-op.
+ *
+ * Transitioning to active/inactive will indicate whether or not releasing touch will trigger
+ * the back action.
+ */
+ private fun updateArrowState(newState: GestureState, force: Boolean = false) {
+ if (!force && currentState == newState) return
+
+ if (DEBUG) Log.d(TAG, "updateArrowState $currentState -> $newState")
+ previousState = currentState
+ currentState = newState
+ if (currentState == GestureState.GONE) {
+ mView.cancelAlphaAnimations()
+ mView.visibility = View.GONE
+ } else {
+ mView.visibility = View.VISIBLE
+ }
+
+ when (currentState) {
+ // Transitioning to GONE never animates since the arrow is (presumably) already off the
+ // screen
+ GestureState.GONE -> updateRestingArrowDimens(animated = false, currentState)
+ GestureState.ENTRY -> {
+ updateYPosition(startY)
+ updateRestingArrowDimens(animated = true, currentState)
+ }
+ GestureState.ACTIVE -> {
+ updateRestingArrowDimens(animated = true, currentState)
+ // Vibrate the first time we transition to ACTIVE
+ if (!hasHapticPlayed) {
+ hasHapticPlayed = true
+ vibrationTime = SystemClock.uptimeMillis()
+ vibratorHelper.vibrate(VibrationEffect.EFFECT_TICK)
+ }
+ }
+ GestureState.INACTIVE -> {
+ updateRestingArrowDimens(animated = true, currentState)
+ }
+ GestureState.FLUNG -> playFlingBackAnimation()
+ GestureState.COMMITTED -> playCommitBackAnimation()
+ GestureState.CANCELLED -> playCancelBackAnimation()
+ }
+ }
+
+ private fun scheduleFailsafe() {
+ if (!ENABLE_FAILSAFE) return
+ cancelFailsafe()
+ if (DEBUG) Log.d(TAG, "scheduleFailsafe")
+ mainHandler.postDelayed(failsafeRunnable, FAILSAFE_DELAY_MS)
+ }
+
+ private fun cancelFailsafe() {
+ if (DEBUG) Log.d(TAG, "cancelFailsafe")
+ mainHandler.removeCallbacks(failsafeRunnable)
+ }
+
+ private fun onFailsafe() {
+ if (DEBUG) Log.d(TAG, "onFailsafe")
+ updateArrowState(GestureState.GONE, force = true)
+ }
+
+ override fun dump(pw: PrintWriter) {
+ pw.println("$TAG:")
+ pw.println(" currentState=$currentState")
+ pw.println(" isLeftPanel=$mView.isLeftPanel")
+ }
+
+ init {
+ if (DEBUG) mView.drawDebugInfo = { canvas ->
+ val debugStrings = listOf(
+ "$currentState",
+ "startX=$startX",
+ "startY=$startY",
+ "xDelta=${"%.1f".format(totalTouchDelta)}",
+ "xTranslation=${"%.1f".format(previousXTranslation)}",
+ "pre=${"%.0f".format(preThresholdStretchProgress(previousXTranslation) * 100)}%",
+ "post=${"%.0f".format(fullScreenStretchProgress(previousXTranslation) * 100)}%"
+ )
+ val debugPaint = Paint().apply {
+ color = Color.WHITE
+ }
+ val debugInfoBottom = debugStrings.size * 32f + 4f
+ canvas.drawRect(
+ 4f,
+ 4f,
+ canvas.width.toFloat(),
+ debugStrings.size * 32f + 4f,
+ debugPaint
+ )
+ debugPaint.apply {
+ color = Color.BLACK
+ textSize = 32f
+ }
+ var offset = 32f
+ for (debugText in debugStrings) {
+ canvas.drawText(debugText, 10f, offset, debugPaint)
+ offset += 32f
+ }
+ debugPaint.apply {
+ color = Color.RED
+ style = Paint.Style.STROKE
+ strokeWidth = 4f
+ }
+ val canvasWidth = canvas.width.toFloat()
+ val canvasHeight = canvas.height.toFloat()
+ canvas.drawRect(0f, 0f, canvasWidth, canvasHeight, debugPaint)
+
+ fun drawVerticalLine(x: Float, color: Int) {
+ debugPaint.color = color
+ val x = if (mView.isLeftPanel) x else canvasWidth - x
+ canvas.drawLine(x, debugInfoBottom, x, canvas.height.toFloat(), debugPaint)
+ }
+
+ drawVerticalLine(x = params.swipeTriggerThreshold, color = Color.BLUE)
+ drawVerticalLine(x = startX, color = Color.GREEN)
+ drawVerticalLine(x = previousXTranslation, color = Color.DKGRAY)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index ea41fe74f798..057ed2439df3 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -53,15 +53,16 @@ import android.view.MotionEvent;
import android.view.Surface;
import android.view.ViewConfiguration;
import android.view.WindowManager;
-import android.view.WindowMetrics;
+import android.window.BackEvent;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.internal.util.LatencyTracker;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
import com.android.systemui.model.SysUiState;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
@@ -82,15 +83,19 @@ import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.EdgeBackGestureHandlerProto;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
import com.android.wm.shell.back.BackAnimation;
+import com.android.wm.shell.pip.Pip;
import java.io.PrintWriter;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Utility class to handle edge swipes for back gesture
@@ -128,7 +133,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
@Override
public void onPrioritizedRotation(@Surface.Rotation int rotation) {
mStartingQuickstepRotation = rotation;
- updateDisabledForQuickstep(mContext.getResources().getConfiguration());
+ updateDisabledForQuickstep(mLastReportedConfig);
}
};
@@ -145,16 +150,6 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mPackageName = "_UNKNOWN";
}
}
-
- @Override
- public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
- mIsInPipMode = true;
- }
-
- @Override
- public void onActivityUnpinned() {
- mIsInPipMode = false;
- }
};
private DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
@@ -182,10 +177,13 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final PluginManager mPluginManager;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
+ private final BackPanelController.Factory mBackPanelControllerFactory;
private final ViewConfiguration mViewConfiguration;
private final WindowManager mWindowManager;
private final IWindowManager mWindowManagerService;
+ private final Optional<Pip> mPipOptional;
private final FalsingManager mFalsingManager;
+ private final Configuration mLastReportedConfig = new Configuration();
// Activities which should not trigger Back gesture.
private final List<ComponentName> mGestureBlockingActivities = new ArrayList<>();
@@ -199,6 +197,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final Region mExcludeRegion = new Region();
private final Region mUnrestrictedExcludeRegion = new Region();
private final LatencyTracker mLatencyTracker;
+ private final Provider<BackGestureTfClassifierProvider>
+ mBackGestureTfClassifierProviderProvider;
+ private final FeatureFlags mFeatureFlags;
// The left side edge width where touch down is allowed
private int mEdgeWidthLeft;
@@ -208,12 +209,18 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private float mBottomGestureHeight;
// The slop to distinguish between horizontal and vertical motion
private float mTouchSlop;
+ // The threshold for triggering back
+ private float mBackSwipeTriggerThreshold;
+ // The threshold for back swipe full progress.
+ private float mBackSwipeProgressThreshold;
// Duration after which we consider the event as longpress.
private final int mLongPressTimeout;
private int mStartingQuickstepRotation = -1;
// We temporarily disable back gesture when user is quickswitching
// between apps of different orientations
private boolean mDisabledForQuickstep;
+ // This gets updated when the value of PipTransitionState#isInPip changes.
+ private boolean mIsInPip;
private final PointF mDownPoint = new PointF();
private final PointF mEndPoint = new PointF();
@@ -229,7 +236,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean mIsNavBarShownTransiently;
private boolean mIsBackGestureAllowed;
private boolean mGestureBlockingActivityRunning;
- private boolean mIsInPipMode;
+ private boolean mIsNewBackAffordanceEnabled;
private InputMonitor mInputMonitor;
private InputChannelCompat.InputEventReceiver mInputEventReceiver;
@@ -272,6 +279,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
Log.d(DEBUG_MISSING_GESTURE_TAG, "Triggered back: down="
+ sendDown + ", up=" + sendUp);
}
+ } else {
+ mBackAnimation.setTriggerBack(true);
}
mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
@@ -283,6 +292,9 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
@Override
public void cancelBack() {
+ if (mBackAnimation != null) {
+ mBackAnimation.setTriggerBack(false);
+ }
logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
(int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
@@ -297,13 +309,27 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
}
};
-
- EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
- SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor,
- BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer,
- NavigationModeController navigationModeController, ViewConfiguration viewConfiguration,
- WindowManager windowManager, IWindowManager windowManagerService,
- FalsingManager falsingManager, LatencyTracker latencyTracker) {
+ private final Consumer<Boolean> mOnIsInPipStateChangedListener =
+ (isInPip) -> mIsInPip = isInPip;
+
+ EdgeBackGestureHandler(
+ Context context,
+ OverviewProxyService overviewProxyService,
+ SysUiState sysUiState,
+ PluginManager pluginManager,
+ @Main Executor executor,
+ BroadcastDispatcher broadcastDispatcher,
+ ProtoTracer protoTracer,
+ NavigationModeController navigationModeController,
+ BackPanelController.Factory backPanelControllerFactory,
+ ViewConfiguration viewConfiguration,
+ WindowManager windowManager,
+ IWindowManager windowManagerService,
+ Optional<Pip> pipOptional,
+ FalsingManager falsingManager,
+ LatencyTracker latencyTracker,
+ Provider<BackGestureTfClassifierProvider> backGestureTfClassifierProviderProvider,
+ FeatureFlags featureFlags) {
super(broadcastDispatcher);
mContext = context;
mDisplayId = context.getDisplayId();
@@ -313,11 +339,16 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mPluginManager = pluginManager;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
+ mBackPanelControllerFactory = backPanelControllerFactory;
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
+ mPipOptional = pipOptional;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
+ mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
+ mFeatureFlags = featureFlags;
+ mLastReportedConfig.setTo(mContext.getResources().getConfiguration());
ComponentName recentsComponentName = ComponentName.unflattenFromString(
context.getString(com.android.internal.R.string.config_recentsComponentName));
if (recentsComponentName != null) {
@@ -385,6 +416,11 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
final float backGestureSlop = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.BACK_GESTURE_SLOP_MULTIPLIER, 0.75f);
mTouchSlop = mViewConfiguration.getScaledTouchSlop() * backGestureSlop;
+ mBackSwipeTriggerThreshold = res.getDimension(
+ R.dimen.navigation_edge_action_drag_threshold);
+ mBackSwipeProgressThreshold = res.getDimension(
+ R.dimen.navigation_edge_action_progress_threshold);
+ updateBackAnimationThresholds();
}
public void updateNavigationBarOverlayExcludeRegion(Rect exclude) {
@@ -474,6 +510,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mPluginManager.removePluginListener(this);
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
DeviceConfig.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
+ mPipOptional.ifPresent(pip -> pip.setOnIsInPipStateChangedListener(null));
try {
mWindowManagerService.unregisterSystemGestureExclusionListener(
@@ -491,6 +528,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
mMainExecutor::execute, mOnPropertiesChangedListener);
+ mPipOptional.ifPresent(
+ pip -> pip.setOnIsInPipStateChangedListener(mOnIsInPipStateChangedListener));
try {
mWindowManagerService.registerSystemGestureExclusionListener(
@@ -507,8 +546,8 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
Choreographer.getInstance(), this::onInputEvent);
// Add a nav bar panel window
- setEdgeBackPlugin(
- new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
+ mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE);
+ resetEdgeBackPlugin();
mPluginManager.addPluginListener(
this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false);
}
@@ -523,7 +562,17 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
@Override
public void onPluginDisconnected(NavigationEdgeBackPlugin plugin) {
- setEdgeBackPlugin(new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
+ resetEdgeBackPlugin();
+ }
+
+ private void resetEdgeBackPlugin() {
+ if (mIsNewBackAffordanceEnabled) {
+ setEdgeBackPlugin(
+ mBackPanelControllerFactory.create(mContext, mBackAnimation));
+ } else {
+ setEdgeBackPlugin(
+ new NavigationBarEdgePanel(mContext, mBackAnimation, mLatencyTracker));
+ }
}
private void setEdgeBackPlugin(NavigationEdgeBackPlugin edgeBackPlugin) {
@@ -583,10 +632,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
}
if (newState) {
- String mlModelName = DeviceConfig.getString(DeviceConfig.NAMESPACE_SYSTEMUI,
- SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_NAME, "backgesture");
- mBackGestureTfClassifierProvider = SystemUIFactory.getInstance()
- .createBackGestureTfClassifierProvider(mContext.getAssets(), mlModelName);
+ mBackGestureTfClassifierProvider = mBackGestureTfClassifierProviderProvider.get();
mMLModelThreshold = DeviceConfig.getFloat(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.BACK_GESTURE_ML_MODEL_THRESHOLD, 0.9f);
if (mBackGestureTfClassifierProvider.isActive()) {
@@ -653,7 +699,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private boolean isWithinTouchRegion(int x, int y) {
// If the point is inside the PiP or Nav bar overlay excluded bounds, then ignore the back
// gesture
- final boolean isInsidePip = mIsInPipMode && mPipExcludedBounds.contains(x, y);
+ final boolean isInsidePip = mIsInPip && mPipExcludedBounds.contains(x, y);
if (isInsidePip || mNavBarOverlayExcludedBounds.contains(x, y)) {
return false;
}
@@ -717,6 +763,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
MotionEvent cancelEv = MotionEvent.obtain(ev);
cancelEv.setAction(MotionEvent.ACTION_CANCEL);
mEdgeBackPlugin.onMotionEvent(cancelEv);
+ dispatchToBackAnimation(cancelEv);
cancelEv.recycle();
}
@@ -763,6 +810,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
if (mAllowGesture) {
mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
mEdgeBackPlugin.onMotionEvent(ev);
+ dispatchToBackAnimation(ev);
}
if (mLogGesture) {
mDownPoint.set(ev.getX(), ev.getY());
@@ -836,12 +884,23 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
if (mAllowGesture) {
// forward touch
mEdgeBackPlugin.onMotionEvent(ev);
+ dispatchToBackAnimation(ev);
}
}
mProtoTracer.scheduleFrameUpdate();
}
+ private void dispatchToBackAnimation(MotionEvent event) {
+ if (mBackAnimation != null) {
+ mBackAnimation.onBackMotion(
+ event.getX(),
+ event.getY(),
+ event.getActionMasked(),
+ mIsOnLeftEdge ? BackEvent.EDGE_LEFT : BackEvent.EDGE_RIGHT);
+ }
+ }
+
private void updateDisabledForQuickstep(Configuration newConfig) {
int rotation = newConfig.windowConfiguration.getRotation();
mDisabledForQuickstep = mStartingQuickstepRotation > -1 &&
@@ -856,12 +915,12 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
if (DEBUG_MISSING_GESTURE) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "Config changed: config=" + newConfig);
}
+ mLastReportedConfig.updateFrom(newConfig);
updateDisplaySize();
}
private void updateDisplaySize() {
- WindowMetrics metrics = mWindowManager.getMaximumWindowMetrics();
- Rect bounds = metrics.getBounds();
+ Rect bounds = mLastReportedConfig.windowConfiguration.getMaxBounds();
mDisplaySize.set(bounds.width(), bounds.height());
if (DEBUG_MISSING_GESTURE) {
Log.d(DEBUG_MISSING_GESTURE_TAG, "Update display size: mDisplaySize=" + mDisplaySize);
@@ -869,6 +928,16 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
if (mEdgeBackPlugin != null) {
mEdgeBackPlugin.setDisplaySize(mDisplaySize);
}
+ updateBackAnimationThresholds();
+ }
+
+ private void updateBackAnimationThresholds() {
+ if (mBackAnimation == null) {
+ return;
+ }
+ mBackAnimation.setSwipeThresholds(
+ mBackSwipeTriggerThreshold,
+ Math.min(mDisplaySize.x, mBackSwipeProgressThreshold));
}
private boolean sendEvent(int action, int code) {
@@ -906,7 +975,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
pw.println(" mInRejectedExclusion=" + mInRejectedExclusion);
pw.println(" mExcludeRegion=" + mExcludeRegion);
pw.println(" mUnrestrictedExcludeRegion=" + mUnrestrictedExcludeRegion);
- pw.println(" mIsInPipMode=" + mIsInPipMode);
+ pw.println(" mIsInPip=" + mIsInPip);
pw.println(" mPipExcludedBounds=" + mPipExcludedBounds);
pw.println(" mNavBarOverlayExcludedBounds=" + mNavBarOverlayExcludedBounds);
pw.println(" mEdgeWidthLeft=" + mEdgeWidthLeft);
@@ -948,9 +1017,7 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
public void setBackAnimation(BackAnimation backAnimation) {
mBackAnimation = backAnimation;
- if (mEdgeBackPlugin != null && mEdgeBackPlugin instanceof NavigationBarEdgePanel) {
- ((NavigationBarEdgePanel) mEdgeBackPlugin).setBackAnimation(backAnimation);
- }
+ updateBackAnimationThresholds();
}
/**
@@ -967,20 +1034,35 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
private final BroadcastDispatcher mBroadcastDispatcher;
private final ProtoTracer mProtoTracer;
private final NavigationModeController mNavigationModeController;
+ private final BackPanelController.Factory mBackPanelControllerFactory;
private final ViewConfiguration mViewConfiguration;
private final WindowManager mWindowManager;
private final IWindowManager mWindowManagerService;
+ private final Optional<Pip> mPipOptional;
private final FalsingManager mFalsingManager;
private final LatencyTracker mLatencyTracker;
+ private final Provider<BackGestureTfClassifierProvider>
+ mBackGestureTfClassifierProviderProvider;
+ private final FeatureFlags mFeatureFlags;
@Inject
public Factory(OverviewProxyService overviewProxyService,
- SysUiState sysUiState, PluginManager pluginManager, @Main Executor executor,
- BroadcastDispatcher broadcastDispatcher, ProtoTracer protoTracer,
- NavigationModeController navigationModeController,
- ViewConfiguration viewConfiguration, WindowManager windowManager,
- IWindowManager windowManagerService, FalsingManager falsingManager,
- LatencyTracker latencyTracker) {
+ SysUiState sysUiState,
+ PluginManager pluginManager,
+ @Main Executor executor,
+ BroadcastDispatcher broadcastDispatcher,
+ ProtoTracer protoTracer,
+ NavigationModeController navigationModeController,
+ BackPanelController.Factory backPanelControllerFactory,
+ ViewConfiguration viewConfiguration,
+ WindowManager windowManager,
+ IWindowManager windowManagerService,
+ Optional<Pip> pipOptional,
+ FalsingManager falsingManager,
+ LatencyTracker latencyTracker,
+ Provider<BackGestureTfClassifierProvider>
+ backGestureTfClassifierProviderProvider,
+ FeatureFlags featureFlags) {
mOverviewProxyService = overviewProxyService;
mSysUiState = sysUiState;
mPluginManager = pluginManager;
@@ -988,19 +1070,37 @@ public class EdgeBackGestureHandler extends CurrentUserTracker
mBroadcastDispatcher = broadcastDispatcher;
mProtoTracer = protoTracer;
mNavigationModeController = navigationModeController;
+ mBackPanelControllerFactory = backPanelControllerFactory;
mViewConfiguration = viewConfiguration;
mWindowManager = windowManager;
mWindowManagerService = windowManagerService;
+ mPipOptional = pipOptional;
mFalsingManager = falsingManager;
mLatencyTracker = latencyTracker;
+ mBackGestureTfClassifierProviderProvider = backGestureTfClassifierProviderProvider;
+ mFeatureFlags = featureFlags;
}
/** Construct a {@link EdgeBackGestureHandler}. */
public EdgeBackGestureHandler create(Context context) {
- return new EdgeBackGestureHandler(context, mOverviewProxyService, mSysUiState,
- mPluginManager, mExecutor, mBroadcastDispatcher, mProtoTracer,
- mNavigationModeController, mViewConfiguration, mWindowManager,
- mWindowManagerService, mFalsingManager, mLatencyTracker);
+ return new EdgeBackGestureHandler(
+ context,
+ mOverviewProxyService,
+ mSysUiState,
+ mPluginManager,
+ mExecutor,
+ mBroadcastDispatcher,
+ mProtoTracer,
+ mNavigationModeController,
+ mBackPanelControllerFactory,
+ mViewConfiguration,
+ mWindowManager,
+ mWindowManagerService,
+ mPipOptional,
+ mFalsingManager,
+ mLatencyTracker,
+ mBackGestureTfClassifierProviderProvider,
+ mFeatureFlags);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
new file mode 100644
index 000000000000..d56537b694da
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt
@@ -0,0 +1,144 @@
+package com.android.systemui.navigationbar.gestural
+
+import android.content.res.Resources
+import com.android.systemui.R
+
+data class EdgePanelParams(private var resources: Resources) {
+
+ data class ArrowDimens(
+ val length: Float = 0f,
+ val height: Float = 0f
+ )
+
+ data class BackgroundDimens(
+ val width: Float = 0f,
+ val height: Float = 0f,
+ val edgeCornerRadius: Float = 0f,
+ val farCornerRadius: Float = 0f
+ )
+
+ data class BackIndicatorDimens(
+ val horizontalTranslation: Float = 0f,
+ val arrowDimens: ArrowDimens = ArrowDimens(),
+ val backgroundDimens: BackgroundDimens = BackgroundDimens()
+ )
+
+ var arrowThickness: Float = 0f
+ private set
+ var entryIndicator = BackIndicatorDimens()
+ private set
+ var activeIndicator = BackIndicatorDimens()
+ private set
+ var preThresholdIndicator = BackIndicatorDimens()
+ private set
+ var fullyStretchedIndicator = BackIndicatorDimens()
+ private set
+ var cancelledEdgeCornerRadius: Float = 0f
+ private set
+ var cancelledArrowDimens = ArrowDimens()
+
+ // navigation bar edge constants
+ var arrowPaddingEnd: Int = 0
+ private set
+
+ // The closest to y
+ var minArrowYPosition: Int = 0
+ private set
+ var fingerOffset: Int = 0
+ private set
+ var swipeTriggerThreshold: Float = 0f
+ private set
+ var swipeProgressThreshold: Float = 0f
+ private set
+
+ // The minimum delta needed to change direction / stop triggering back
+ var minDeltaForSwitch: Int = 0
+ private set
+
+ init {
+ update(resources)
+ }
+
+ private fun getDimen(id: Int): Float {
+ return resources.getDimension(id)
+ }
+
+ private fun getPx(id: Int): Int {
+ return resources.getDimensionPixelSize(id)
+ }
+
+ fun update(resources: Resources) {
+ this.resources = resources
+ arrowThickness = getDimen(R.dimen.navigation_edge_arrow_thickness)
+ arrowPaddingEnd = getPx(R.dimen.navigation_edge_panel_padding)
+ minArrowYPosition = getPx(R.dimen.navigation_edge_arrow_min_y)
+ fingerOffset = getPx(R.dimen.navigation_edge_finger_offset)
+ swipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_drag_threshold)
+ swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold)
+ minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch)
+
+ entryIndicator = BackIndicatorDimens(
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_entry_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_entry_arrow_height),
+ ),
+ backgroundDimens = BackgroundDimens(
+ width = getDimen(R.dimen.navigation_edge_entry_background_width),
+ height = getDimen(R.dimen.navigation_edge_entry_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_entry_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_entry_far_corners)
+ )
+ )
+
+ activeIndicator = BackIndicatorDimens(
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_active_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_active_arrow_height),
+ ),
+ backgroundDimens = BackgroundDimens(
+ width = getDimen(R.dimen.navigation_edge_active_background_width),
+ height = getDimen(R.dimen.navigation_edge_active_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners)
+
+ )
+ )
+
+ preThresholdIndicator = BackIndicatorDimens(
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_pre_threshold_margin),
+ arrowDimens = ArrowDimens(
+ length = entryIndicator.arrowDimens.length,
+ height = entryIndicator.arrowDimens.height,
+ ),
+ backgroundDimens = BackgroundDimens(
+ width = getDimen(R.dimen.navigation_edge_pre_threshold_background_width),
+ height = getDimen(R.dimen.navigation_edge_pre_threshold_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_pre_threshold_far_corners)
+ )
+ )
+
+ fullyStretchedIndicator = BackIndicatorDimens(
+ horizontalTranslation = getDimen(R.dimen.navigation_edge_stretch_margin),
+ arrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_stretched_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_stretched_arrow_height),
+ ),
+ backgroundDimens = BackgroundDimens(
+ width = getDimen(R.dimen.navigation_edge_stretch_background_width),
+ height = getDimen(R.dimen.navigation_edge_stretch_background_height),
+ edgeCornerRadius = getDimen(R.dimen.navigation_edge_stretch_edge_corners),
+ farCornerRadius = getDimen(R.dimen.navigation_edge_stretch_far_corners)
+ )
+ )
+
+ cancelledEdgeCornerRadius = getDimen(R.dimen.navigation_edge_cancelled_edge_corners)
+
+ cancelledArrowDimens = ArrowDimens(
+ length = getDimen(R.dimen.navigation_edge_cancelled_arrow_length),
+ height = getDimen(R.dimen.navigation_edge_cancelled_arrow_height)
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/GestureModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/GestureModule.java
new file mode 100644
index 000000000000..f98496d43730
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/GestureModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.navigationbar.gestural;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ *
+ */
+@Module
+public interface GestureModule {
+ /** */
+ @Provides
+ static BackGestureTfClassifierProvider providsBackGestureTfClassifierProvider() {
+ return new BackGestureTfClassifierProvider();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt
new file mode 100644
index 000000000000..dd35445f9b7b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people
+
+import com.android.systemui.people.data.repository.PeopleTileRepository
+import com.android.systemui.people.data.repository.PeopleTileRepositoryImpl
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import com.android.systemui.people.data.repository.PeopleWidgetRepositoryImpl
+import dagger.Binds
+import dagger.Module
+
+/** Dagger module to provide/bind people space dependencies. */
+@Module
+interface PeopleModule {
+ @Binds fun bindTileRepository(impl: PeopleTileRepositoryImpl): PeopleTileRepository
+
+ @Binds fun bindWidgetRepository(impl: PeopleWidgetRepositoryImpl): PeopleWidgetRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
index b55d86e8fb1c..0ba077eb0912 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleProvider.java
@@ -29,7 +29,8 @@ import android.os.UserHandle;
import android.util.Log;
import android.widget.RemoteViews;
-import com.android.systemui.SystemUIAppComponentFactory;
+import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback;
+import com.android.systemui.SystemUIAppComponentFactoryBase.ContextInitializer;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.shared.system.PeopleProviderUtils;
@@ -37,11 +38,11 @@ import javax.inject.Inject;
/** API that returns a People Tile preview. */
public class PeopleProvider extends ContentProvider implements
- SystemUIAppComponentFactory.ContextInitializer {
+ ContextInitializer {
private static final String TAG = "PeopleProvider";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
private static final String EMPTY_STRING = "";
- private SystemUIAppComponentFactory.ContextAvailableCallback mCallback;
+ private ContextAvailableCallback mCallback;
@Inject
PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
@@ -144,7 +145,7 @@ public class PeopleProvider extends ContentProvider implements
@Override
public void setContextAvailableCallback(
- SystemUIAppComponentFactory.ContextAvailableCallback callback) {
+ ContextAvailableCallback callback) {
mCallback = callback;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
index 93a3f81fdd6b..7cc95a158a14 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java
@@ -19,169 +19,63 @@ package com.android.systemui.people;
import static android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID;
import static android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID;
-import static com.android.systemui.people.PeopleTileViewHelper.getPersonIconBitmap;
-import static com.android.systemui.people.PeopleTileViewHelper.getSizeInDp;
-
-import android.app.Activity;
-import android.app.people.PeopleSpaceTile;
-import android.content.Context;
import android.content.Intent;
-import android.content.res.TypedArray;
-import android.graphics.Color;
-import android.graphics.Outline;
-import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.util.Log;
-import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewOutlineProvider;
-import android.widget.LinearLayout;
-import com.android.systemui.R;
-import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
-import com.android.systemui.people.widget.PeopleTileKey;
+import androidx.activity.ComponentActivity;
+import androidx.lifecycle.ViewModelProvider;
-import java.util.ArrayList;
-import java.util.List;
+import com.android.systemui.people.ui.view.PeopleViewBinder;
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel;
import javax.inject.Inject;
/** People Tile Widget configuration activity that shows the user their conversation tiles. */
-public class PeopleSpaceActivity extends Activity {
+public class PeopleSpaceActivity extends ComponentActivity {
private static final String TAG = "PeopleSpaceActivity";
private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
- private PeopleSpaceWidgetManager mPeopleSpaceWidgetManager;
- private Context mContext;
- private int mAppWidgetId;
+ private final PeopleViewModel.Factory mViewModelFactory;
@Inject
- public PeopleSpaceActivity(PeopleSpaceWidgetManager peopleSpaceWidgetManager) {
+ public PeopleSpaceActivity(PeopleViewModel.Factory viewModelFactory) {
super();
- mPeopleSpaceWidgetManager = peopleSpaceWidgetManager;
-
+ mViewModelFactory = viewModelFactory;
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mContext = getApplicationContext();
- mAppWidgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID,
- INVALID_APPWIDGET_ID);
setResult(RESULT_CANCELED);
- }
-
- /** Builds the conversation selection activity. */
- private void buildActivity() {
- List<PeopleSpaceTile> priorityTiles = new ArrayList<>();
- List<PeopleSpaceTile> recentTiles = new ArrayList<>();
- try {
- priorityTiles = mPeopleSpaceWidgetManager.getPriorityTiles();
- recentTiles = mPeopleSpaceWidgetManager.getRecentTiles();
- } catch (Exception e) {
- Log.e(TAG, "Couldn't retrieve conversations", e);
- }
-
- // If no conversations, render activity without conversations
- if (recentTiles.isEmpty() && priorityTiles.isEmpty()) {
- setContentView(R.layout.people_space_activity_no_conversations);
-
- // The Tile preview has colorBackground as its background. Change it so it's different
- // than the activity's background.
- LinearLayout item = findViewById(android.R.id.background);
- GradientDrawable shape = (GradientDrawable) item.getBackground();
- final TypedArray ta = mContext.getTheme().obtainStyledAttributes(
- new int[]{com.android.internal.R.attr.colorSurface});
- shape.setColor(ta.getColor(0, Color.WHITE));
- return;
- }
- setContentView(R.layout.people_space_activity);
- setTileViews(R.id.priority, R.id.priority_tiles, priorityTiles);
- setTileViews(R.id.recent, R.id.recent_tiles, recentTiles);
- }
+ PeopleViewModel viewModel = new ViewModelProvider(this, mViewModelFactory).get(
+ PeopleViewModel.class);
- private ViewOutlineProvider mViewOutlineProvider = new ViewOutlineProvider() {
- @Override
- public void getOutline(View view, Outline outline) {
- outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(),
- mContext.getResources().getDimension(R.dimen.people_space_widget_radius));
- }
- };
+ // Update the widget ID coming from the intent.
+ int widgetId = getIntent().getIntExtra(EXTRA_APPWIDGET_ID, INVALID_APPWIDGET_ID);
+ viewModel.onWidgetIdChanged(widgetId);
- /** Sets a {@link PeopleSpaceTileView}s for each conversation. */
- private void setTileViews(int viewId, int tilesId, List<PeopleSpaceTile> tiles) {
- if (tiles.isEmpty()) {
- LinearLayout view = findViewById(viewId);
- view.setVisibility(View.GONE);
- return;
- }
-
- ViewGroup layout = findViewById(tilesId);
- layout.setClipToOutline(true);
- layout.setOutlineProvider(mViewOutlineProvider);
- for (int i = 0; i < tiles.size(); ++i) {
- PeopleSpaceTile tile = tiles.get(i);
- PeopleSpaceTileView tileView = new PeopleSpaceTileView(mContext,
- layout, tile.getId(), i == (tiles.size() - 1));
- setTileView(tileView, tile);
- }
+ ViewGroup view = PeopleViewBinder.create(this);
+ PeopleViewBinder.bind(view, viewModel, /* lifecycleOwner= */ this,
+ (result) -> {
+ finishActivity(result);
+ return null;
+ });
+ setContentView(view);
}
- /** Sets {@code tileView} with the data in {@code conversation}. */
- private void setTileView(PeopleSpaceTileView tileView, PeopleSpaceTile tile) {
- try {
- if (tile.getUserName() != null) {
- tileView.setName(tile.getUserName().toString());
- }
- tileView.setPersonIcon(getPersonIconBitmap(mContext, tile,
- getSizeInDp(mContext, R.dimen.avatar_size_for_medium,
- mContext.getResources().getDisplayMetrics().density)));
-
- PeopleTileKey key = new PeopleTileKey(tile);
- tileView.setOnClickListener(v -> storeWidgetConfiguration(tile, key));
- } catch (Exception e) {
- Log.e(TAG, "Couldn't retrieve shortcut information", e);
+ private void finishActivity(PeopleViewModel.Result result) {
+ if (result instanceof PeopleViewModel.Result.Success) {
+ if (DEBUG) Log.d(TAG, "Widget added!");
+ Intent data = ((PeopleViewModel.Result.Success) result).getData();
+ setResult(RESULT_OK, data);
+ } else {
+ if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
+ setResult(RESULT_CANCELED);
}
- }
-
- /** Stores the user selected configuration for {@code mAppWidgetId}. */
- private void storeWidgetConfiguration(PeopleSpaceTile tile, PeopleTileKey key) {
- if (PeopleSpaceUtils.DEBUG) {
- if (DEBUG) {
- Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: "
- + tile.getId() + " for widget ID: "
- + mAppWidgetId);
- }
- }
- mPeopleSpaceWidgetManager.addNewWidget(mAppWidgetId, key);
- finishActivity();
- }
-
- /** Finish activity with a successful widget configuration result. */
- private void finishActivity() {
- if (DEBUG) Log.d(TAG, "Widget added!");
- setActivityResult(RESULT_OK);
finish();
}
-
- /** Finish activity without choosing a widget. */
- public void dismissActivity(View v) {
- if (DEBUG) Log.d(TAG, "Activity dismissed with no widgets added!");
- finish();
- }
-
- private void setActivityResult(int result) {
- Intent resultValue = new Intent();
- resultValue.putExtra(EXTRA_APPWIDGET_ID, mAppWidgetId);
- setResult(result, resultValue);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- // Refresh tile views to sync new conversations.
- buildActivity();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
index 4ee951f3cdb1..58e700f81388 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleStoryIconFactory.java
@@ -28,6 +28,7 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.IconDrawableFactory;
import android.util.Log;
+import android.view.ContextThemeWrapper;
import androidx.core.graphics.drawable.RoundedBitmapDrawable;
@@ -52,16 +53,15 @@ class PeopleStoryIconFactory implements AutoCloseable {
PeopleStoryIconFactory(Context context, PackageManager pm,
IconDrawableFactory iconDrawableFactory, int iconSizeDp) {
- context.setTheme(android.R.style.Theme_DeviceDefault_DayNight);
- mIconBitmapSize = (int) (iconSizeDp * context.getResources().getDisplayMetrics().density);
- mDensity = context.getResources().getDisplayMetrics().density;
+ mContext = new ContextThemeWrapper(context, android.R.style.Theme_DeviceDefault_DayNight);
+ mIconBitmapSize = (int) (iconSizeDp * mContext.getResources().getDisplayMetrics().density);
+ mDensity = mContext.getResources().getDisplayMetrics().density;
mIconSize = mDensity * iconSizeDp;
mPackageManager = pm;
mIconDrawableFactory = iconDrawableFactory;
- mImportantConversationColor = context.getColor(R.color.important_conversation);
- mAccentColor = Utils.getColorAttr(context,
+ mImportantConversationColor = mContext.getColor(R.color.important_conversation);
+ mAccentColor = Utils.getColorAttr(mContext,
com.android.internal.R.attr.colorAccentPrimaryVariant).getDefaultColor();
- mContext = context;
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
index 00aa1381ace1..be82b1faac8e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java
@@ -75,6 +75,7 @@ import androidx.core.math.MathUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
+import com.android.systemui.people.data.model.PeopleTileModel;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.people.widget.PeopleTileKey;
@@ -299,7 +300,8 @@ public class PeopleTileViewHelper {
return createLastInteractionRemoteViews();
}
- private static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
+ /** Whether the conversation associated with {@code tile} can bypass DND. */
+ public static boolean isDndBlockingTileData(@Nullable PeopleSpaceTile tile) {
if (tile == null) return false;
int notificationPolicyState = tile.getNotificationPolicyState();
@@ -536,7 +538,8 @@ public class PeopleTileViewHelper {
return views;
}
- private static boolean getHasNewStory(PeopleSpaceTile tile) {
+ /** Whether {@code tile} has a new story. */
+ public static boolean getHasNewStory(PeopleSpaceTile tile) {
return tile.getStatuses() != null && tile.getStatuses().stream().anyMatch(
c -> c.getActivity() == ACTIVITY_NEW_STORY);
}
@@ -1250,16 +1253,24 @@ public class PeopleTileViewHelper {
}
/** Returns a bitmap with the user icon and package icon. */
- public static Bitmap getPersonIconBitmap(Context context, PeopleSpaceTile tile,
+ public static Bitmap getPersonIconBitmap(Context context, PeopleTileModel tile,
int maxAvatarSize) {
- boolean hasNewStory = getHasNewStory(tile);
- return getPersonIconBitmap(context, tile, maxAvatarSize, hasNewStory);
+ return getPersonIconBitmap(context, maxAvatarSize, tile.getHasNewStory(),
+ tile.getUserIcon(), tile.getKey().getPackageName(), tile.getKey().getUserId(),
+ tile.isImportant(), tile.isDndBlocking());
}
/** Returns a bitmap with the user icon and package icon. */
private static Bitmap getPersonIconBitmap(
Context context, PeopleSpaceTile tile, int maxAvatarSize, boolean hasNewStory) {
- Icon icon = tile.getUserIcon();
+ return getPersonIconBitmap(context, maxAvatarSize, hasNewStory, tile.getUserIcon(),
+ tile.getPackageName(), getUserId(tile),
+ tile.isImportantConversation(), isDndBlockingTileData(tile));
+ }
+
+ private static Bitmap getPersonIconBitmap(
+ Context context, int maxAvatarSize, boolean hasNewStory, Icon icon, String packageName,
+ int userId, boolean importantConversation, boolean dndBlockingTileData) {
if (icon == null) {
Drawable placeholder = context.getDrawable(R.drawable.ic_avatar_with_badge).mutate();
placeholder.setColorFilter(getDisabledColorFilter());
@@ -1272,10 +1283,10 @@ public class PeopleTileViewHelper {
RoundedBitmapDrawable roundedDrawable = RoundedBitmapDrawableFactory.create(
context.getResources(), icon.getBitmap());
Drawable personDrawable = storyIcon.getPeopleTileDrawable(roundedDrawable,
- tile.getPackageName(), getUserId(tile), tile.isImportantConversation(),
+ packageName, userId, importantConversation,
hasNewStory);
- if (isDndBlockingTileData(tile)) {
+ if (dndBlockingTileData) {
personDrawable.setColorFilter(getDisabledColorFilter());
}
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt
new file mode 100644
index 000000000000..5d8539fabc6b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/model/PeopleTileModel.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.model
+
+import android.graphics.drawable.Icon
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** Models a tile/conversation. */
+data class PeopleTileModel(
+ val key: PeopleTileKey,
+ val username: String,
+ val userIcon: Icon,
+ val hasNewStory: Boolean,
+ val isImportant: Boolean,
+ val isDndBlocking: Boolean,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt
new file mode 100644
index 000000000000..01b43d52130b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleTileRepository.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.repository
+
+import android.app.people.PeopleSpaceTile
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.PeopleTileViewHelper
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.people.widget.PeopleTileKey
+import javax.inject.Inject
+
+/** A Repository to fetch the current tiles/conversations. */
+// TODO(b/238993727): Make the tiles API reactive.
+interface PeopleTileRepository {
+ /* The current priority tiles. */
+ fun priorityTiles(): List<PeopleTileModel>
+
+ /* The current recent tiles. */
+ fun recentTiles(): List<PeopleTileModel>
+}
+
+@SysUISingleton
+class PeopleTileRepositoryImpl
+@Inject
+constructor(
+ private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+) : PeopleTileRepository {
+ override fun priorityTiles(): List<PeopleTileModel> {
+ return peopleSpaceWidgetManager.priorityTiles.map { it.toModel() }
+ }
+
+ override fun recentTiles(): List<PeopleTileModel> {
+ return peopleSpaceWidgetManager.recentTiles.map { it.toModel() }
+ }
+
+ private fun PeopleSpaceTile.toModel(): PeopleTileModel {
+ return PeopleTileModel(
+ PeopleTileKey(this),
+ userName.toString(),
+ userIcon,
+ PeopleTileViewHelper.getHasNewStory(this),
+ isImportantConversation,
+ PeopleTileViewHelper.isDndBlockingTileData(this),
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt
new file mode 100644
index 000000000000..f2b6cb1fc3f5
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/data/repository/PeopleWidgetRepository.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.people.widget.PeopleSpaceWidgetManager
+import com.android.systemui.people.widget.PeopleTileKey
+import javax.inject.Inject
+
+interface PeopleWidgetRepository {
+ /**
+ * Bind the widget with ID [widgetId] to the tile keyed by [tileKey].
+ *
+ * If there is already a widget with [widgetId], this existing widget will be reconfigured and
+ * associated to this tile. If there is no widget with [widgetId], a new one will be created.
+ */
+ fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey)
+}
+
+@SysUISingleton
+class PeopleWidgetRepositoryImpl
+@Inject
+constructor(
+ private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
+) : PeopleWidgetRepository {
+ override fun setWidgetTile(widgetId: Int, tileKey: PeopleTileKey) {
+ peopleSpaceWidgetManager.addNewWidget(widgetId, tileKey)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
new file mode 100644
index 000000000000..d8a429e5bb1a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/view/PeopleViewBinder.kt
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.view
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Outline
+import android.graphics.drawable.GradientDrawable
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.ViewOutlineProvider
+import android.widget.LinearLayout
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.Lifecycle.State.CREATED
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.R
+import com.android.systemui.people.PeopleSpaceTileView
+import com.android.systemui.people.ui.viewmodel.PeopleTileViewModel
+import com.android.systemui.people.ui.viewmodel.PeopleViewModel
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** A ViewBinder for [PeopleViewModel]. */
+object PeopleViewBinder {
+ private const val TAG = "PeopleViewBinder"
+
+ /**
+ * The [ViewOutlineProvider] used to clip the corner radius of the recent and priority lists.
+ */
+ private val ViewOutlineProvider =
+ object : ViewOutlineProvider() {
+ override fun getOutline(view: View, outline: Outline) {
+ outline.setRoundRect(
+ 0,
+ 0,
+ view.width,
+ view.height,
+ view.context.resources.getDimension(R.dimen.people_space_widget_radius),
+ )
+ }
+ }
+
+ /** Create a [View] that can later be [bound][bind] to a [PeopleViewModel]. */
+ @JvmStatic
+ fun create(context: Context): ViewGroup {
+ return LayoutInflater.from(context)
+ .inflate(R.layout.people_space_activity, /* root= */ null) as ViewGroup
+ }
+
+ /** Bind [view] to [viewModel]. */
+ @JvmStatic
+ fun bind(
+ view: ViewGroup,
+ viewModel: PeopleViewModel,
+ lifecycleOwner: LifecycleOwner,
+ onResult: (PeopleViewModel.Result) -> Unit,
+ ) {
+ // Call [onResult] as soon as a result is available.
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(CREATED) {
+ viewModel.result.collect { result ->
+ if (result != null) {
+ viewModel.clearResult()
+ onResult(result)
+ }
+ }
+ }
+ }
+
+ // Start collecting the UI data once the Activity is STARTED.
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ combine(
+ viewModel.priorityTiles,
+ viewModel.recentTiles,
+ ) { priority, recent ->
+ priority to recent
+ }
+ .collect { (priorityTiles, recentTiles) ->
+ if (priorityTiles.isNotEmpty() || recentTiles.isNotEmpty()) {
+ setConversationsContent(
+ view,
+ priorityTiles,
+ recentTiles,
+ viewModel::onTileClicked,
+ )
+ } else {
+ setNoConversationsContent(view, viewModel::onUserJourneyCancelled)
+ }
+ }
+ }
+ }
+
+ // Make sure to refresh the tiles/conversations when the Activity is resumed, so that it
+ // updates them when going back to the Activity after leaving it.
+ lifecycleOwner.lifecycleScope.launch {
+ lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+ viewModel.onTileRefreshRequested()
+ }
+ }
+ }
+
+ private fun setNoConversationsContent(view: ViewGroup, onGotItClicked: () -> Unit) {
+ // This should never happen.
+ if (view.childCount > 1) {
+ error("view has ${view.childCount} children, it should have maximum 1")
+ }
+
+ // The static content for no conversations is already shown.
+ if (view.findViewById<View>(R.id.top_level_no_conversations) != null) {
+ return
+ }
+
+ // If we were showing the content with conversations earlier, remove it.
+ if (view.childCount == 1) {
+ view.removeViewAt(0)
+ }
+
+ val context = view.context
+ val noConversationsView =
+ LayoutInflater.from(context)
+ .inflate(R.layout.people_space_activity_no_conversations, /* root= */ view)
+
+ noConversationsView.findViewById<View>(R.id.got_it_button).setOnClickListener {
+ onGotItClicked()
+ }
+
+ // The Tile preview has colorBackground as its background. Change it so it's different than
+ // the activity's background.
+ val item = noConversationsView.findViewById<LinearLayout>(android.R.id.background)
+ val shape = item.background as GradientDrawable
+ val ta =
+ context.theme.obtainStyledAttributes(
+ intArrayOf(com.android.internal.R.attr.colorSurface)
+ )
+ shape.setColor(ta.getColor(0, Color.WHITE))
+ ta.recycle()
+ }
+
+ private fun setConversationsContent(
+ view: ViewGroup,
+ priorityTiles: List<PeopleTileViewModel>,
+ recentTiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ ) {
+ // This should never happen.
+ if (view.childCount > 1) {
+ error("view has ${view.childCount} children, it should have maximum 1")
+ }
+
+ // Inflate the content with conversations, if it's not already.
+ if (view.findViewById<View>(R.id.top_level_with_conversations) == null) {
+ // If we were showing the content without conversations earlier, remove it.
+ if (view.childCount == 1) {
+ view.removeViewAt(0)
+ }
+
+ LayoutInflater.from(view.context)
+ .inflate(R.layout.people_space_activity_with_conversations, /* root= */ view)
+ }
+
+ // TODO(b/193782241): Replace the NestedScrollView + 2x LinearLayout from this layout into a
+ // single RecyclerView once this screen is tested by screenshot tests. Introduce a
+ // PeopleSpaceTileViewBinder that will properly create and bind the View associated to a
+ // PeopleSpaceTileViewModel (and remove the PeopleSpaceTileView class).
+ val conversationsView = view.requireViewById<View>(R.id.top_level_with_conversations)
+ setTileViews(
+ conversationsView,
+ R.id.priority,
+ R.id.priority_tiles,
+ priorityTiles,
+ onTileClicked,
+ )
+
+ setTileViews(
+ conversationsView,
+ R.id.recent,
+ R.id.recent_tiles,
+ recentTiles,
+ onTileClicked,
+ )
+ }
+
+ /** Sets a [PeopleSpaceTileView]s for each conversation. */
+ private fun setTileViews(
+ root: View,
+ tilesListId: Int,
+ tilesId: Int,
+ tiles: List<PeopleTileViewModel>,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ ) {
+ // Remove any previously added tile.
+ // TODO(b/193782241): Once this list is a big RecyclerView, set the current list and use
+ // DiffUtil to do as less addView/removeView as possible.
+ val layout = root.requireViewById<ViewGroup>(tilesId)
+ layout.removeAllViews()
+ layout.outlineProvider = ViewOutlineProvider
+
+ val tilesListView = root.requireViewById<LinearLayout>(tilesListId)
+ if (tiles.isEmpty()) {
+ tilesListView.visibility = View.GONE
+ return
+ }
+ tilesListView.visibility = View.VISIBLE
+
+ // Add each tile.
+ tiles.forEachIndexed { i, tile ->
+ val tileView =
+ PeopleSpaceTileView(root.context, layout, tile.key.shortcutId, i == tiles.size - 1)
+ bindTileView(tileView, tile, onTileClicked)
+ }
+ }
+
+ /** Sets [tileView] with the data in [conversation]. */
+ private fun bindTileView(
+ tileView: PeopleSpaceTileView,
+ tile: PeopleTileViewModel,
+ onTileClicked: (PeopleTileViewModel) -> Unit,
+ ) {
+ try {
+ tileView.setName(tile.username)
+ tileView.setPersonIcon(tile.icon)
+ tileView.setOnClickListener { onTileClicked(tile) }
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't retrieve shortcut information", e)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
new file mode 100644
index 000000000000..40205ce9424a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleTileViewModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.viewmodel
+
+import android.graphics.Bitmap
+import com.android.systemui.people.widget.PeopleTileKey
+
+/** Models UI state for a single tile/conversation. */
+data class PeopleTileViewModel(
+ val key: PeopleTileKey,
+ val icon: Bitmap,
+ val username: String?,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
new file mode 100644
index 000000000000..0834a5ae75f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/people/ui/viewmodel/PeopleViewModel.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.people.ui.viewmodel
+
+import android.appwidget.AppWidgetManager.EXTRA_APPWIDGET_ID
+import android.appwidget.AppWidgetManager.INVALID_APPWIDGET_ID
+import android.content.Context
+import android.content.Intent
+import android.util.Log
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.android.systemui.R
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.people.PeopleSpaceUtils
+import com.android.systemui.people.PeopleTileViewHelper
+import com.android.systemui.people.data.model.PeopleTileModel
+import com.android.systemui.people.data.repository.PeopleTileRepository
+import com.android.systemui.people.data.repository.PeopleWidgetRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Models UI state for the people space, allowing the user to select which conversation should be
+ * associated to a new or existing Conversation widget.
+ */
+class PeopleViewModel(
+ @Application private val context: Context,
+ private val tileRepository: PeopleTileRepository,
+ private val widgetRepository: PeopleWidgetRepository,
+) : ViewModel() {
+ /**
+ * The list of the priority tiles/conversations.
+ *
+ * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
+ * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
+ */
+ private val _priorityTiles = MutableStateFlow(priorityTiles())
+ val priorityTiles: Flow<List<PeopleTileViewModel>> = _priorityTiles.asStateFlow()
+
+ /**
+ * The list of the priority tiles/conversations.
+ *
+ * Important: Even though this is a Flow, the underlying API used to populate this Flow is not
+ * reactive and you have to manually call [onTileRefreshRequested] to refresh the tiles.
+ */
+ private val _recentTiles = MutableStateFlow(recentTiles())
+ val recentTiles: Flow<List<PeopleTileViewModel>> = _recentTiles.asStateFlow()
+
+ /** The ID of the widget currently being edited/added. */
+ private val _appWidgetId = MutableStateFlow(INVALID_APPWIDGET_ID)
+ val appWidgetId: StateFlow<Int> = _appWidgetId.asStateFlow()
+
+ /** The result of this user journey. */
+ private val _result = MutableStateFlow<Result?>(null)
+ val result: StateFlow<Result?> = _result.asStateFlow()
+
+ /** Refresh the [priorityTiles] and [recentTiles]. */
+ fun onTileRefreshRequested() {
+ _priorityTiles.value = priorityTiles()
+ _recentTiles.value = recentTiles()
+ }
+
+ /** Called when the [appWidgetId] should be changed to [widgetId]. */
+ fun onWidgetIdChanged(widgetId: Int) {
+ _appWidgetId.value = widgetId
+ }
+
+ /** Clear [result], setting it to null. */
+ fun clearResult() {
+ _result.value = null
+ }
+
+ /** Called when a tile is clicked. */
+ fun onTileClicked(tile: PeopleTileViewModel) {
+ val widgetId = _appWidgetId.value
+ if (PeopleSpaceUtils.DEBUG) {
+ Log.d(
+ TAG,
+ "Put ${tile.username}'s shortcut ID: ${tile.key.shortcutId} for widget ID $widgetId"
+ )
+ }
+ widgetRepository.setWidgetTile(widgetId, tile.key)
+ _result.value =
+ Result.Success(Intent().apply { putExtra(EXTRA_APPWIDGET_ID, appWidgetId.value) })
+ }
+
+ /** Called when this user journey is cancelled. */
+ fun onUserJourneyCancelled() {
+ _result.value = Result.Cancelled
+ }
+
+ private fun priorityTiles(): List<PeopleTileViewModel> {
+ return try {
+ tileRepository.priorityTiles().map { it.toViewModel() }
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't retrieve priority conversations", e)
+ emptyList()
+ }
+ }
+
+ private fun recentTiles(): List<PeopleTileViewModel> {
+ return try {
+ tileRepository.recentTiles().map { it.toViewModel() }
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't retrieve recent conversations", e)
+ emptyList()
+ }
+ }
+
+ private fun PeopleTileModel.toViewModel(): PeopleTileViewModel {
+ val icon =
+ PeopleTileViewHelper.getPersonIconBitmap(
+ context,
+ this,
+ PeopleTileViewHelper.getSizeInDp(
+ context,
+ R.dimen.avatar_size_for_medium,
+ context.resources.displayMetrics.density,
+ )
+ )
+ return PeopleTileViewModel(key, icon, username)
+ }
+
+ /** The Factory that should be used to create a [PeopleViewModel]. */
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val tileRepository: PeopleTileRepository,
+ private val widgetRepository: PeopleWidgetRepository,
+ ) : ViewModelProvider.Factory {
+ override fun <T : ViewModel> create(modelClass: Class<T>): T {
+ check(modelClass == PeopleViewModel::class.java)
+ return PeopleViewModel(context, tileRepository, widgetRepository) as T
+ }
+ }
+
+ sealed class Result {
+ class Success(val data: Intent) : Result()
+ object Cancelled : Result()
+ }
+
+ companion object {
+ private const val TAG = "PeopleViewModel"
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
index e1b97a454c5d..20c6c556706e 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/LaunchConversationActivity.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.os.Bundle;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
@@ -34,6 +35,7 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.UiEventLoggerImpl;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.people.PeopleSpaceUtils;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -43,6 +45,7 @@ import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubble;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Inject;
@@ -58,6 +61,7 @@ public class LaunchConversationActivity extends Activity {
private boolean mIsForTesting;
private IStatusBarService mIStatusBarService;
private CommandQueue mCommandQueue;
+ private Executor mBgExecutor;
private Bubble mBubble;
private NotificationEntry mEntryToBubble;
@@ -67,7 +71,8 @@ public class LaunchConversationActivity extends Activity {
CommonNotifCollection commonNotifCollection,
Optional<BubblesManager> bubblesManagerOptional,
UserManager userManager,
- CommandQueue commandQueue
+ CommandQueue commandQueue,
+ @Background Executor bgExecutor
) {
super();
mVisibilityProvider = visibilityProvider;
@@ -91,6 +96,7 @@ public class LaunchConversationActivity extends Activity {
mCommandQueue.removeCallback(this);
}
});
+ mBgExecutor = bgExecutor;
}
@Override
@@ -172,34 +178,36 @@ public class LaunchConversationActivity extends Activity {
return;
}
- try {
- if (mIStatusBarService == null || mCommonNotifCollection == null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
- }
- return;
+ if (mIStatusBarService == null || mCommonNotifCollection == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping clear notification: null services, key: " + notifKey);
}
+ return;
+ }
- NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
- if (entry == null || entry.getRanking() == null) {
- if (DEBUG) {
- Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
- + " is null, key: " + notifKey);
- }
- return;
+ NotificationEntry entry = mCommonNotifCollection.getEntry(notifKey);
+ if (entry == null || entry.getRanking() == null) {
+ if (DEBUG) {
+ Log.d(TAG, "Skipping clear notification: NotificationEntry or its Ranking"
+ + " is null, key: " + notifKey);
}
+ return;
+ }
- NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
- int rank = notifVisibility.rank;
+ NotificationVisibility notifVisibility = mVisibilityProvider.obtain(entry, true);
+ int rank = notifVisibility.rank;
- if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
- mIStatusBarService.onNotificationClear(
- packageName, userHandle.getIdentifier(), notifKey,
- NotificationStats.DISMISSAL_OTHER,
- NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
- } catch (Exception e) {
- Log.e(TAG, "Exception cancelling notification:" + e);
- }
+ if (DEBUG) Log.d(TAG, "Clearing notification, key: " + notifKey + ", rank: " + rank);
+ mBgExecutor.execute(() -> {
+ try {
+ mIStatusBarService.onNotificationClear(
+ packageName, userHandle.getIdentifier(), notifKey,
+ NotificationStats.DISMISSAL_OTHER,
+ NotificationStats.DISMISS_SENTIMENT_POSITIVE, notifVisibility);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Exception cancelling notification:" + e);
+ }
+ });
}
@VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
index 1a7bd8cb6cf9..18be0aa6580d 100644
--- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
+++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java
@@ -289,7 +289,7 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "Updating widget: " + appWidgetId);
PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
if (tile == null) {
- Log.e(TAG, "Matching conversation not found for shortcut ID");
+ Log.e(TAG, "Matching conversation not found for widget " + appWidgetId);
}
updateAppWidgetOptionsAndView(appWidgetId, tile);
widgetIdToTile.put(appWidgetId, tile);
@@ -308,7 +308,7 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
if (!PeopleTileKey.isValid(key)) {
- Log.e(TAG, "Cannot update invalid widget");
+ Log.e(TAG, "Invalid tile key updating widget " + appWidgetId);
return;
}
RemoteViews views = PeopleTileViewHelper.createRemoteViews(mContext, tile, appWidgetId,
@@ -330,7 +330,7 @@ public class PeopleSpaceWidgetManager {
/** Updates tile in app widget options and the current view. */
public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
if (tile == null) {
- if (DEBUG) Log.w(TAG, "Storing null tile");
+ Log.w(TAG, "Storing null tile for widget " + appWidgetId);
}
synchronized (mTiles) {
mTiles.put(appWidgetId, tile);
@@ -348,7 +348,7 @@ public class PeopleSpaceWidgetManager {
try {
return getTileForExistingWidgetThrowing(appWidgetId);
} catch (Exception e) {
- Log.e(TAG, "failed to retrieve tile for widget ID " + appWidgetId, e);
+ Log.e(TAG, "failed to retrieve tile for existing widget " + appWidgetId, e);
return null;
}
}
@@ -388,7 +388,7 @@ public class PeopleSpaceWidgetManager {
boolean supplementFromStorage) throws
PackageManager.NameNotFoundException {
if (!PeopleTileKey.isValid(key)) {
- Log.e(TAG, "PeopleTileKey invalid: " + key.toString());
+ Log.e(TAG, "Invalid tile key finding tile for existing widget " + appWidgetId);
return null;
}
@@ -423,7 +423,7 @@ public class PeopleSpaceWidgetManager {
// Add current state.
return getTileWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
} catch (RemoteException e) {
- Log.e(TAG, "getTileFromPersistentStorage failing", e);
+ Log.e(TAG, "getTileFromPersistentStorage failing for widget " + appWidgetId, e);
return null;
}
}
@@ -591,10 +591,7 @@ public class PeopleSpaceWidgetManager {
if (DEBUG) Log.d(TAG, "Augmenting tile for existing widget: " + widgetId);
PeopleSpaceTile tile = getTileForExistingWidget(widgetId);
if (tile == null) {
- if (DEBUG) {
- Log.w(TAG, "Widget: " + widgetId
- + ". Null tile for existing widget, skipping update.");
- }
+ Log.w(TAG, "Null tile for existing widget " + widgetId + ", skipping update.");
return Optional.empty();
}
String contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null);
@@ -816,7 +813,7 @@ public class PeopleSpaceWidgetManager {
tile = getTileFromPersistentStorage(key, appWidgetId, /* supplementFromStorage= */
false);
} catch (PackageManager.NameNotFoundException e) {
- Log.e(TAG, "Cannot add widget since app was uninstalled");
+ Log.e(TAG, "Cannot add widget " + appWidgetId + " since app was uninstalled");
return;
}
if (tile == null) {
@@ -851,7 +848,7 @@ public class PeopleSpaceWidgetManager {
Collections.singletonList(tile.getId()),
tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
} catch (Exception e) {
- Log.w(TAG, "failed to cache shortcut", e);
+ Log.w(TAG, "failed to cache shortcut for widget " + appWidgetId, e);
}
PeopleSpaceTile finalTile = tile;
mBgExecutor.execute(
@@ -862,7 +859,7 @@ public class PeopleSpaceWidgetManager {
public void registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key) {
// Retrieve storage needed for registration.
if (!PeopleTileKey.isValid(key)) {
- if (DEBUG) Log.w(TAG, "Could not register listener for widget: " + widgetId);
+ Log.w(TAG, "Invalid tile key registering listener for widget " + widgetId);
return;
}
TileConversationListener newListener = new TileConversationListener();
@@ -911,7 +908,7 @@ public class PeopleSpaceWidgetManager {
widgetSp.getInt(USER_ID, INVALID_USER_ID),
widgetSp.getString(PACKAGE_NAME, null));
if (!PeopleTileKey.isValid(key)) {
- if (DEBUG) Log.e(TAG, "Could not delete " + widgetId);
+ Log.e(TAG, "Invalid tile key trying to remove widget " + widgetId);
return;
}
storedWidgetIdsForKey = new HashSet<>(
@@ -1083,7 +1080,8 @@ public class PeopleSpaceWidgetManager {
synchronized (mLock) {
existingTile = getTileForExistingWidgetThrowing(appWidgetId);
if (existingTile == null) {
- Log.e(TAG, "Matching conversation not found for shortcut ID");
+ Log.e(TAG, "Matching conversation not found for widget "
+ + appWidgetId);
continue;
}
updatedTile = getTileWithCurrentState(existingTile, entryPoint);
@@ -1091,7 +1089,7 @@ public class PeopleSpaceWidgetManager {
}
} catch (PackageManager.NameNotFoundException e) {
// Delete data for uninstalled widgets.
- Log.e(TAG, "package no longer found for tile", e);
+ Log.e(TAG, "Package no longer found for widget " + appWidgetId, e);
JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
if (jobScheduler != null
&& jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index 05e8c021e3f5..90fc1d7ed49e 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -55,6 +55,7 @@ import android.view.WindowManager;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
import com.android.settingslib.Utils;
@@ -62,6 +63,7 @@ import com.android.settingslib.fuelgauge.BatterySaverUtils;
import com.android.settingslib.utils.PowerUtil;
import com.android.systemui.R;
import com.android.systemui.SystemUIApplication;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
@@ -93,6 +95,8 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
private static final String TAG_TEMPERATURE = "high_temp";
private static final String TAG_AUTO_SAVER = "auto_saver";
+ private static final String INTERACTION_JANK_TAG = "start_power_saver";
+
private static final int SHOWING_NOTHING = 0;
private static final int SHOWING_WARNING = 1;
private static final int SHOWING_INVALID_CHARGER = 3;
@@ -707,7 +711,9 @@ public class PowerNotificationWarnings implements PowerUI.WarningsUI {
});
WeakReference<View> ref = mBatteryControllerLazy.get().getLastPowerSaverStartView();
if (ref != null && ref.get() != null && ref.get().isAggregatedVisible()) {
- mDialogLaunchAnimator.showFromView(d, ref.get());
+ mDialogLaunchAnimator.showFromView(d, ref.get(),
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
} else {
d.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 6f05852f3e03..0288c9fce64a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -45,16 +45,18 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_ENABLED
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.TASK_MANAGER_SHOW_FOOTER_DOT
+import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.Dumpable
import com.android.systemui.R
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.dump.DumpManager
-import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.system.SysUiStatsLog
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.util.DeviceConfigProxy
import com.android.systemui.util.indentIfPossible
@@ -81,6 +83,7 @@ class FgsManagerController @Inject constructor(
) : IForegroundServiceObserver.Stub(), Dumpable {
companion object {
+ private const val INTERACTION_JANK_TAG = "active_background_apps"
private val LOG_TAG = FgsManagerController::class.java.simpleName
private const val DEFAULT_TASK_MANAGER_ENABLED = true
private const val DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT = false
@@ -153,31 +156,38 @@ class FgsManagerController @Inject constructor(
currentProfileIds.addAll(userTracker.userProfiles.map { it.id })
- deviceConfigProxy.addOnPropertiesChangedListener(NAMESPACE_SYSTEMUI,
- backgroundExecutor) {
+ deviceConfigProxy.addOnPropertiesChangedListener(
+ NAMESPACE_SYSTEMUI,
+ backgroundExecutor
+ ) {
isAvailable = it.getBoolean(TASK_MANAGER_ENABLED, isAvailable)
showFooterDot =
- it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
+ it.getBoolean(TASK_MANAGER_SHOW_FOOTER_DOT, showFooterDot)
}
- isAvailable = deviceConfigProxy.getBoolean(NAMESPACE_SYSTEMUI,
- TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED)
- showFooterDot = deviceConfigProxy.getBoolean(NAMESPACE_SYSTEMUI,
- TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT)
+ isAvailable = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_ENABLED, DEFAULT_TASK_MANAGER_ENABLED
+ )
+ showFooterDot = deviceConfigProxy.getBoolean(
+ NAMESPACE_SYSTEMUI,
+ TASK_MANAGER_SHOW_FOOTER_DOT, DEFAULT_TASK_MANAGER_SHOW_FOOTER_DOT
+ )
dumpManager.registerDumpable(this)
broadcastDispatcher.registerReceiver(
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- if (intent.action == Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER) {
- showDialog(null)
- }
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER) {
+ showDialog(null)
}
- },
- IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER),
- executor = mainExecutor,
- flags = Context.RECEIVER_NOT_EXPORTED)
+ }
+ },
+ IntentFilter(Intent.ACTION_SHOW_FOREGROUND_SERVICE_MANAGER),
+ executor = mainExecutor,
+ flags = Context.RECEIVER_NOT_EXPORTED
+ )
initialized = true
}
@@ -193,10 +203,12 @@ class FgsManagerController @Inject constructor(
val userPackageKey = UserPackage(userId, packageName)
if (isForeground) {
runningServiceTokens.getOrPut(userPackageKey) { StartTimeAndTokens(systemClock) }
- .addToken(token)
+ .addToken(token)
} else {
if (runningServiceTokens[userPackageKey]?.also {
- it.removeToken(token) }?.isEmpty() == true) {
+ it.removeToken(token)
+ }?.isEmpty() == true
+ ) {
runningServiceTokens.remove(userPackageKey)
}
}
@@ -209,7 +221,7 @@ class FgsManagerController @Inject constructor(
@GuardedBy("lock")
val onNumberOfPackagesChangedListeners: MutableSet<OnNumberOfPackagesChangedListener> =
- mutableSetOf()
+ mutableSetOf()
@GuardedBy("lock")
val onDialogDismissedListeners: MutableSet<OnDialogDismissedListener> = mutableSetOf()
@@ -284,7 +296,7 @@ class FgsManagerController @Inject constructor(
recyclerView.adapter = appListAdapter
val topSpacing = dialogContext.resources
- .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing)
+ .getDimensionPixelSize(R.dimen.fgs_manager_list_top_spacing)
dialog.setView(recyclerView, 0, topSpacing, 0, 0)
this.dialog = dialog
@@ -302,7 +314,15 @@ class FgsManagerController @Inject constructor(
mainExecutor.execute {
viewLaunchedFrom
- ?.let { dialogLaunchAnimator.showFromView(dialog, it) } ?: dialog.show()
+ ?.let {
+ dialogLaunchAnimator.showFromView(
+ dialog, it,
+ cuj = DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
+ } ?: dialog.show()
}
backgroundExecutor.execute {
@@ -329,11 +349,14 @@ class FgsManagerController @Inject constructor(
addedPackages.forEach {
val ai = packageManager.getApplicationInfoAsUser(it.packageName, 0, it.userId)
- runningApps[it] = RunningApp(it.userId, it.packageName,
- runningServiceTokens[it]!!.startTime, it.uiControl,
- packageManager.getApplicationLabel(ai),
- packageManager.getUserBadgedIcon(
- packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)))
+ runningApps[it] = RunningApp(
+ it.userId, it.packageName,
+ runningServiceTokens[it]!!.startTime, it.uiControl,
+ packageManager.getApplicationLabel(ai),
+ packageManager.getUserBadgedIcon(
+ packageManager.getApplicationIcon(ai), UserHandle.of(it.userId)
+ )
+ )
logEvent(stopped = false, it.packageName, it.userId, runningApps[it]!!.timeStarted)
}
@@ -349,7 +372,7 @@ class FgsManagerController @Inject constructor(
mainExecutor.execute {
appListAdapter
- .setData(runningApps.values.toList().sortedByDescending { it.timeStarted })
+ .setData(runningApps.values.toList().sortedByDescending { it.timeStarted })
}
}
@@ -367,8 +390,10 @@ class FgsManagerController @Inject constructor(
}
backgroundExecutor.execute {
val uid = packageManager.getPackageUidAsUser(packageName, userId)
- SysUiStatsLog.write(SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event,
- timeLogged - timeStarted)
+ SysUiStatsLog.write(
+ SysUiStatsLog.TASK_MANAGER_EVENT_REPORTED, uid, event,
+ timeLogged - timeStarted
+ )
}
}
@@ -379,8 +404,10 @@ class FgsManagerController @Inject constructor(
private var data: List<RunningApp> = listOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppItemViewHolder {
- return AppItemViewHolder(LayoutInflater.from(parent.context)
- .inflate(R.layout.fgs_manager_app_item, parent, false))
+ return AppItemViewHolder(
+ LayoutInflater.from(parent.context)
+ .inflate(R.layout.fgs_manager_app_item, parent, false)
+ )
}
override fun onBindViewHolder(holder: AppItemViewHolder, position: Int) {
@@ -392,8 +419,9 @@ class FgsManagerController @Inject constructor(
iconView.setImageDrawable(runningApp.icon)
appLabelView.text = runningApp.appLabel
durationView.text = DateUtils.formatDuration(
- max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
- DateUtils.LENGTH_MEDIUM)
+ max(systemClock.elapsedRealtime() - runningApp.timeStarted, 60000),
+ DateUtils.LENGTH_MEDIUM
+ )
stopButton.setOnClickListener {
stopButton.setText(R.string.fgs_manager_app_item_stop_button_stopped_label)
stopPackage(runningApp.userId, runningApp.packageName, runningApp.timeStarted)
@@ -431,12 +459,12 @@ class FgsManagerController @Inject constructor(
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int):
- Boolean {
+ Boolean {
return oldData[oldItemPosition] == newData[newItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int):
- Boolean {
+ Boolean {
return oldData[oldItemPosition].stopped == newData[newItemPosition].stopped
}
}).dispatchUpdatesTo(this)
@@ -462,7 +490,7 @@ class FgsManagerController @Inject constructor(
fun updateUiControl() {
backgroundRestrictionExemptionReason =
- activityManager.getBackgroundRestrictionExemptionReason(uid)
+ activityManager.getBackgroundRestrictionExemptionReason(uid)
uiControl = when (backgroundRestrictionExemptionReason) {
PowerExemptionManager.REASON_SYSTEM_UID,
PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY
@@ -523,8 +551,10 @@ class FgsManagerController @Inject constructor(
fun dump(pw: PrintWriter) {
pw.println("StartTimeAndTokens: [")
pw.indentIfPossible {
- pw.println("startTime=$startTime (time running =" +
- " ${systemClock.elapsedRealtime() - startTime}ms)")
+ pw.println(
+ "startTime=$startTime (time running =" +
+ " ${systemClock.elapsedRealtime() - startTime}ms)"
+ )
pw.println("tokens: [")
pw.indentIfPossible {
for (token in tokens) {
@@ -572,8 +602,10 @@ class FgsManagerController @Inject constructor(
pw.indentIfPossible {
pw.println("userId=$userId")
pw.println("packageName=$packageName")
- pw.println("timeStarted=$timeStarted (time since start =" +
- " ${systemClock.elapsedRealtime() - timeStarted}ms)")
+ pw.println(
+ "timeStarted=$timeStarted (time since start =" +
+ " ${systemClock.elapsedRealtime() - timeStarted}ms)"
+ )
pw.println("uiControl=$uiControl")
pw.println("appLabel=$appLabel")
pw.println("icon=$icon")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index ce50ddff7b0f..0697133a02f9 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -15,6 +15,7 @@ import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.animation.Interpolator;
import android.view.animation.OvershootInterpolator;
@@ -283,6 +284,7 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
.inflate(R.layout.qs_paged_page, this, false);
page.setMinRows(mMinRows);
page.setMaxColumns(mMaxColumns);
+ page.setSelected(false);
return page;
}
@@ -625,6 +627,16 @@ public class PagedTileLayout extends ViewPager implements QSTileLayout {
}
}
+ @Override
+ public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(event);
+ if (mAdapter != null && mAdapter.getCount() > 0) {
+ event.setItemCount(mAdapter.getCount());
+ event.setFromIndex(getCurrentPageNumber());
+ event.setToIndex(getCurrentPageNumber());
+ }
+ }
+
private static Animator setupBounceAnimator(View view, int ordinal) {
view.setAlpha(0f);
view.setScaleX(0f);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
index ba6b1dd28c97..875493d73f9e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFgsManagerFooter.java
@@ -17,6 +17,7 @@
package com.android.systemui.qs;
import static com.android.systemui.qs.dagger.QSFragmentModule.QS_FGS_MANAGER_FOOTER_VIEW;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.content.Context;
import android.view.View;
@@ -141,8 +142,8 @@ public class QSFgsManagerFooter implements View.OnClickListener,
public void handleRefreshState() {
mMainExecutor.execute(() -> {
- CharSequence text = mContext.getResources().getQuantityString(
- R.plurals.fgs_manager_footer_label, mNumPackages, mNumPackages);
+ CharSequence text = icuMessageFormat(mContext.getResources(),
+ R.string.fgs_manager_footer_label, mNumPackages);
mFooterText.setText(text);
mNumberView.setText(Integer.toString(mNumPackages));
mNumberView.setContentDescription(text);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
index d03a2e55d628..8c5e6cc63147 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragment.java
@@ -57,7 +57,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.policy.BrightnessMirrorController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
import com.android.systemui.util.LifecycleFragment;
-import com.android.systemui.util.Utils;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -624,7 +623,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (mQSAnimator != null) {
mQSAnimator.setPosition(expansion);
}
- updateMediaPositions();
}
private void setAlphaAnimationProgress(float progress) {
@@ -644,10 +642,11 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
if (mLastQSExpansion == 1.0f) {
// Fully expanded, let's set the layout bounds as clip bounds. This is necessary because
// it's a scrollview and otherwise wouldn't be clipped. However, we set the horizontal
- // bounds so the pages go to the ends of QSContainerImpl
- ViewGroup.MarginLayoutParams lp =
- (ViewGroup.MarginLayoutParams) mQSPanelScrollView.getLayoutParams();
- mQsBounds.set(-lp.leftMargin, 0, mQSPanelScrollView.getWidth() + lp.rightMargin,
+ // bounds so the pages go to the ends of QSContainerImpl (most cases) or its parent
+ // (large screen portrait)
+ int sideMargin = getResources().getDimensionPixelSize(
+ R.dimen.qs_tiles_page_horizontal_margin) * 2;
+ mQsBounds.set(-sideMargin, 0, mQSPanelScrollView.getWidth() + sideMargin,
mQSPanelScrollView.getHeight());
}
mQSPanelScrollView.setClipBounds(mQsBounds);
@@ -660,54 +659,6 @@ public class QSFragment extends LifecycleFragment implements QS, CommandQueue.Ca
- mQSPanelScrollView.getPaddingBottom());
}
- private void updateMediaPositions() {
- if (Utils.useQsMediaPlayer(getContext())) {
- mContainer.getLocationOnScreen(mTmpLocation);
- float absoluteBottomPosition = mTmpLocation[1] + mContainer.getHeight();
- // The Media can be scrolled off screen by default, let's offset it
- float expandedMediaPosition = absoluteBottomPosition - mQSPanelScrollView.getScrollY()
- + mQSPanelScrollView.getScrollRange();
- pinToBottom(expandedMediaPosition, mQsMediaHost, true /* expanded */);
- // The expanded media host should never move above the laid out position
- pinToBottom(absoluteBottomPosition, mQqsMediaHost, false /* expanded */);
- }
- }
-
- private void pinToBottom(float absoluteBottomPosition, MediaHost mediaHost, boolean expanded) {
- View hostView = mediaHost.getHostView();
- // On keyguard we cross-fade to expanded, so no need to pin it.
- // If the collapsed qs isn't visible, we also just keep it at the laid out position.
- if (mLastQSExpansion > 0 && !isKeyguardState() && mQqsMediaHost.getVisible()) {
- float targetPosition = absoluteBottomPosition - getTotalBottomMargin(hostView)
- - hostView.getHeight();
- float currentPosition = mediaHost.getCurrentBounds().top
- - hostView.getTranslationY();
- float translationY = targetPosition - currentPosition;
- if (expanded) {
- // Never go below the laid out position. This is necessary since the qs panel can
- // change in height and we don't want to ever go below it's position
- translationY = Math.min(translationY, 0);
- } else {
- translationY = Math.max(translationY, 0);
- }
- hostView.setTranslationY(translationY);
- } else {
- hostView.setTranslationY(0);
- }
- }
-
- private float getTotalBottomMargin(View startView) {
- int result = 0;
- View child = startView;
- View parent = (View) startView.getParent();
- while (!(parent instanceof QSContainerImpl) && parent != null) {
- result += parent.getHeight() - child.getBottom();
- child = parent;
- parent = (View) parent.getParent();
- }
- return result;
- }
-
private boolean headerWillBeAnimating() {
return mState == StatusBarState.KEYGUARD && mShowCollapsedOnKeyguard
&& !isKeyguardState();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
index 584de6e8c28f..87fcce455ea6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSSecurityFooter.java
@@ -80,9 +80,11 @@ import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.util.FrameworkStatsLog;
import com.android.systemui.FontSizeUtils;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Background;
@@ -108,6 +110,8 @@ class QSSecurityFooter extends ViewController<View>
protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final boolean DEBUG_FORCE_VISIBLE = false;
+ private static final String INTERACTION_JANK_TAG = "managed_device_info";
+
private final TextView mFooterText;
private final ImageView mPrimaryFooterIcon;
private Context mContext;
@@ -557,7 +561,8 @@ class QSSecurityFooter extends ViewController<View>
mDialog.setView(view);
if (mView.isAggregatedVisible()) {
- mDialogLaunchAnimator.showFromView(mDialog, mView);
+ mDialogLaunchAnimator.showFromView(mDialog, mView, new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
} else {
mDialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
index fbdabc74aba4..4552abd402b0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
@@ -19,8 +19,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Build;
-import android.os.Handler;
-import android.os.Looper;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings.Secure;
@@ -28,6 +26,7 @@ import android.text.TextUtils;
import android.util.ArraySet;
import android.util.Log;
+import androidx.annotation.MainThread;
import androidx.annotation.Nullable;
import com.android.internal.logging.InstanceId;
@@ -35,9 +34,7 @@ import com.android.internal.logging.InstanceIdSequence;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.PluginListener;
@@ -68,12 +65,20 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Optional;
import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.function.Predicate;
import javax.inject.Inject;
import javax.inject.Provider;
-/** Platform implementation of the quick settings tile host **/
+/** Platform implementation of the quick settings tile host
+ *
+ * This class keeps track of the set of current tiles and is the in memory source of truth
+ * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes,
+ * {@link #onTuningChanged} will be called and the tiles will be re-created as needed.
+ *
+ * This class also provides the interface for adding/removing/changing tiles.
+ */
@SysUISingleton
public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, Dumpable {
private static final String TAG = "QSTileHost";
@@ -89,11 +94,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
private final TunerService mTunerService;
private final PluginManager mPluginManager;
private final DumpManager mDumpManager;
- private final BroadcastDispatcher mBroadcastDispatcher;
private final QSLogger mQSLogger;
private final UiEventLogger mUiEventLogger;
private final InstanceIdSequence mInstanceIdSequence;
private final CustomTileStatePersister mCustomTileStatePersister;
+ private final Executor mMainExecutor;
private final List<Callback> mCallbacks = new ArrayList<>();
@Nullable
@@ -113,13 +118,11 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
public QSTileHost(Context context,
StatusBarIconController iconController,
QSFactory defaultFactory,
- @Main Handler mainHandler,
- @Background Looper bgLooper,
+ @Main Executor mainExecutor,
PluginManager pluginManager,
TunerService tunerService,
Provider<AutoTileManager> autoTiles,
DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher,
Optional<CentralSurfaces> centralSurfacesOptional,
QSLogger qsLogger,
UiEventLogger uiEventLogger,
@@ -137,7 +140,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mDumpManager = dumpManager;
mQSLogger = qsLogger;
mUiEventLogger = uiEventLogger;
- mBroadcastDispatcher = broadcastDispatcher;
+ mMainExecutor = mainExecutor;
mTileServiceRequestController = tileServiceRequestControllerBuilder.create(this);
mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
@@ -151,7 +154,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mSecureSettings = secureSettings;
mCustomTileStatePersister = customTileStatePersister;
- mainHandler.post(() -> {
+ mainExecutor.execute(() -> {
// This is technically a hack to avoid circular dependency of
// QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
// finishes before creating any tiles.
@@ -258,6 +261,33 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
return mTileSpecs.indexOf(spec);
}
+ /**
+ * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this
+ * will be called with the new value of the setting.
+ *
+ * This method will do the following:
+ * <ol>
+ * <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li>
+ * <li>Create new tiles for those that don't already exist. If this tiles end up being
+ * not available, they'll also be destroyed.</li>
+ * <li>Save the resolved list of tiles (current tiles that are available) into the setting.
+ * This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs},
+ * and visible tiles ({@link #mTiles}) must match.
+ * </li>
+ * </ol>
+ *
+ * Additionally, if the user has changed, it'll do the following:
+ * <ul>
+ * <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li>
+ * <li>Destroy any {@link CustomTile} and recreate it for the new user.</li>
+ * </ul>
+ *
+ * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches
+ * in main thread.
+ *
+ * @see QSTile#isAvailable
+ */
+ @MainThread
@Override
public void onTuningChanged(String key, String newValue) {
if (!TILES_SETTING.equals(key)) {
@@ -330,34 +360,44 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
mCurrentUser = currentUser;
List<String> currentSpecs = new ArrayList<>(mTileSpecs);
mTileSpecs.clear();
- mTileSpecs.addAll(tileSpecs);
+ mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
mTiles.clear();
mTiles.putAll(newTiles);
if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
// If we didn't manage to create any tiles, set it to empty (default)
Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
- changeTiles(currentSpecs, loadTileSpecs(mContext, ""));
+ changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
} else {
+ String resolvedTiles = TextUtils.join(",", mTileSpecs);
+ if (!resolvedTiles.equals(newValue)) {
+ // If the resolved tiles (those we actually ended up with) are different than
+ // the ones that are in the setting, update the Setting.
+ saveTilesToSettings(mTileSpecs);
+ }
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onTilesChanged();
}
}
}
+ /**
+ * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need
+ * its lifecycle terminated).
+ */
@Override
public void removeTile(String spec) {
- changeTileSpecs(tileSpecs-> tileSpecs.remove(spec));
+ mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
}
/**
* Remove many tiles at once.
*
- * It will only save to settings once (as opposed to {@link QSTileHost#removeTile} called
+ * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called
* multiple times).
*/
@Override
public void removeTiles(Collection<String> specs) {
- changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs));
+ mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
}
@Override
@@ -381,27 +421,29 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
* @param requestPosition -1 for end, 0 for beginning, or X for insertion at position X
*/
public void addTile(String spec, int requestPosition) {
- if (spec.equals("work")) Log.wtfStack(TAG, "Adding work tile");
- changeTileSpecs(tileSpecs -> {
- if (tileSpecs.contains(spec)) return false;
-
- int size = tileSpecs.size();
- if (requestPosition == POSITION_AT_END || requestPosition >= size) {
- tileSpecs.add(spec);
- } else {
- tileSpecs.add(requestPosition, spec);
- }
- return true;
- });
+ mMainExecutor.execute(() ->
+ changeTileSpecs(tileSpecs -> {
+ if (tileSpecs.contains(spec)) return false;
+
+ int size = tileSpecs.size();
+ if (requestPosition == POSITION_AT_END || requestPosition >= size) {
+ tileSpecs.add(spec);
+ } else {
+ tileSpecs.add(requestPosition, spec);
+ }
+ return true;
+ })
+ );
}
- void saveTilesToSettings(List<String> tileSpecs) {
- if (tileSpecs.contains("work")) Log.wtfStack(TAG, "Saving work tile");
+ @MainThread
+ private void saveTilesToSettings(List<String> tileSpecs) {
mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
null /* tag */, false /* default */, mCurrentUser,
true /* overrideable by restore */);
}
+ @MainThread
private void changeTileSpecs(Predicate<List<String>> changeFunction) {
final String setting = mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser);
final List<String> tileSpecs = loadTileSpecs(mContext, setting);
@@ -421,29 +463,32 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, D
*/
public void addTile(ComponentName tile, boolean end) {
String spec = CustomTile.toSpec(tile);
- if (!mTileSpecs.contains(spec)) {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- if (end) {
- newSpecs.add(spec);
- } else {
- newSpecs.add(0, spec);
- }
- changeTiles(mTileSpecs, newSpecs);
- }
+ addTile(spec, end ? POSITION_AT_END : 0);
}
- public void removeTile(ComponentName tile) {
- List<String> newSpecs = new ArrayList<>(mTileSpecs);
- newSpecs.remove(CustomTile.toSpec(tile));
- changeTiles(mTileSpecs, newSpecs);
+ /**
+ * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
+ * removed by a <b>user action</b> like {@code adb}.
+ */
+ public void removeTileByUser(ComponentName tile) {
+ mMainExecutor.execute(() -> {
+ List<String> newSpecs = new ArrayList<>(mTileSpecs);
+ if (newSpecs.remove(CustomTile.toSpec(tile))) {
+ changeTilesByUser(mTileSpecs, newSpecs);
+ }
+ });
}
/**
* Change the tiles triggered by the user editing.
* <p>
* This is not called on device start, or on user change.
+ *
+ * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles
+ * that are removed.
*/
- public void changeTiles(List<String> previousTiles, List<String> newTiles) {
+ @MainThread
+ public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
final List<String> copy = new ArrayList<>(previousTiles);
final int NP = copy.size();
for (int i = 0; i < NP; i++) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index e52bfbd67275..d84b12c714bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -182,7 +182,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
for (int i = 1; i < mTiles.size() && mTiles.get(i) != null; i++) {
newSpecs.add(mTiles.get(i).spec);
}
- host.changeTiles(mCurrentSpecs, newSpecs);
+ host.changeTilesByUser(mCurrentSpecs, newSpecs);
mCurrentSpecs = newSpecs;
}
@@ -200,7 +200,7 @@ public class TileAdapter extends RecyclerView.Adapter<Holder> implements TileSta
/** */
public void resetTileSpecs(List<String> specs) {
// Notify the host so the tiles get removed callbacks.
- mHost.changeTiles(mCurrentSpecs, specs);
+ mHost.changeTilesByUser(mCurrentSpecs, specs);
setTileSpecs(specs);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
index bd2f64bb9874..6265b3c056e7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileRequestDialog.kt
@@ -73,6 +73,7 @@ class TileRequestDialog(
icon = tileData.icon?.loadDrawable(context)?.let {
QSTileImpl.DrawableIcon(it)
} ?: ResourceIcon.get(R.drawable.android)
+ contentDescription = label
}
tile.onStateChanged(state)
tile.post {
@@ -95,4 +96,4 @@ class TileRequestDialog(
val label: CharSequence,
val icon: Icon?
)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
index bf565a8c52e0..cfc57db2eeb8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServiceManager.java
@@ -289,7 +289,7 @@ public class TileServiceManager {
}
}
- mServices.getHost().removeTile(component);
+ mServices.getHost().removeTile(CustomTile.toSpec(component));
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 1488231275c3..a92c7e3c8554 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -39,6 +39,7 @@ import com.android.systemui.qs.tiles.ColorInversionTile;
import com.android.systemui.qs.tiles.DataSaverTile;
import com.android.systemui.qs.tiles.DeviceControlsTile;
import com.android.systemui.qs.tiles.DndTile;
+import com.android.systemui.qs.tiles.DreamTile;
import com.android.systemui.qs.tiles.FlashlightTile;
import com.android.systemui.qs.tiles.HotspotTile;
import com.android.systemui.qs.tiles.InternetTile;
@@ -96,6 +97,7 @@ public class QSFactoryImpl implements QSFactory {
private final Provider<QuickAccessWalletTile> mQuickAccessWalletTileProvider;
private final Provider<QRCodeScannerTile> mQRCodeScannerTileProvider;
private final Provider<OneHandedModeTile> mOneHandedModeTileProvider;
+ private final Provider<DreamTile> mDreamTileProvider;
private final Lazy<QSHost> mQsHostLazy;
private final Provider<CustomTile.Builder> mCustomTileBuilderProvider;
@@ -132,7 +134,8 @@ public class QSFactoryImpl implements QSFactory {
Provider<QuickAccessWalletTile> quickAccessWalletTileProvider,
Provider<QRCodeScannerTile> qrCodeScannerTileProvider,
Provider<OneHandedModeTile> oneHandedModeTileProvider,
- Provider<ColorCorrectionTile> colorCorrectionTileProvider) {
+ Provider<ColorCorrectionTile> colorCorrectionTileProvider,
+ Provider<DreamTile> dreamTileProvider) {
mQsHostLazy = qsHostLazy;
mCustomTileBuilderProvider = customTileBuilderProvider;
@@ -165,6 +168,7 @@ public class QSFactoryImpl implements QSFactory {
mQRCodeScannerTileProvider = qrCodeScannerTileProvider;
mOneHandedModeTileProvider = oneHandedModeTileProvider;
mColorCorrectionTileProvider = colorCorrectionTileProvider;
+ mDreamTileProvider = dreamTileProvider;
}
/** Creates a tile with a type based on {@code tileSpec} */
@@ -238,6 +242,8 @@ public class QSFactoryImpl implements QSFactory {
return mOneHandedModeTileProvider.get();
case "color_correction":
return mColorCorrectionTileProvider.get();
+ case "dream":
+ return mDreamTileProvider.get();
}
// Custom tiles
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
index 59164dea9c45..5147d5934039 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSTileViewImpl.kt
@@ -670,7 +670,8 @@ internal object SubtitleArrayMapping {
"qr_code_scanner" to R.array.tile_states_qr_code_scanner,
"alarm" to R.array.tile_states_alarm,
"onehanded" to R.array.tile_states_onehanded,
- "color_correction" to R.array.tile_states_color_correction
+ "color_correction" to R.array.tile_states_color_correction,
+ "dream" to R.array.tile_states_dream
)
fun getSubtitleId(spec: String?): Int {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
index ccec80db3ea5..86d4fa3002fc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/AirplaneModeTile.java
@@ -56,7 +56,6 @@ import dagger.Lazy;
/** Quick settings tile: Airplane mode **/
public class AirplaneModeTile extends QSTileImpl<BooleanState> {
- private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_airplane);
private final SettingObserver mSetting;
private final BroadcastDispatcher mBroadcastDispatcher;
private final Lazy<ConnectivityManager> mLazyConnectivityManager;
@@ -129,11 +128,8 @@ public class AirplaneModeTile extends QSTileImpl<BooleanState> {
final boolean airplaneMode = value != 0;
state.value = airplaneMode;
state.label = mContext.getString(R.string.airplane_mode);
- state.icon = mIcon;
- if (state.slash == null) {
- state.slash = new SlashState();
- }
- state.slash.isSlashed = !airplaneMode;
+ state.icon = ResourceIcon.get(state.value
+ ? R.drawable.qs_airplane_icon_on : R.drawable.qs_airplane_icon_off);
state.state = airplaneMode ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
state.contentDescription = state.label;
state.expandedAccessibilityClassName = Switch.class.getName();
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
index 1004fcae3827..ee49b294dfcd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatterySaverTile.java
@@ -57,8 +57,6 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
private boolean mCharging;
private boolean mPluggedIn;
- private Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_battery_saver);
-
@Inject
public BatterySaverTile(
QSHost host,
@@ -145,7 +143,9 @@ public class BatterySaverTile extends QSTileImpl<BooleanState> implements
protected void handleUpdateState(BooleanState state, Object arg) {
state.state = mPluggedIn ? Tile.STATE_UNAVAILABLE
: mPowerSave ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.icon = mIcon;
+ state.icon = ResourceIcon.get(mPowerSave
+ ? R.drawable.qs_battery_saver_icon_on
+ : R.drawable.qs_battery_saver_icon_off);
state.label = mContext.getString(R.string.battery_detail_switch_title);
state.secondaryLabel = "";
state.contentDescription = state.label;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index f736231bc22b..9a0d0d9656ca 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -16,12 +16,12 @@
package com.android.systemui.qs.tiles;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
-import android.content.Context;
import android.content.Intent;
-import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Looper;
import android.os.UserManager;
@@ -134,9 +134,10 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
state.contentDescription = mContext.getString(
R.string.accessibility_quick_settings_bluetooth);
state.stateDescription = "";
+
if (enabled) {
if (connected) {
- state.icon = new BluetoothConnectedTileIcon();
+ state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_on);
if (!TextUtils.isEmpty(mController.getConnectedDeviceName())) {
state.label = mController.getConnectedDeviceName();
}
@@ -145,21 +146,19 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
+ ", " + state.secondaryLabel;
} else if (state.isTransient) {
state.icon = ResourceIcon.get(
- com.android.internal.R.drawable.ic_bluetooth_transient_animation);
+ R.drawable.qs_bluetooth_icon_search);
state.stateDescription = state.secondaryLabel;
} else {
state.icon =
- ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth);
+ ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
state.stateDescription = mContext.getString(R.string.accessibility_not_connected);
}
state.state = Tile.STATE_ACTIVE;
} else {
- state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_bluetooth);
+ state.icon = ResourceIcon.get(R.drawable.qs_bluetooth_icon_off);
state.state = Tile.STATE_INACTIVE;
}
- state.dualLabelContentDescription = mContext.getResources().getString(
- R.string.accessibility_quick_settings_open_settings, getTileLabel());
state.expandedAccessibilityClassName = Switch.class.getName();
}
@@ -185,10 +184,8 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
List<CachedBluetoothDevice> connectedDevices = mController.getConnectedDevices();
if (enabled && connected && !connectedDevices.isEmpty()) {
if (connectedDevices.size() > 1) {
- // TODO(b/76102598): add a new string for "X connected devices" after P
- return mContext.getResources().getQuantityString(
- R.plurals.quick_settings_hotspot_secondary_label_num_devices,
- connectedDevices.size(),
+ return icuMessageFormat(mContext.getResources(),
+ R.string.quick_settings_hotspot_secondary_label_num_devices,
connectedDevices.size());
}
@@ -244,22 +241,4 @@ public class BluetoothTile extends QSTileImpl<BooleanState> {
refreshState();
}
};
-
- /**
- * Bluetooth icon wrapper (when connected with no battery indicator) for Quick Settings. This is
- * used instead of {@link com.android.systemui.qs.tileimpl.QSTileImpl.DrawableIcon} in order to
- * use a context that reflects dark/light theme attributes.
- */
- private class BluetoothConnectedTileIcon extends Icon {
-
- BluetoothConnectedTileIcon() {
- // Do nothing. Default constructor to limit visibility.
- }
-
- @Override
- public Drawable getDrawable(Context context) {
- // This method returns Pair<Drawable, String> - the first value is the drawable.
- return context.getDrawable(R.drawable.ic_bluetooth_connected);
- }
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
index fa2d4447f26d..ee41f1d1077d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CameraToggleTile.java
@@ -72,9 +72,9 @@ public class CameraToggleTile extends SensorPrivacyToggleTile {
@Override
public @DrawableRes int getIconRes(boolean isBlocked) {
if (isBlocked) {
- return com.android.internal.R.drawable.ic_camera_blocked;
+ return R.drawable.qs_camera_access_icon_off;
} else {
- return com.android.internal.R.drawable.ic_camera_allowed;
+ return R.drawable.qs_camera_access_icon_on;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
index 4afd39e36ce3..dce137f5b9f3 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java
@@ -33,10 +33,12 @@ import android.widget.Button;
import androidx.annotation.Nullable;
import com.android.internal.app.MediaRouteDialogPresenter;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -63,6 +65,9 @@ import javax.inject.Inject;
/** Quick settings tile: Cast **/
public class CastTile extends QSTileImpl<BooleanState> {
+
+ private static final String INTERACTION_JANK_TAG = "cast";
+
private static final Intent CAST_SETTINGS =
new Intent(Settings.ACTION_CAST_SETTINGS);
@@ -211,7 +216,9 @@ public class CastTile extends QSTileImpl<BooleanState> {
mUiHandler.post(() -> {
if (view != null) {
- mDialogLaunchAnimator.showFromView(dialog, view);
+ mDialogLaunchAnimator.showFromView(dialog, view,
+ new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
} else {
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index 1bbe411eee25..9fdf5940c392 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -24,10 +24,12 @@ import android.widget.Switch;
import androidx.annotation.Nullable;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -46,6 +48,8 @@ import javax.inject.Inject;
public class DataSaverTile extends QSTileImpl<BooleanState> implements
DataSaverController.Listener{
+ private static final String INTERACTION_JANK_TAG = "start_data_saver";
+
private final DataSaverController mDataSaverController;
private final DialogLaunchAnimator mDialogLaunchAnimator;
@@ -102,7 +106,9 @@ public class DataSaverTile extends QSTileImpl<BooleanState> implements
dialog.setShowForAllUsers(true);
if (view != null) {
- mDialogLaunchAnimator.showFromView(dialog, view);
+ mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG));
} else {
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
index 6cff4cd03b0d..8b7f53fa5a3f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DndTile.java
@@ -39,11 +39,13 @@ import android.widget.Switch;
import androidx.annotation.Nullable;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settingslib.notification.EnableZenModeDialog;
import com.android.systemui.Prefs;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -71,6 +73,8 @@ public class DndTile extends QSTileImpl<BooleanState> {
private static final Intent ZEN_PRIORITY_SETTINGS =
new Intent(Settings.ACTION_ZEN_MODE_PRIORITY_SETTINGS);
+ private static final String INTERACTION_JANK_TAG = "start_zen_mode";
+
private final ZenModeController mController;
private final SharedPreferences mSharedPreferences;
private final SettingObserver mSettingZenDuration;
@@ -175,7 +179,10 @@ public class DndTile extends QSTileImpl<BooleanState> {
mUiHandler.post(() -> {
Dialog dialog = makeZenModeDialog();
if (view != null) {
- mDialogLaunchAnimator.showFromView(dialog, view, false);
+ mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG),
+ /* animateBackgroundBoundsChange= */ false);
} else {
dialog.show();
}
@@ -219,16 +226,15 @@ public class DndTile extends QSTileImpl<BooleanState> {
if (mController == null) return;
final int zen = arg instanceof Integer ? (Integer) arg : mController.getZen();
final boolean newValue = zen != ZEN_MODE_OFF;
- final boolean valueChanged = state.value != newValue;
- if (state.slash == null) state.slash = new SlashState();
state.dualTarget = true;
state.value = newValue;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
- state.slash.isSlashed = !state.value;
+ state.icon = ResourceIcon.get(state.value
+ ? R.drawable.qs_dnd_icon_on
+ : R.drawable.qs_dnd_icon_off);
state.label = getTileLabel();
state.secondaryLabel = TextUtils.emptyIfNull(ZenModeConfig.getDescription(mContext,
zen != Global.ZEN_MODE_OFF, mController.getConfig(), false));
- state.icon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_dnd);
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_ADJUST_VOLUME);
// Keeping the secondaryLabel in contentDescription instead of stateDescription is easier
// to understand.
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
new file mode 100644
index 000000000000..bebd5803fabf
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DreamTile.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.provider.Settings;
+import android.service.dreams.IDreamManager;
+import android.service.quicksettings.Tile;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.dagger.qualifiers.Background;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dreams.dagger.DreamModule;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.plugins.qs.QSTile;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSHost;
+import com.android.systemui.qs.SettingObserver;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.SecureSettings;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+/** Quick settings tile: Screensaver (dream) **/
+public class DreamTile extends QSTileImpl<QSTile.BooleanState> {
+
+ private static final String LOG_TAG = "QSDream";
+ // TODO: consider 1 animated icon instead
+ private final Icon mIconDocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver);
+ private final Icon mIconUndocked = ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked);
+ private final IDreamManager mDreamManager;
+ private final BroadcastDispatcher mBroadcastDispatcher;
+ private final SettingObserver mEnabledSettingObserver;
+ private final SettingObserver mDreamSettingObserver;
+ private final UserTracker mUserTracker;
+ private final boolean mDreamSupported;
+ private final boolean mDreamOnlyEnabledForSystemUser;
+
+ private boolean mIsDocked = false;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+ mIsDocked = intent.getIntExtra(Intent.EXTRA_DOCK_STATE, -1)
+ != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+ }
+ refreshState();
+ }
+ };
+
+ @Inject
+ public DreamTile(
+ QSHost host,
+ @Background Looper backgroundLooper,
+ @Main Handler mainHandler,
+ FalsingManager falsingManager,
+ MetricsLogger metricsLogger,
+ StatusBarStateController statusBarStateController,
+ ActivityStarter activityStarter,
+ QSLogger qsLogger,
+ IDreamManager dreamManager,
+ SecureSettings secureSettings,
+ BroadcastDispatcher broadcastDispatcher,
+ UserTracker userTracker,
+ @Named(DreamModule.DREAM_SUPPORTED) boolean dreamSupported,
+ @Named(DreamModule.DREAM_ONLY_ENABLED_FOR_SYSTEM_USER)
+ boolean dreamOnlyEnabledForSystemUser
+ ) {
+ super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger,
+ statusBarStateController, activityStarter, qsLogger);
+ mDreamManager = dreamManager;
+ mBroadcastDispatcher = broadcastDispatcher;
+ mEnabledSettingObserver = new SettingObserver(secureSettings, mHandler,
+ Settings.Secure.SCREENSAVER_ENABLED) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ refreshState();
+ }
+ };
+ mDreamSettingObserver = new SettingObserver(secureSettings, mHandler,
+ Settings.Secure.SCREENSAVER_COMPONENTS) {
+ @Override
+ protected void handleValueChanged(int value, boolean observedChange) {
+ refreshState();
+ }
+ };
+ mUserTracker = userTracker;
+ mDreamSupported = dreamSupported;
+ mDreamOnlyEnabledForSystemUser = dreamOnlyEnabledForSystemUser;
+ }
+
+ @Override
+ public void handleSetListening(boolean listening) {
+ super.handleSetListening(listening);
+
+ if (listening) {
+ final IntentFilter filter = new IntentFilter();
+ filter.addAction(Intent.ACTION_DREAMING_STARTED);
+ filter.addAction(Intent.ACTION_DREAMING_STOPPED);
+ filter.addAction(Intent.ACTION_DOCK_EVENT);
+ mBroadcastDispatcher.registerReceiver(mReceiver, filter);
+ } else {
+ mBroadcastDispatcher.unregisterReceiver(mReceiver);
+ }
+ mEnabledSettingObserver.setListening(listening);
+ mDreamSettingObserver.setListening(listening);
+ }
+
+ @Override
+ public BooleanState newTileState() {
+ return new BooleanState();
+ }
+
+ @Override
+ protected void handleClick(@Nullable View view) {
+ try {
+ if (mDreamManager.isDreaming()) {
+ mDreamManager.awaken();
+ } else {
+ mDreamManager.dream();
+ }
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can't dream", e);
+ }
+ }
+
+ @Override
+ protected void handleLongClick(@Nullable View view) {
+ try {
+ // Need to wake on long click so bouncer->settings works.
+ mDreamManager.awaken();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can't awaken", e);
+ }
+ super.handleLongClick(view);
+ }
+
+ @Override
+ protected void handleUpdateState(BooleanState state, Object arg) {
+ state.label = getTileLabel();
+ state.secondaryLabel = getActiveDreamName();
+ state.contentDescription = getContentDescription(state.secondaryLabel);
+ state.icon = mIsDocked ? mIconDocked : mIconUndocked;
+
+ if (getActiveDream() == null || !isScreensaverEnabled()) {
+ state.state = Tile.STATE_UNAVAILABLE;
+ } else {
+ state.state = isDreaming() ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
+ }
+ }
+
+ @Nullable
+ @Override
+ public Intent getLongClickIntent() {
+ return new Intent(Settings.ACTION_DREAM_SETTINGS);
+ }
+
+ @Override
+ public CharSequence getTileLabel() {
+ return mContext.getString(R.string.quick_settings_screensaver_label);
+ }
+
+ @Override
+ public boolean isAvailable() {
+ // Only enable for devices that have dreams for the user(s) that can dream.
+ // For now, restrict to debug users.
+ return Build.isDebuggable()
+ && mDreamSupported
+ && (!mDreamOnlyEnabledForSystemUser || mUserTracker.getUserHandle().isSystem());
+ }
+
+ @VisibleForTesting
+ protected CharSequence getContentDescription(CharSequence dreamName) {
+ return !TextUtils.isEmpty(dreamName)
+ ? getTileLabel() + ", " + dreamName : getTileLabel();
+ }
+
+ private boolean isDreaming() {
+ try {
+ return mDreamManager.isDreaming();
+ } catch (RemoteException e) {
+ Log.e(LOG_TAG, "Can't check if dreaming", e);
+ return false;
+ }
+ }
+
+ private ComponentName getActiveDream() {
+ try {
+ final ComponentName[] dreams = mDreamManager.getDreamComponents();
+ return dreams != null && dreams.length > 0 ? dreams[0] : null;
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to get active dream", e);
+ return null;
+ }
+ }
+
+ private CharSequence getActiveDreamName() {
+ final ComponentName componentName = getActiveDream();
+ if (componentName != null) {
+ PackageManager pm = mContext.getPackageManager();
+ try {
+ ServiceInfo ri = pm.getServiceInfo(componentName, 0);
+ if (ri != null) {
+ return ri.loadLabel(pm);
+ }
+ } catch (PackageManager.NameNotFoundException exc) {
+ return null; // uninstalled?
+ }
+ }
+ return null;
+ }
+
+ private boolean isScreensaverEnabled() {
+ return mEnabledSettingObserver.getValue() == 1;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
index 7c8e77b5d993..b6f6e933bf84 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/HotspotTile.java
@@ -16,6 +16,8 @@
package com.android.systemui.qs.tiles;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+
import android.content.Intent;
import android.os.Handler;
import android.os.Looper;
@@ -186,9 +188,8 @@ public class HotspotTile extends QSTileImpl<BooleanState> {
return mContext.getString(
R.string.quick_settings_hotspot_secondary_label_data_saver_enabled);
} else if (numConnectedDevices > 0 && isActive) {
- return mContext.getResources().getQuantityString(
- R.plurals.quick_settings_hotspot_secondary_label_num_devices,
- numConnectedDevices,
+ return icuMessageFormat(mContext.getResources(),
+ R.string.quick_settings_hotspot_secondary_label_num_devices,
numConnectedDevices);
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
index fc93f44a44aa..9466a694ea1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java
@@ -48,8 +48,6 @@ import javax.inject.Inject;
/** Quick settings tile: Location **/
public class LocationTile extends QSTileImpl<BooleanState> {
- private final Icon mIcon = ResourceIcon.get(R.drawable.ic_location);
-
private final LocationController mController;
private final KeyguardStateController mKeyguard;
private final Callback mCallback = new Callback();
@@ -119,8 +117,8 @@ public class LocationTile extends QSTileImpl<BooleanState> {
if (state.disabledByPolicy == false) {
checkIfRestrictionEnforcedByAdminOnly(state, UserManager.DISALLOW_CONFIG_LOCATION);
}
- state.icon = mIcon;
- state.slash.isSlashed = !state.value;
+ state.icon = ResourceIcon.get(state.value
+ ? R.drawable.qs_location_icon_on : R.drawable.qs_location_icon_off);
state.label = mContext.getString(R.string.quick_settings_location_label);
state.contentDescription = state.label;
state.state = state.value ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
index f4f0b2cdc432..e54709562c10 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/MicrophoneToggleTile.java
@@ -72,9 +72,9 @@ public class MicrophoneToggleTile extends SensorPrivacyToggleTile {
@Override
public @DrawableRes int getIconRes(boolean isBlocked) {
if (isBlocked) {
- return com.android.internal.R.drawable.ic_mic_blocked;
+ return R.drawable.qs_mic_access_off;
} else {
- return com.android.internal.R.drawable.ic_mic_allowed;
+ return R.drawable.qs_mic_access_on;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
index 177c82ed3d78..600874f0d01a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RotationLockTile.java
@@ -57,6 +57,7 @@ import javax.inject.Inject;
/** Quick settings tile: Rotation **/
public class RotationLockTile extends QSTileImpl<BooleanState> implements
BatteryController.BatteryStateChangeCallback {
+ private static final String EMPTY_SECONDARY_STRING = "";
private final Icon mIcon = ResourceIcon.get(com.android.internal.R.drawable.ic_qs_auto_rotate);
private final RotationLockController mController;
@@ -144,13 +145,15 @@ public class RotationLockTile extends QSTileImpl<BooleanState> implements
&& mController.isCameraRotationEnabled();
state.value = !rotationLocked;
state.label = mContext.getString(R.string.quick_settings_rotation_unlocked_label);
- state.icon = mIcon;
+ state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off);
state.contentDescription = getAccessibilityString(rotationLocked);
- if (!rotationLocked && cameraRotation) {
- state.secondaryLabel = mContext.getResources().getString(
- R.string.rotation_lock_camera_rotation_on);
+ if (!rotationLocked) {
+ state.secondaryLabel = cameraRotation ? mContext.getResources().getString(
+ R.string.rotation_lock_camera_rotation_on)
+ : EMPTY_SECONDARY_STRING;
+ state.icon = ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on);
} else {
- state.secondaryLabel = "";
+ state.secondaryLabel = EMPTY_SECONDARY_STRING;
}
state.stateDescription = state.secondaryLabel;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
index 45e43ee20d67..02d30c5224a0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java
@@ -27,8 +27,10 @@ import android.widget.Switch;
import androidx.annotation.Nullable;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
@@ -52,6 +54,8 @@ import javax.inject.Inject;
public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
implements RecordingController.RecordingStateChangeCallback {
private static final String TAG = "ScreenRecordTile";
+ private static final String INTERACTION_JANK_TAG = "screen_record";
+
private final RecordingController mController;
private final KeyguardDismissUtil mKeyguardDismissUtil;
private final KeyguardStateController mKeyguardStateController;
@@ -165,7 +169,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState>
ActivityStarter.OnDismissAction dismissAction = () -> {
if (shouldAnimateFromView) {
- mDialogLaunchAnimator.showFromView(dialog, view);
+ mDialogLaunchAnimator.showFromView(dialog, view, new DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG));
} else {
dialog.show();
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
index 4386169c8887..8566ca308738 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogFactory.kt
@@ -19,7 +19,9 @@ import android.content.Context
import android.os.Handler
import android.util.Log
import android.view.View
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -45,6 +47,7 @@ class InternetDialogFactory @Inject constructor(
private val keyguardStateController: KeyguardStateController
) {
companion object {
+ private const val INTERACTION_JANK_TAG = "internet"
var internetDialog: InternetDialog? = null
}
@@ -61,12 +64,20 @@ class InternetDialogFactory @Inject constructor(
}
return
} else {
- internetDialog = InternetDialog(context, this, internetDialogController,
- canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
- executor, keyguardStateController)
+ internetDialog = InternetDialog(
+ context, this, internetDialogController,
+ canConfigMobileData, canConfigWifi, aboveStatusBar, uiEventLogger, handler,
+ executor, keyguardStateController
+ )
if (view != null) {
- dialogLaunchAnimator.showFromView(internetDialog!!, view,
- animateBackgroundBoundsChange = true)
+ dialogLaunchAnimator.showFromView(
+ internetDialog!!, view,
+ animateBackgroundBoundsChange = true,
+ cuj = DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
} else {
internetDialog?.show()
}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index 88aa734df2b3..bdcc6b0b2a57 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -25,8 +25,10 @@ import android.provider.Settings
import android.view.LayoutInflater
import android.view.View
import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.UiEventLogger
import com.android.systemui.R
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
@@ -67,6 +69,7 @@ class UserSwitchDialogController @VisibleForTesting constructor(
)
companion object {
+ private const val INTERACTION_JANK_TAG = "switch_user"
private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
}
@@ -89,14 +92,16 @@ class UserSwitchDialogController @VisibleForTesting constructor(
if (!falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
val controller = dialogLaunchAnimator.createActivityLaunchController(
- getButton(BUTTON_NEUTRAL))
+ getButton(BUTTON_NEUTRAL)
+ )
if (controller == null) {
dismiss()
}
activityStarter.postStartActivityDismissingKeyguard(
- USER_SETTINGS_INTENT, 0, controller)
+ USER_SETTINGS_INTENT, 0, controller
+ )
}
}, false /* dismissOnClick */)
val gridFrame = LayoutInflater.from(this.context)
@@ -107,7 +112,13 @@ class UserSwitchDialogController @VisibleForTesting constructor(
adapter.linkToViewGroup(gridFrame.findViewById(R.id.grid))
- dialogLaunchAnimator.showFromView(this, view)
+ dialogLaunchAnimator.showFromView(
+ this, view,
+ cuj = DialogCuj(
+ InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN,
+ INTERACTION_JANK_TAG
+ )
+ )
uiEventLogger.log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
adapter.injectDialogShower(DialogShowerImpl(this, dialogLaunchAnimator))
}
@@ -117,15 +128,16 @@ class UserSwitchDialogController @VisibleForTesting constructor(
private val animateFrom: Dialog,
private val dialogLaunchAnimator: DialogLaunchAnimator
) : DialogInterface by animateFrom, DialogShower {
- override fun showDialog(dialog: Dialog) {
+ override fun showDialog(dialog: Dialog, cuj: DialogCuj) {
dialogLaunchAnimator.showFromDialog(
dialog,
- animateFrom = animateFrom
+ animateFrom = animateFrom,
+ cuj
)
}
}
interface DialogShower : DialogInterface {
- fun showDialog(dialog: Dialog)
+ fun showDialog(dialog: Dialog, cuj: DialogCuj)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 9768e706764f..438236d6a63d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -51,7 +51,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
-import android.graphics.Bitmap;
import android.graphics.Insets;
import android.graphics.Rect;
import android.graphics.Region;
@@ -97,6 +96,7 @@ import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.navigationbar.buttons.KeyButtonView;
import com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
import com.android.systemui.settings.CurrentUserTracker;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.recents.IOverviewProxy;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.Task;
@@ -105,7 +105,6 @@ import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.CallbackController;
import com.android.wm.shell.back.BackAnimation;
@@ -297,12 +296,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
- public Rect getNonMinimizedSplitScreenSecondaryBounds() {
- // Deprecated
- return null;
- }
-
- @Override
public void setNavBarButtonAlpha(float alpha, boolean animate) {
verifyCallerAndClearCallingIdentityPostMain("setNavBarButtonAlpha", () ->
notifyNavBarButtonAlphaChanged(alpha, animate));
@@ -349,17 +342,6 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
@Override
- public void handleImageAsScreenshot(Bitmap screenImage, Rect locationInScreen,
- Insets visibleInsets, int taskId) {
- // Deprecated
- }
-
- @Override
- public void setSplitScreenMinimized(boolean minimized) {
- // Deprecated
- }
-
- @Override
public void notifySwipeToHomeFinished() {
verifyCallerAndClearCallingIdentity("notifySwipeToHomeFinished", () ->
mPipOptional.ifPresent(
@@ -736,7 +718,7 @@ public class OverviewProxyService extends CurrentUserTracker implements
}
private void onStatusBarStateChanged(boolean keyguardShowing, boolean keyguardOccluded,
- boolean bouncerShowing, boolean isDozing) {
+ boolean bouncerShowing, boolean isDozing, boolean panelExpanded) {
mSysUiState.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING,
keyguardShowing && !keyguardOccluded)
.setFlag(SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
index bdad36c58480..93a2efc4e96d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/RippleShader.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleShader.kt
@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
import android.graphics.PointF
import android.graphics.RuntimeShader
@@ -148,7 +148,7 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) {
val fadeOutNoise = subProgress(0.4f, 1f, value)
var fadeOutRipple = 0f
var fadeCircle = 0f
- if (shouldFadeOutRipple) {
+ if (!rippleFill) {
fadeCircle = subProgress(0f, 0.2f, value)
fadeOutRipple = subProgress(0.3f, 1f, value)
}
@@ -202,5 +202,9 @@ class RippleShader internal constructor() : RuntimeShader(SHADER) {
setFloatUniform("in_pixelDensity", value)
}
- var shouldFadeOutRipple: Boolean = true
+ /**
+ * True if the ripple should stayed filled in as it expands to give a filled-in circle effect.
+ * False for a ring effect.
+ */
+ var rippleFill: Boolean = false
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 10e90fee1469..fc52464ecf85 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/charging/ChargingRippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.ripple
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
@@ -30,9 +30,10 @@ import android.view.View
private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
/**
- * Expanding ripple effect that shows when charging begins.
+ * A generic expanding ripple effect. To trigger the ripple expansion, set [radius] and [origin],
+ * then call [startRipple].
*/
-class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+open class RippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
private val rippleShader = RippleShader()
private val defaultColor: Int = 0xffffffff.toInt()
private val ripplePaint = Paint()
@@ -74,9 +75,9 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
}
val animator = ValueAnimator.ofFloat(0f, 1f)
animator.duration = duration
- animator.addUpdateListener { animator ->
- val now = animator.currentPlayTime
- val progress = animator.animatedValue as Float
+ animator.addUpdateListener { updateListener ->
+ val now = updateListener.currentPlayTime
+ val progress = updateListener.animatedValue as Float
rippleShader.progress = progress
rippleShader.distortionStrength = 1 - progress
rippleShader.time = now.toFloat()
@@ -92,10 +93,20 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
rippleInProgress = true
}
+ /** Set the color to be used for the ripple. */
fun setColor(color: Int) {
rippleShader.color = color
}
+ /**
+ * Set whether the ripple should remain filled as the ripple expands.
+ *
+ * See [RippleShader.rippleFill].
+ */
+ fun setRippleFill(rippleFill: Boolean) {
+ rippleShader.rippleFill = rippleFill
+ }
+
override fun onDraw(canvas: Canvas?) {
if (canvas == null || !canvas.isHardwareAccelerated) {
// Drawing with the ripple shader requires hardware acceleration, so skip
@@ -107,6 +118,6 @@ class ChargingRippleView(context: Context?, attrs: AttributeSet?) : View(context
// animation implementation in the ripple shader.
val maskRadius = (1 - (1 - rippleShader.progress) * (1 - rippleShader.progress) *
(1 - rippleShader.progress)) * radius * 2
- canvas?.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
+ canvas.drawCircle(origin.x, origin.y, maskRadius, ripplePaint)
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
index 5bb3413595ba..a837cbb8a50e 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java
@@ -29,6 +29,7 @@ import android.graphics.drawable.Icon;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -40,6 +41,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.UiEventLogger;
import com.android.systemui.R;
import com.android.systemui.dagger.qualifiers.LongRunning;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.screenrecord.ScreenMediaRecorder.ScreenMediaRecorderListener;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.statusbar.phone.KeyguardDismissUtil;
@@ -51,9 +54,10 @@ import javax.inject.Inject;
/**
* A service which records the device screen and optionally microphone input.
*/
-public class RecordingService extends Service implements MediaRecorder.OnInfoListener {
+public class RecordingService extends Service implements ScreenMediaRecorderListener {
public static final int REQUEST_CODE = 2;
+ private static final int USER_ID_NOT_SPECIFIED = -1;
private static final int NOTIFICATION_RECORDING_ID = 4274;
private static final int NOTIFICATION_PROCESSING_ID = 4275;
private static final int NOTIFICATION_VIEW_ID = 4273;
@@ -73,6 +77,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
private final RecordingController mController;
private final KeyguardDismissUtil mKeyguardDismissUtil;
+ private final Handler mMainHandler;
private ScreenRecordingAudioSource mAudioSource;
private boolean mShowTaps;
private boolean mOriginalShowTaps;
@@ -84,10 +89,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
@Inject
public RecordingService(RecordingController controller, @LongRunning Executor executor,
- UiEventLogger uiEventLogger, NotificationManager notificationManager,
+ @Main Handler handler, UiEventLogger uiEventLogger,
+ NotificationManager notificationManager,
UserContextProvider userContextTracker, KeyguardDismissUtil keyguardDismissUtil) {
mController = controller;
mLongExecutor = executor;
+ mMainHandler = handler;
mUiEventLogger = uiEventLogger;
mNotificationManager = notificationManager;
mUserContextTracker = userContextTracker;
@@ -138,6 +145,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
mRecorder = new ScreenMediaRecorder(
mUserContextTracker.getUserContext(),
+ mMainHandler,
currentUserId,
mAudioSource,
this
@@ -166,14 +174,8 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
}
// Check user ID - we may be getting a stop intent after user switch, in which case
// we want to post the notifications for that user, which is NOT current user
- int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
- if (userId == -1) {
- userId = mUserContextTracker.getUserContext().getUserId();
- }
- Log.d(TAG, "notifying for user " + userId);
- stopRecording(userId);
- mNotificationManager.cancel(NOTIFICATION_RECORDING_ID);
- stopSelf();
+ int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, USER_ID_NOT_SPECIFIED);
+ stopService(userId);
break;
case ACTION_SHARE:
@@ -378,15 +380,39 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
return builder.build();
}
- private void stopRecording(int userId) {
+ private void stopService() {
+ stopService(USER_ID_NOT_SPECIFIED);
+ }
+
+ private void stopService(int userId) {
+ if (userId == USER_ID_NOT_SPECIFIED) {
+ userId = mUserContextTracker.getUserContext().getUserId();
+ }
+ Log.d(TAG, "notifying for user " + userId);
setTapsVisible(mOriginalShowTaps);
if (getRecorder() != null) {
- getRecorder().end();
- saveRecording(userId);
+ try {
+ getRecorder().end();
+ saveRecording(userId);
+ } catch (RuntimeException exception) {
+ // RuntimeException could happen if the recording stopped immediately after starting
+ // let's release the recorder and delete all temporary files in this case
+ getRecorder().release();
+ showErrorToast(R.string.screenrecord_start_error);
+ Log.e(TAG, "stopRecording called, but there was an error when ending"
+ + "recording");
+ exception.printStackTrace();
+ } catch (Throwable throwable) {
+ // Something unexpected happen, SystemUI will crash but let's delete
+ // the temporary files anyway
+ getRecorder().release();
+ throw new RuntimeException(throwable);
+ }
} else {
Log.e(TAG, "stopRecording called, but recorder was null");
}
updateState(false);
+ stopSelf();
}
private void saveRecording(int userId) {
@@ -446,4 +472,12 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis
Log.d(TAG, "Media recorder info: " + what);
onStartCommand(getStopIntent(this), 0, 0);
}
+
+ @Override
+ public void onStopped() {
+ if (mController.isRecording()) {
+ Log.d(TAG, "Stopping recording because the system requested the stop");
+ stopService();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
index 2133cf63d1c3..d098b4b3442a 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/ScreenMediaRecorder.java
@@ -40,6 +40,7 @@ import android.media.projection.IMediaProjectionManager;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.net.Uri;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ServiceManager;
@@ -51,16 +52,19 @@ import android.view.Surface;
import android.view.WindowManager;
import java.io.File;
+import java.io.Closeable;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.text.SimpleDateFormat;
+import java.util.ArrayList;
import java.util.Date;
+import java.util.List;
/**
* Recording screen and mic/internal audio
*/
-public class ScreenMediaRecorder {
+public class ScreenMediaRecorder extends MediaProjection.Callback {
private static final int TOTAL_NUM_TRACKS = 1;
private static final int VIDEO_FRAME_RATE = 30;
private static final int VIDEO_FRAME_RATE_TO_RESOLUTION_RATIO = 6;
@@ -81,14 +85,16 @@ public class ScreenMediaRecorder {
private ScreenRecordingMuxer mMuxer;
private ScreenInternalAudioRecorder mAudio;
private ScreenRecordingAudioSource mAudioSource;
+ private final Handler mHandler;
private Context mContext;
- MediaRecorder.OnInfoListener mListener;
+ ScreenMediaRecorderListener mListener;
- public ScreenMediaRecorder(Context context,
+ public ScreenMediaRecorder(Context context, Handler handler,
int user, ScreenRecordingAudioSource audioSource,
- MediaRecorder.OnInfoListener listener) {
+ ScreenMediaRecorderListener listener) {
mContext = context;
+ mHandler = handler;
mUser = user;
mListener = listener;
mAudioSource = audioSource;
@@ -105,6 +111,7 @@ public class ScreenMediaRecorder {
IBinder projection = proj.asBinder();
mMediaProjection = new MediaProjection(mContext,
IMediaProjection.Stub.asInterface(projection));
+ mMediaProjection.registerCallback(this, mHandler);
File cacheDir = mContext.getCacheDir();
cacheDir.mkdirs();
@@ -162,10 +169,15 @@ public class ScreenMediaRecorder {
metrics.densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mInputSurface,
- null,
- null);
-
- mMediaRecorder.setOnInfoListener(mListener);
+ new VirtualDisplay.Callback() {
+ @Override
+ public void onStopped() {
+ onStop();
+ }
+ },
+ mHandler);
+
+ mMediaRecorder.setOnInfoListener((mr, what, extra) -> mListener.onInfo(mr, what, extra));
if (mAudioSource == INTERNAL ||
mAudioSource == MIC_AND_INTERNAL) {
mTempAudioFile = File.createTempFile("temp", ".aac",
@@ -259,21 +271,34 @@ public class ScreenMediaRecorder {
}
/**
- * End screen recording
+ * End screen recording, throws an exception if stopping recording failed
*/
- void end() {
- mMediaRecorder.stop();
- mMediaRecorder.release();
- mInputSurface.release();
- mVirtualDisplay.release();
- mMediaProjection.stop();
+ void end() throws IOException {
+ Closer closer = new Closer();
+
+ // MediaRecorder might throw RuntimeException if stopped immediately after starting
+ // We should remove the recording in this case as it will be invalid
+ closer.register(mMediaRecorder::stop);
+ closer.register(mMediaRecorder::release);
+ closer.register(mInputSurface::release);
+ closer.register(mVirtualDisplay::release);
+ closer.register(mMediaProjection::stop);
+ closer.register(this::stopInternalAudioRecording);
+
+ closer.close();
+
mMediaRecorder = null;
mMediaProjection = null;
- stopInternalAudioRecording();
Log.d(TAG, "end recording");
}
+ @Override
+ public void onStop() {
+ Log.d(TAG, "The system notified about stopping the projection");
+ mListener.onStopped();
+ }
+
private void stopInternalAudioRecording() {
if (mAudioSource == INTERNAL || mAudioSource == MIC_AND_INTERNAL) {
mAudio.end();
@@ -337,6 +362,18 @@ public class ScreenMediaRecorder {
}
/**
+ * Release the resources without saving the data
+ */
+ protected void release() {
+ if (mTempVideoFile != null) {
+ mTempVideoFile.delete();
+ }
+ if (mTempAudioFile != null) {
+ mTempAudioFile.delete();
+ }
+ }
+
+ /**
* Object representing the recording
*/
public class SavedRecording {
@@ -362,4 +399,66 @@ public class ScreenMediaRecorder {
return mThumbnailBitmap;
}
}
+
+ interface ScreenMediaRecorderListener {
+ /**
+ * Called to indicate an info or a warning during recording.
+ * See {@link MediaRecorder.OnInfoListener} for the full description.
+ */
+ void onInfo(MediaRecorder mr, int what, int extra);
+
+ /**
+ * Called when the recording stopped by the system.
+ * For example, this might happen when doing partial screen sharing of an app
+ * and the app that is being captured is closed.
+ */
+ void onStopped();
+ }
+
+ /**
+ * Allows to register multiple {@link Closeable} objects and close them all by calling
+ * {@link Closer#close}. If there is an exception thrown during closing of one
+ * of the registered closeables it will continue trying closing the rest closeables.
+ * If there are one or more exceptions thrown they will be re-thrown at the end.
+ * In case of multiple exceptions only the first one will be thrown and all the rest
+ * will be printed.
+ */
+ private static class Closer implements Closeable {
+ private final List<Closeable> mCloseables = new ArrayList<>();
+
+ void register(Closeable closeable) {
+ mCloseables.add(closeable);
+ }
+
+ @Override
+ public void close() throws IOException {
+ Throwable throwable = null;
+
+ for (int i = 0; i < mCloseables.size(); i++) {
+ Closeable closeable = mCloseables.get(i);
+
+ try {
+ closeable.close();
+ } catch (Throwable e) {
+ if (throwable == null) {
+ throwable = e;
+ } else {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ if (throwable != null) {
+ if (throwable instanceof IOException) {
+ throw (IOException) throwable;
+ }
+
+ if (throwable instanceof RuntimeException) {
+ throw (RuntimeException) throwable;
+ }
+
+ throw (Error) throwable;
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
index daaa897374cb..814b8e90e0dd 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionProxyReceiver.java
@@ -102,7 +102,7 @@ public class ActionProxyReceiver extends BroadcastReceiver {
? ACTION_TYPE_EDIT
: ACTION_TYPE_SHARE;
mScreenshotSmartActions.notifyScreenshotAction(
- context, intent.getStringExtra(EXTRA_ID), actionType, false, null);
+ intent.getStringExtra(EXTRA_ID), actionType, false, null);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
index 8d44205bef30..e0346f2e2a98 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/DeleteScreenshotReceiver.java
@@ -62,7 +62,7 @@ public class DeleteScreenshotReceiver extends BroadcastReceiver {
});
if (intent.getBooleanExtra(EXTRA_SMART_ACTIONS_ENABLED, false)) {
mScreenshotSmartActions.notifyScreenshotAction(
- context, intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null);
+ intent.getStringExtra(EXTRA_ID), ACTION_TYPE_DELETE, false, null);
}
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
new file mode 100644
index 000000000000..6224e1bf2414
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ReferenceScreenshotModule.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.screenshot;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ *
+ */
+@Module
+public interface ReferenceScreenshotModule {
+ /** */
+ @Provides
+ static ScreenshotNotificationSmartActionsProvider providesScrnshtNotifSmartActionsProvider() {
+ return new ScreenshotNotificationSmartActionsProvider();
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
index 50ee1f7ba97a..f248d6913878 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java
@@ -38,7 +38,6 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
-import android.os.Handler;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -49,7 +48,6 @@ import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
import com.android.systemui.R;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
import com.google.common.util.concurrent.ListenableFuture;
@@ -89,7 +87,10 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
SaveImageInBackgroundTask(Context context, ImageExporter exporter,
ScreenshotSmartActions screenshotSmartActions,
ScreenshotController.SaveImageInBackgroundData data,
- Supplier<ActionTransition> sharedElementTransition) {
+ Supplier<ActionTransition> sharedElementTransition,
+ ScreenshotNotificationSmartActionsProvider
+ screenshotNotificationSmartActionsProvider
+ ) {
mContext = context;
mScreenshotSmartActions = screenshotSmartActions;
mImageData = new ScreenshotController.SavedImageData();
@@ -103,15 +104,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> {
// Initialize screenshot notification smart actions provider.
mSmartActionsEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, true);
- if (mSmartActionsEnabled) {
- mSmartActionsProvider =
- SystemUIFactory.getInstance()
- .createScreenshotNotificationSmartActionsProvider(
- context, THREAD_POOL_EXECUTOR, new Handler());
- } else {
- // If smart actions is not enabled use empty implementation.
- mSmartActionsProvider = new ScreenshotNotificationSmartActionsProvider();
- }
+ mSmartActionsProvider = screenshotNotificationSmartActionsProvider;
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index c213f192291a..82de389060ae 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -263,6 +263,8 @@ public class ScreenshotController {
private final ScrollCaptureController mScrollCaptureController;
private final LongScreenshotData mLongScreenshotHolder;
private final boolean mIsLowRamDevice;
+ private final ScreenshotNotificationSmartActionsProvider
+ mScreenshotNotificationSmartActionsProvider;
private final TimeoutHandler mScreenshotHandler;
private ScreenshotView mScreenshotView;
@@ -298,7 +300,9 @@ public class ScreenshotController {
LongScreenshotData longScreenshotHolder,
ActivityManager activityManager,
TimeoutHandler timeoutHandler,
- BroadcastSender broadcastSender) {
+ BroadcastSender broadcastSender,
+ ScreenshotNotificationSmartActionsProvider screenshotNotificationSmartActionsProvider
+ ) {
mScreenshotSmartActions = screenshotSmartActions;
mNotificationsController = screenshotNotificationsController;
mScrollCaptureClient = scrollCaptureClient;
@@ -308,6 +312,7 @@ public class ScreenshotController {
mScrollCaptureController = scrollCaptureController;
mLongScreenshotHolder = longScreenshotHolder;
mIsLowRamDevice = activityManager.isLowRamDevice();
+ mScreenshotNotificationSmartActionsProvider = screenshotNotificationSmartActionsProvider;
mBgExecutor = Executors.newSingleThreadExecutor();
mBroadcastSender = broadcastSender;
@@ -838,14 +843,20 @@ public class ScreenshotController {
// The media player creation is slow and needs on the background thread.
return CallbackToFutureAdapter.getFuture((completer) -> {
mBgExecutor.execute(() -> {
- MediaPlayer player = MediaPlayer.create(mContext,
- Uri.fromFile(new File(mContext.getResources().getString(
- com.android.internal.R.string.config_cameraShutterSound))), null,
- new AudioAttributes.Builder()
- .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
- .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .build(), AudioSystem.newAudioSessionId());
- completer.set(player);
+ try {
+ MediaPlayer player = MediaPlayer.create(mContext,
+ Uri.fromFile(new File(mContext.getResources().getString(
+ com.android.internal.R.string.config_cameraShutterSound))),
+ null,
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_ASSISTANCE_SONIFICATION)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build(), AudioSystem.newAudioSessionId());
+ completer.set(player);
+ } catch (IllegalStateException e) {
+ Log.w(TAG, "Screenshot sound initialization failed", e);
+ completer.set(null);
+ }
});
return "ScreenshotController#loadCameraSound";
});
@@ -956,7 +967,8 @@ public class ScreenshotController {
}
mSaveInBgTask = new SaveImageInBackgroundTask(mContext, mImageExporter,
- mScreenshotSmartActions, data, getActionTransitionSupplier());
+ mScreenshotSmartActions, data, getActionTransitionSupplier(),
+ mScreenshotNotificationSmartActionsProvider);
mSaveInBgTask.execute();
}
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
index 0527818135dd..68b46d2b7525 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSmartActions.java
@@ -16,8 +16,6 @@
package com.android.systemui.screenshot;
-import static android.os.AsyncTask.THREAD_POOL_EXECUTOR;
-
import static com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS;
import static com.android.systemui.screenshot.LogConfig.logTag;
import static com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType;
@@ -25,17 +23,14 @@ import static com.android.systemui.screenshot.ScreenshotNotificationSmartActions
import android.app.ActivityManager;
import android.app.Notification;
import android.content.ComponentName;
-import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
-import android.os.Handler;
import android.os.SystemClock;
import android.os.UserHandle;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -46,6 +41,7 @@ import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.inject.Inject;
+import javax.inject.Provider;
/**
* Collects the static functions for retrieving and acting on smart actions.
@@ -53,9 +49,17 @@ import javax.inject.Inject;
@SysUISingleton
public class ScreenshotSmartActions {
private static final String TAG = logTag(ScreenshotSmartActions.class);
+ private final Provider<ScreenshotNotificationSmartActionsProvider>
+ mScreenshotNotificationSmartActionsProviderProvider;
@Inject
- public ScreenshotSmartActions() {}
+ public ScreenshotSmartActions(
+ Provider<ScreenshotNotificationSmartActionsProvider>
+ screenshotNotificationSmartActionsProviderProvider
+ ) {
+ mScreenshotNotificationSmartActionsProviderProvider =
+ screenshotNotificationSmartActionsProviderProvider;
+ }
@VisibleForTesting
CompletableFuture<List<Notification.Action>> getSmartActionsFuture(
@@ -165,12 +169,11 @@ public class ScreenshotSmartActions {
}
}
- void notifyScreenshotAction(Context context, String screenshotId, String action,
+ void notifyScreenshotAction(String screenshotId, String action,
boolean isSmartAction, Intent intent) {
try {
ScreenshotNotificationSmartActionsProvider provider =
- SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
- context, THREAD_POOL_EXECUTOR, new Handler());
+ mScreenshotNotificationSmartActionsProviderProvider.get();
if (DEBUG_ACTIONS) {
Log.d(TAG, String.format("%s notifyAction: %s id=%s, isSmartAction=%b",
provider.getClass(), action, screenshotId, isSmartAction));
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
index f703058f4a0f..45af1874e9db 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsReceiver.java
@@ -60,7 +60,7 @@ public class SmartActionsReceiver extends BroadcastReceiver {
}
mScreenshotSmartActions.notifyScreenshotAction(
- context, intent.getStringExtra(EXTRA_ID), actionType, true,
+ intent.getStringExtra(EXTRA_ID), actionType, true,
pendingIntent.getIntent());
}
}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java b/packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt
index 73fd6931066d..aa218dbfdd43 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellCommandHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManager.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,26 +14,26 @@
* limitations under the License.
*/
-package com.android.wm.shell;
+package com.android.systemui.settings
-import com.android.wm.shell.common.annotations.ExternalThread;
-
-import java.io.PrintWriter;
+import android.content.Context
+import android.content.SharedPreferences
+import java.io.File
/**
- * An entry point into the shell for dumping shell internal state and running adb commands.
- *
- * Use with {@code adb shell dumpsys activity service SystemUIService WMShell ...}.
+ * Interface for retrieving file paths for file storage of system and secondary users.
*/
-@ExternalThread
-public interface ShellCommandHandler {
+interface UserFileManager {
/**
- * Dumps the shell state.
+ * Return the file based on current user.
*/
- void dump(PrintWriter pw);
-
+ fun getFile(fileName: String, userId: Int): File
/**
- * Handles a shell command.
+ * Get shared preferences from user.
*/
- boolean handleCommand(final String[] args, PrintWriter pw);
+ fun getSharedPreferences(
+ fileName: String,
+ @Context.PreferencesMode mode: Int,
+ userId: Int
+ ): SharedPreferences
}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
new file mode 100644
index 000000000000..ad073c073ed8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserFileManagerImpl.kt
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.SharedPreferences
+import android.os.Environment
+import android.os.UserHandle
+import android.os.UserManager
+import android.util.Log
+import androidx.annotation.VisibleForTesting
+import com.android.systemui.CoreStartable
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.concurrency.DelayableExecutor
+import java.io.File
+import javax.inject.Inject
+
+/**
+ * Implementation for retrieving file paths for file storage of system and secondary users.
+ * Files lie in {File Directory}/UserFileManager/{User Id} for secondary user.
+ * For system user, we use the conventional {File Directory}
+ */
+@SysUISingleton
+class UserFileManagerImpl @Inject constructor(
+ // Context of system process and system user.
+ val context: Context,
+ val userManager: UserManager,
+ val broadcastDispatcher: BroadcastDispatcher,
+ @Background val backgroundExecutor: DelayableExecutor
+) : UserFileManager, CoreStartable(context) {
+ companion object {
+ private const val FILES = "files"
+ @VisibleForTesting internal const val SHARED_PREFS = "shared_prefs"
+ @VisibleForTesting internal const val ID = "UserFileManager"
+ }
+
+ private val broadcastReceiver = object : BroadcastReceiver() {
+ /**
+ * Listen to Intent.ACTION_USER_REMOVED to clear user data.
+ */
+ override fun onReceive(context: Context, intent: Intent) {
+ if (intent.action == Intent.ACTION_USER_REMOVED) {
+ clearDeletedUserData()
+ }
+ }
+ }
+
+ /**
+ * Poll for user-specific directories to delete upon start up.
+ */
+ override fun start() {
+ clearDeletedUserData()
+ val filter = IntentFilter().apply {
+ addAction(Intent.ACTION_USER_REMOVED)
+ }
+ broadcastDispatcher.registerReceiver(broadcastReceiver, filter, backgroundExecutor)
+ }
+
+ /**
+ * Return the file based on current user.
+ */
+ override fun getFile(fileName: String, userId: Int): File {
+ return if (UserHandle(userId).isSystem) {
+ Environment.buildPath(
+ context.filesDir,
+ fileName
+ )
+ } else {
+ val secondaryFile = Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ FILES,
+ fileName
+ )
+ ensureParentDirExists(secondaryFile)
+ secondaryFile
+ }
+ }
+
+ /**
+ * Get shared preferences from user.
+ */
+ override fun getSharedPreferences(
+ fileName: String,
+ @Context.PreferencesMode mode: Int,
+ userId: Int
+ ): SharedPreferences {
+ if (UserHandle(userId).isSystem) {
+ return context.getSharedPreferences(fileName, mode)
+ }
+ val secondaryUserDir = Environment.buildPath(
+ context.filesDir,
+ ID,
+ userId.toString(),
+ SHARED_PREFS,
+ fileName
+ )
+
+ ensureParentDirExists(secondaryUserDir)
+ return context.getSharedPreferences(secondaryUserDir, mode)
+ }
+
+ /**
+ * Remove dirs for deleted users.
+ */
+ @VisibleForTesting
+ internal fun clearDeletedUserData() {
+ backgroundExecutor.execute {
+ val file = Environment.buildPath(context.filesDir, ID)
+ if (!file.exists()) return@execute
+ val aliveUsers = userManager.aliveUsers.map { it.id.toString() }
+ val dirsToDelete = file.list().filter { !aliveUsers.contains(it) }
+
+ dirsToDelete.forEach { dir ->
+ try {
+ val dirToDelete = Environment.buildPath(
+ file,
+ dir,
+ )
+ dirToDelete.deleteRecursively()
+ } catch (e: Exception) {
+ Log.e(ID, "Deletion failed.", e)
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks to see if parent dir of the file exists. If it does not, we create the parent dirs
+ * recursively.
+ */
+ @VisibleForTesting
+ internal fun ensureParentDirExists(file: File) {
+ val parent = file.parentFile
+ if (!parent.exists()) {
+ if (!parent.mkdirs()) {
+ Log.e(ID, "Could not create parent directory for file: ${file.absolutePath}")
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
index 7084d3ffc9ff..2f62e44ba4c4 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/MultiUserUtilsModule.java
@@ -21,25 +21,28 @@ import android.content.Context;
import android.os.Handler;
import android.os.UserManager;
+import com.android.systemui.CoreStartable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.settings.UserContentResolverProvider;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.settings.UserFileManager;
+import com.android.systemui.settings.UserFileManagerImpl;
import com.android.systemui.settings.UserTracker;
import com.android.systemui.settings.UserTrackerImpl;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.ClassKey;
+import dagger.multibindings.IntoMap;
/**
* Dagger Module for classes found within the com.android.systemui.settings package.
*/
@Module
-public abstract class SettingsModule {
-
-
+public abstract class MultiUserUtilsModule {
@Binds
@SysUISingleton
abstract UserContextProvider bindUserContextProvider(UserTracker tracker);
@@ -62,4 +65,12 @@ public abstract class SettingsModule {
tracker.initialize(startingUser);
return tracker;
}
+
+ @Binds
+ @IntoMap
+ @ClassKey(UserFileManagerImpl.class)
+ abstract CoreStartable bindUserFileManagerCoreStartable(UserFileManagerImpl sysui);
+
+ @Binds
+ abstract UserFileManager bindUserFileManager(UserFileManagerImpl impl);
}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
new file mode 100644
index 000000000000..e360ec20bd9b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManager.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import androidx.constraintlayout.widget.ConstraintSet
+
+typealias ConstraintChange = ConstraintSet.() -> Unit
+
+operator fun ConstraintChange?.plus(other: ConstraintChange?): ConstraintChange? {
+ // Prevent wrapping
+ if (this == null) return other
+ if (other == null) return this
+ else return {
+ this@plus()
+ other()
+ }
+}
+
+/**
+ * Contains all changes that need to be performed to the different [ConstraintSet] in
+ * [LargeScreenShadeHeaderController].
+ */
+data class ConstraintsChanges(
+ val qqsConstraintsChanges: ConstraintChange? = null,
+ val qsConstraintsChanges: ConstraintChange? = null,
+ val largeScreenConstraintsChanges: ConstraintChange? = null
+) {
+ operator fun plus(other: ConstraintsChanges) = ConstraintsChanges(
+ qqsConstraintsChanges + other.qqsConstraintsChanges,
+ qsConstraintsChanges + other.qsConstraintsChanges,
+ largeScreenConstraintsChanges + other.largeScreenConstraintsChanges
+ )
+}
+
+/**
+ * Determines [ConstraintChanges] for [LargeScreenShadeHeaderController] based on configurations.
+ *
+ * Given that the number of different scenarios is not that large, having specific methods instead
+ * of a full map between state and [ConstraintSet] was preferred.
+ */
+interface CombinedShadeHeadersConstraintManager {
+ /**
+ * Changes for when the visibility of the privacy chip changes
+ */
+ fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges
+
+ /**
+ * Changes for situations with no top center cutout (there may be a corner cutout)
+ */
+ fun emptyCutoutConstraints(): ConstraintsChanges
+
+ /**
+ * Changes to incorporate side insets due to rounded corners/corner cutouts
+ */
+ fun edgesGuidelinesConstraints(
+ cutoutStart: Int,
+ paddingStart: Int,
+ cutoutEnd: Int,
+ paddingEnd: Int
+ ): ConstraintsChanges
+
+ /**
+ * Changes for situations with top center cutout (in this case, there are no corner cutouts).
+ */
+ fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
new file mode 100644
index 000000000000..4063af3cbc36
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/CombinedShadeHeadersConstraintManagerImpl.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.view.ViewGroup
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.R
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+
+/**
+ * Standard implementation of [CombinedShadeHeadersConstraintManager].
+ */
+@CentralSurfacesComponent.CentralSurfacesScope
+object CombinedShadeHeadersConstraintManagerImpl : CombinedShadeHeadersConstraintManager {
+
+ override fun privacyChipVisibilityConstraints(visible: Boolean): ConstraintsChanges {
+ val constraintAlpha = if (visible) 0f else 1f
+ return ConstraintsChanges(
+ qqsConstraintsChanges = {
+ setAlpha(R.id.statusIcons, constraintAlpha)
+ setAlpha(R.id.batteryRemainingIcon, constraintAlpha)
+ }
+ )
+ }
+
+ override fun emptyCutoutConstraints(): ConstraintsChanges {
+ return ConstraintsChanges(
+ qqsConstraintsChanges = {
+ connect(R.id.date, ConstraintSet.END, R.id.barrier, ConstraintSet.START)
+ createBarrier(
+ R.id.barrier,
+ ConstraintSet.START,
+ 0,
+ R.id.statusIcons,
+ R.id.privacy_container
+ )
+ connect(R.id.statusIcons, ConstraintSet.START, R.id.date, ConstraintSet.END)
+ connect(R.id.privacy_container, ConstraintSet.START, R.id.date, ConstraintSet.END)
+ constrainWidth(R.id.statusIcons, ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+ )
+ }
+
+ override fun edgesGuidelinesConstraints(
+ cutoutStart: Int,
+ paddingStart: Int,
+ cutoutEnd: Int,
+ paddingEnd: Int
+ ): ConstraintsChanges {
+ val change: ConstraintChange = {
+ setGuidelineBegin(R.id.begin_guide, Math.max(cutoutStart - paddingStart, 0))
+ setGuidelineEnd(R.id.end_guide, Math.max(cutoutEnd - paddingEnd, 0))
+ }
+ return ConstraintsChanges(
+ qqsConstraintsChanges = change,
+ qsConstraintsChanges = change
+ )
+ }
+
+ override fun centerCutoutConstraints(rtl: Boolean, offsetFromEdge: Int): ConstraintsChanges {
+ val centerStart = if (!rtl) R.id.center_left else R.id.center_right
+ val centerEnd = if (!rtl) R.id.center_right else R.id.center_left
+ // Use guidelines to block the center cutout area.
+ return ConstraintsChanges(
+ qqsConstraintsChanges = {
+ setGuidelineBegin(centerStart, offsetFromEdge)
+ setGuidelineEnd(centerEnd, offsetFromEdge)
+ connect(R.id.date, ConstraintSet.END, centerStart, ConstraintSet.START)
+ connect(
+ R.id.statusIcons,
+ ConstraintSet.START,
+ centerEnd,
+ ConstraintSet.END
+ )
+ connect(
+ R.id.privacy_container,
+ ConstraintSet.START,
+ centerEnd,
+ ConstraintSet.END
+ )
+ constrainWidth(R.id.statusIcons, 0)
+ },
+ qsConstraintsChanges = {
+ setGuidelineBegin(centerStart, offsetFromEdge)
+ setGuidelineEnd(centerEnd, offsetFromEdge)
+ connect(
+ R.id.privacy_container,
+ ConstraintSet.START,
+ centerEnd,
+ ConstraintSet.END
+ )
+ }
+ )
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
new file mode 100644
index 000000000000..5793105e481e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/LargeScreenShadeHeaderController.kt
@@ -0,0 +1,510 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.annotation.IdRes
+import android.app.StatusBarManager
+import android.content.res.Configuration
+import android.os.Trace
+import android.os.Trace.TRACE_TAG_APP
+import android.util.Pair
+import android.view.View
+import android.view.WindowInsets
+import android.widget.TextView
+import androidx.annotation.VisibleForTesting
+import androidx.constraintlayout.motion.widget.MotionLayout
+import com.android.settingslib.Utils
+import com.android.systemui.Dumpable
+import com.android.systemui.R
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
+import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.qs.carrier.QSCarrierGroup
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
+import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.ViewController
+import java.io.PrintWriter
+import javax.inject.Inject
+import javax.inject.Named
+
+/**
+ * Controller for QS header on Large Screen width (large screen + landscape).
+ *
+ * Additionally, this serves as the staging ground for the combined QS headers. A single
+ * [MotionLayout] that changes constraints depending on the configuration and can animate the
+ * expansion of the headers in small screen portrait.
+ *
+ * [header] will be a [MotionLayout] if [Flags.COMBINED_QS_HEADERS] is enabled. In this case, the
+ * [MotionLayout] has 2 transitions:
+ * * [HEADER_TRANSITION_ID]: [QQS_HEADER_CONSTRAINT] <-> [QS_HEADER_CONSTRAINT] for portrait
+ * handheld device configuration.
+ * * [LARGE_SCREEN_HEADER_TRANSITION_ID]: [LARGE_SCREEN_HEADER_CONSTRAINT] (to itself) for all
+ * other configurations
+ */
+@CentralSurfacesScope
+class LargeScreenShadeHeaderController @Inject constructor(
+ @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
+ private val statusBarIconController: StatusBarIconController,
+ private val privacyIconsController: HeaderPrivacyIconsController,
+ private val insetsProvider: StatusBarContentInsetsProvider,
+ private val configurationController: ConfigurationController,
+ private val variableDateViewControllerFactory: VariableDateViewController.Factory,
+ @Named(LARGE_SCREEN_BATTERY_CONTROLLER)
+ private val batteryMeterViewController: BatteryMeterViewController,
+ private val dumpManager: DumpManager,
+ private val featureFlags: FeatureFlags,
+ private val qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
+ private val combinedShadeHeadersConstraintManager: CombinedShadeHeadersConstraintManager
+) : ViewController<View>(header), Dumpable {
+
+ companion object {
+ /** IDs for transitions and constraints for the [MotionLayout]. These are only used when
+ * [Flags.COMBINED_QS_HEADERS] is enabled.
+ */
+ @VisibleForTesting
+ internal val HEADER_TRANSITION_ID = R.id.header_transition
+ @VisibleForTesting
+ internal val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
+ @VisibleForTesting
+ internal val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
+ @VisibleForTesting
+ internal val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
+ @VisibleForTesting
+ internal val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
+
+ private fun Int.stateToString() = when (this) {
+ QQS_HEADER_CONSTRAINT -> "QQS Header"
+ QS_HEADER_CONSTRAINT -> "QS Header"
+ LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
+ else -> "Unknown state"
+ }
+ }
+
+ init {
+ loadConstraints()
+ }
+
+ private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
+
+ private lateinit var iconManager: StatusBarIconController.TintedIconManager
+ private lateinit var carrierIconSlots: List<String>
+ private lateinit var qsCarrierGroupController: QSCarrierGroupController
+
+ private val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
+ private val clock: TextView = header.findViewById(R.id.clock)
+ private val date: TextView = header.findViewById(R.id.date)
+ private val iconContainer: StatusIconContainer = header.findViewById(R.id.statusIcons)
+ private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
+
+ private var cutoutLeft = 0
+ private var cutoutRight = 0
+ private var roundedCorners = 0
+ private var lastInsets: WindowInsets? = null
+
+ private var qsDisabled = false
+ private var visible = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ updateListeners()
+ }
+
+ /**
+ * Whether the QQS/QS part of the shade is visible. This is particularly important in
+ * Lockscreen, as the shade is visible but QS is not.
+ */
+ var qsVisible = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ onShadeExpandedChanged()
+ }
+
+ /**
+ * Whether we are in a configuration with large screen width. In this case, the header is a
+ * single line.
+ */
+ var largeScreenActive = false
+ set(value) {
+ if (field == value) {
+ return
+ }
+ field = value
+ onHeaderStateChanged()
+ }
+
+ /**
+ * Expansion fraction of the QQS/QS shade. This is not the expansion between QQS <-> QS.
+ */
+ var shadeExpandedFraction = -1f
+ set(value) {
+ if (visible && field != value) {
+ header.alpha = ShadeInterpolation.getContentAlpha(value)
+ field = value
+ }
+ }
+
+ /**
+ * Expansion fraction of the QQS <-> QS animation.
+ */
+ var qsExpandedFraction = -1f
+ set(value) {
+ if (visible && field != value) {
+ field = value
+ updatePosition()
+ }
+ }
+
+ /**
+ * Current scroll of QS.
+ */
+ var qsScrollY = 0
+ set(value) {
+ if (field != value) {
+ field = value
+ updateScrollY()
+ }
+ }
+
+ private val insetListener = View.OnApplyWindowInsetsListener { view, insets ->
+ updateConstraintsForInsets(view as MotionLayout, insets)
+ lastInsets = WindowInsets(insets)
+
+ view.onApplyWindowInsets(insets)
+ }
+
+ private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
+ override fun onChipVisibilityRefreshed(visible: Boolean) {
+ if (header is MotionLayout) {
+ // If the privacy chip is visible, we hide the status icons and battery remaining
+ // icon, only in QQS.
+ val update = combinedShadeHeadersConstraintManager
+ .privacyChipVisibilityConstraints(visible)
+ header.updateAllConstraints(update)
+ }
+ }
+ }
+
+ private val configurationControllerListener =
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ if (header !is MotionLayout) {
+ val left = header.resources.getDimensionPixelSize(
+ R.dimen.large_screen_shade_header_left_padding
+ )
+ header.setPadding(
+ left,
+ header.paddingTop,
+ header.paddingRight,
+ header.paddingBottom
+ )
+ }
+ }
+
+ override fun onDensityOrFontScaleChanged() {
+ clock.setTextAppearance(R.style.TextAppearance_QS_Status)
+ date.setTextAppearance(R.style.TextAppearance_QS_Status)
+ qsCarrierGroup.updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
+ if (header is MotionLayout) {
+ loadConstraints()
+ lastInsets?.let { updateConstraintsForInsets(header, it) }
+ }
+ updateResources()
+ }
+ }
+
+ override fun onInit() {
+ if (header is MotionLayout) {
+ variableDateViewControllerFactory.create(date as VariableDateView).init()
+ }
+ batteryMeterViewController.init()
+
+ // battery settings same as in QS icons
+ batteryMeterViewController.ignoreTunerUpdates()
+ batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+ iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
+ iconManager.setTint(
+ Utils.getColorAttrDefaultColor(header.context, android.R.attr.textColorPrimary)
+ )
+
+ carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
+ listOf(
+ header.context.getString(com.android.internal.R.string.status_bar_no_calling),
+ header.context.getString(com.android.internal.R.string.status_bar_call_strength)
+ )
+ } else {
+ listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
+ }
+ qsCarrierGroupController = qsCarrierGroupControllerBuilder
+ .setQSCarrierGroup(qsCarrierGroup)
+ .build()
+ }
+
+ override fun onViewAttached() {
+ privacyIconsController.chipVisibilityListener = chipVisibilityListener
+ if (header is MotionLayout) {
+ header.setOnApplyWindowInsetsListener(insetListener)
+ clock.addOnLayoutChangeListener { v, _, _, _, _, _, _, _, _ ->
+ val newPivot = if (v.isLayoutRtl) v.width.toFloat() else 0f
+ v.pivotX = newPivot
+ }
+ }
+
+ dumpManager.registerDumpable(this)
+ configurationController.addCallback(configurationControllerListener)
+
+ updateVisibility()
+ updateTransition()
+ }
+
+ override fun onViewDetached() {
+ privacyIconsController.chipVisibilityListener = null
+ dumpManager.unregisterDumpable(this::class.java.simpleName)
+ configurationController.removeCallback(configurationControllerListener)
+ }
+
+ fun disable(state1: Int, state2: Int, animate: Boolean) {
+ val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
+ if (disabled == qsDisabled) return
+ qsDisabled = disabled
+ updateVisibility()
+ }
+
+ private fun loadConstraints() {
+ if (header is MotionLayout) {
+ // Use resources.getXml instead of passing the resource id due to bug b/205018300
+ header.getConstraintSet(QQS_HEADER_CONSTRAINT)
+ .load(context, resources.getXml(R.xml.qqs_header))
+ val qsConstraints = if (featureFlags.isEnabled(Flags.NEW_HEADER)) {
+ R.xml.qs_header_new
+ } else {
+ R.xml.qs_header
+ }
+ header.getConstraintSet(QS_HEADER_CONSTRAINT)
+ .load(context, resources.getXml(qsConstraints))
+ header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
+ .load(context, resources.getXml(R.xml.large_screen_shade_header))
+ }
+ }
+
+ private fun updateConstraintsForInsets(view: MotionLayout, insets: WindowInsets) {
+ val cutout = insets.displayCutout
+
+ val sbInsets: Pair<Int, Int> = insetsProvider.getStatusBarContentInsetsForCurrentRotation()
+ cutoutLeft = sbInsets.first
+ cutoutRight = sbInsets.second
+ val hasCornerCutout: Boolean = insetsProvider.currentRotationHasCornerCutout()
+ updateQQSPaddings()
+ // Set these guides as the left/right limits for content that lives in the top row, using
+ // cutoutLeft and cutoutRight
+ var changes = combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(
+ if (view.isLayoutRtl) cutoutRight else cutoutLeft,
+ header.paddingStart,
+ if (view.isLayoutRtl) cutoutLeft else cutoutRight,
+ header.paddingEnd
+ )
+
+ if (cutout != null) {
+ val topCutout = cutout.boundingRectTop
+ if (topCutout.isEmpty || hasCornerCutout) {
+ changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
+ } else {
+ changes += combinedShadeHeadersConstraintManager.centerCutoutConstraints(
+ view.isLayoutRtl,
+ (view.width - view.paddingLeft - view.paddingRight - topCutout.width()) / 2
+ )
+ }
+ } else {
+ changes += combinedShadeHeadersConstraintManager.emptyCutoutConstraints()
+ }
+
+ view.updateAllConstraints(changes)
+ }
+
+ private fun updateScrollY() {
+ if (!largeScreenActive && combinedHeaders) {
+ header.scrollY = qsScrollY
+ }
+ }
+
+ private fun onShadeExpandedChanged() {
+ if (qsVisible) {
+ privacyIconsController.startListening()
+ } else {
+ privacyIconsController.stopListening()
+ }
+ updateVisibility()
+ updatePosition()
+ }
+
+ private fun onHeaderStateChanged() {
+ if (largeScreenActive || combinedHeaders) {
+ privacyIconsController.onParentVisible()
+ } else {
+ privacyIconsController.onParentInvisible()
+ }
+ updateVisibility()
+ updateTransition()
+ }
+
+ /**
+ * If not using [combinedHeaders] this should only be visible on large screen. Else, it should
+ * be visible any time the QQS/QS shade is open.
+ */
+ private fun updateVisibility() {
+ val visibility = if (!largeScreenActive && !combinedHeaders || qsDisabled) {
+ View.GONE
+ } else if (qsVisible) {
+ View.VISIBLE
+ } else {
+ View.INVISIBLE
+ }
+ if (header.visibility != visibility) {
+ header.visibility = visibility
+ visible = visibility == View.VISIBLE
+ }
+ }
+
+ private fun updateTransition() {
+ if (!combinedHeaders) {
+ return
+ }
+ header as MotionLayout
+ if (largeScreenActive) {
+ header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+ header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT).applyTo(header)
+ } else {
+ header.setTransition(HEADER_TRANSITION_ID)
+ header.transitionToStart()
+ updatePosition()
+ updateScrollY()
+ }
+ }
+
+ private fun updatePosition() {
+ if (header is MotionLayout && !largeScreenActive && visible) {
+ Trace.instantForTrack(
+ TRACE_TAG_APP,
+ "LargeScreenHeaderController - updatePosition",
+ "position: $qsExpandedFraction"
+ )
+ header.progress = qsExpandedFraction
+ }
+ }
+
+ private fun updateListeners() {
+ qsCarrierGroupController.setListening(visible)
+ if (visible) {
+ updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
+ qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
+ statusBarIconController.addIconGroup(iconManager)
+ } else {
+ qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
+ statusBarIconController.removeIconGroup(iconManager)
+ }
+ }
+
+ private fun updateSingleCarrier(singleCarrier: Boolean) {
+ if (singleCarrier) {
+ iconContainer.removeIgnoredSlots(carrierIconSlots)
+ } else {
+ iconContainer.addIgnoredSlots(carrierIconSlots)
+ }
+ }
+
+ private fun updateResources() {
+ roundedCorners = resources.getDimensionPixelSize(R.dimen.rounded_corner_content_padding)
+ val padding = resources.getDimensionPixelSize(R.dimen.qs_panel_padding)
+ header.setPadding(padding, header.paddingTop, padding, header.paddingBottom)
+ updateQQSPaddings()
+ }
+
+ private fun updateQQSPaddings() {
+ if (header is MotionLayout) {
+ val clockPaddingStart = resources
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_starting_padding)
+ val clockPaddingEnd = resources
+ .getDimensionPixelSize(R.dimen.status_bar_left_clock_end_padding)
+ clock.setPaddingRelative(
+ clockPaddingStart,
+ clock.paddingTop,
+ clockPaddingEnd,
+ clock.paddingBottom
+ )
+ }
+ }
+
+ override fun dump(pw: PrintWriter, args: Array<out String>) {
+ pw.println("visible: $visible")
+ pw.println("shadeExpanded: $qsVisible")
+ pw.println("shadeExpandedFraction: $shadeExpandedFraction")
+ pw.println("active: $largeScreenActive")
+ pw.println("qsExpandedFraction: $qsExpandedFraction")
+ pw.println("qsScrollY: $qsScrollY")
+ if (combinedHeaders) {
+ header as MotionLayout
+ pw.println("currentState: ${header.currentState.stateToString()}")
+ }
+ }
+
+ private fun MotionLayout.updateConstraints(@IdRes state: Int, update: ConstraintChange) {
+ val constraints = getConstraintSet(state)
+ constraints.update()
+ updateState(state, constraints)
+ }
+
+ /**
+ * Updates the [ConstraintSet] for the case of combined headers.
+ *
+ * Only non-`null` changes are applied to reduce the number of rebuilding in the [MotionLayout].
+ */
+ private fun MotionLayout.updateAllConstraints(updates: ConstraintsChanges) {
+ if (updates.qqsConstraintsChanges != null) {
+ updateConstraints(QQS_HEADER_CONSTRAINT, updates.qqsConstraintsChanges)
+ }
+ if (updates.qsConstraintsChanges != null) {
+ updateConstraints(QS_HEADER_CONSTRAINT, updates.qsConstraintsChanges)
+ }
+ if (updates.largeScreenConstraintsChanges != null) {
+ updateConstraints(LARGE_SCREEN_HEADER_CONSTRAINT, updates.largeScreenConstraintsChanges)
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
index d44a56942065..07e8b9fe3123 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NPVCDownEventState.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NPVCDownEventState.kt
@@ -11,7 +11,7 @@
* KIND, either express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
import android.view.MotionEvent
import com.android.systemui.dump.DumpsysTableLogger
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
index a385e22c1aff..ce9d89f89ae1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
/** Provides certain notification panel events. */
interface NotifPanelEvents {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
index 2aaf6a5f4391..67723843086a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import com.android.systemui.dagger.SysUISingleton;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
index ff48755f750a..e0cd482166b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelUnfoldAnimationController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelUnfoldAnimationController.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
import android.content.Context
import android.view.ViewGroup
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
index d9ba494a4d63..9818af3751a7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.content.Context;
import android.graphics.Canvas;
@@ -25,6 +25,7 @@ import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.TapAgainView;
public class NotificationPanelView extends PanelView {
@@ -35,8 +36,8 @@ public class NotificationPanelView extends PanelView {
*/
public static final int FLING_EXPAND = 0;
- static final String COUNTER_PANEL_OPEN = "panel_open";
- static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+ public static final String COUNTER_PANEL_OPEN = "panel_open";
+ public static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
private int mCurrentPanelAlpha;
private final Paint mAlphaPaint = new Paint();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 3580fe6cdbc5..99d0fe97971a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
@@ -32,6 +32,7 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_N
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_CLOSED;
@@ -39,8 +40,6 @@ import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStat
import static com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManagerKt.STATE_OPENING;
import static com.android.systemui.util.DumpUtilsKt.asIndenting;
-import static java.lang.Float.isNaN;
-
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
@@ -49,15 +48,13 @@ import android.app.ActivityManager;
import android.app.Fragment;
import android.app.StatusBarManager;
import android.content.ContentResolver;
-import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Insets;
import android.graphics.Paint;
+import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.Region;
@@ -80,6 +77,7 @@ import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
+import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
@@ -116,6 +114,7 @@ import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.animation.LaunchAnimator;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -141,10 +140,10 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.GestureRecorder;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -178,12 +177,32 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardClockPositionAlgorithm;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.PhoneStatusBarView;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.TapAgainViewController;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.phone.panelstate.PanelState;
-import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -196,7 +215,6 @@ import com.android.systemui.util.Compile;
import com.android.systemui.util.LargeScreenUtils;
import com.android.systemui.util.ListenerSet;
import com.android.systemui.util.Utils;
-import com.android.systemui.util.settings.SecureSettings;
import com.android.systemui.util.time.SystemClock;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -208,14 +226,13 @@ import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.concurrent.Executor;
import java.util.function.Consumer;
import javax.inject.Inject;
import javax.inject.Provider;
@CentralSurfacesComponent.CentralSurfacesScope
-public class NotificationPanelViewController extends PanelViewController {
+public final class NotificationPanelViewController extends PanelViewController {
private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
@@ -251,9 +268,6 @@ public class NotificationPanelViewController extends PanelViewController {
private final OnOverscrollTopChangedListener
mOnOverscrollTopChangedListener =
new OnOverscrollTopChangedListener();
- private final KeyguardAffordanceHelperCallback
- mKeyguardAffordanceHelperCallback =
- new KeyguardAffordanceHelperCallback();
private final OnEmptySpaceClickListener
mOnEmptySpaceClickListener =
new OnEmptySpaceClickListener();
@@ -264,7 +278,8 @@ public class NotificationPanelViewController extends PanelViewController {
private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
private final SettingsChangeObserver mSettingsChangeObserver;
- @VisibleForTesting final StatusBarStateListener mStatusBarStateListener =
+ @VisibleForTesting
+ final StatusBarStateListener mStatusBarStateListener =
new StatusBarStateListener();
private final NotificationPanelView mView;
private final VibratorHelper mVibratorHelper;
@@ -337,23 +352,24 @@ public class NotificationPanelViewController extends PanelViewController {
// Current max allowed keyguard notifications determined by measuring the panel
private int mMaxAllowedKeyguardNotifications;
- private ViewGroup mPreviewContainer;
- private KeyguardAffordanceHelper mAffordanceHelper;
private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
private KeyguardStatusBarView mKeyguardStatusBar;
private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
- @VisibleForTesting QS mQs;
+ @VisibleForTesting
+ QS mQs;
private FrameLayout mQsFrame;
- private QsFrameTranslateController mQsFrameTranslateController;
+ private final QsFrameTranslateController mQsFrameTranslateController;
private KeyguardStatusViewController mKeyguardStatusViewController;
- private LockIconViewController mLockIconViewController;
+ private final LockIconViewController mLockIconViewController;
private NotificationsQuickSettingsContainer mNotificationContainerParent;
- private NotificationsQSContainerController mNotificationsQSContainerController;
+ private final NotificationsQSContainerController mNotificationsQSContainerController;
+ private final Provider<KeyguardBottomAreaViewController>
+ mKeyguardBottomAreaViewControllerProvider;
private boolean mAnimateNextPositionUpdate;
private float mQuickQsHeaderHeight;
- private ScreenOffAnimationController mScreenOffAnimationController;
- private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+ private final ScreenOffAnimationController mScreenOffAnimationController;
+ private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
private int mTrackingPointer;
private VelocityTracker mQsVelocityTracker;
@@ -406,12 +422,6 @@ public class NotificationPanelViewController extends PanelViewController {
private int mLargeScreenShadeHeaderHeight;
private int mSplitShadeNotificationsScrimMarginBottom;
- /**
- * Vertical overlap allowed between the bottom of the notification shelf and
- * the top of the lock icon or the under-display fingerprint sensor background.
- */
- private int mShelfAndLockIconOverlap;
-
private final KeyguardClockPositionAlgorithm
mClockPositionAlgorithm =
new KeyguardClockPositionAlgorithm();
@@ -426,7 +436,8 @@ public class NotificationPanelViewController extends PanelViewController {
* Determines if QS should be already expanded when expanding shade.
* Used for split shade, two finger gesture as well as accessibility shortcut to QS.
*/
- @VisibleForTesting boolean mQsExpandImmediate;
+ @VisibleForTesting
+ boolean mQsExpandImmediate;
private boolean mTwoFingerQsExpandPossible;
private String mHeaderDebugInfo;
@@ -436,8 +447,6 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private boolean mQsAnimatorExpand;
private boolean mIsLaunchTransitionFinished;
- private boolean mIsLaunchTransitionRunning;
- private Runnable mLaunchAnimationEndRunnable;
private boolean mOnlyAffordanceInThisMotion;
private ValueAnimator mQsSizeChangeAnimator;
@@ -450,16 +459,13 @@ public class NotificationPanelViewController extends PanelViewController {
private int mNavigationBarBottomHeight;
private boolean mExpandingFromHeadsUp;
private boolean mCollapsedOnDown;
- private int mPositionMinSideMargin;
private boolean mClosingWithAlphaFadeOut;
private boolean mHeadsUpAnimatingAway;
private boolean mLaunchingAffordance;
- private boolean mAffordanceHasPreview;
private final FalsingManager mFalsingManager;
private final FalsingCollector mFalsingCollector;
- private String mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- private Runnable mHeadsUpExistenceChangedRunnable = () -> {
+ private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
setHeadsUpAnimatingAway(false);
updatePanelExpansionAndVisibility();
};
@@ -469,9 +475,6 @@ public class NotificationPanelViewController extends PanelViewController {
private boolean mIsFullWidth;
private boolean mBlockingExpansionForCurrentTouch;
- // TODO (b/204204226): no longer needed once refactor is complete
- private final boolean mUseCombinedQSHeaders;
-
/**
* Following variables maintain state of events when input focus transfer may occur.
*/
@@ -490,7 +493,6 @@ public class NotificationPanelViewController extends PanelViewController {
private float mLinearDarkAmount;
private boolean mPulsing;
- private boolean mUserSetupComplete;
private boolean mHideIconsDuringLaunchAnimation = true;
private int mStackScrollerMeasuringPass;
/**
@@ -516,10 +518,10 @@ public class NotificationPanelViewController extends PanelViewController {
mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_OUT);
private final AnimationProperties mPanelAlphaInPropertiesAnimator =
new AnimationProperties().setDuration(200).setAnimationEndAction((property) -> {
- if (mPanelAlphaEndAction != null) {
- mPanelAlphaEndAction.run();
- }
- }).setCustomInterpolator(
+ if (mPanelAlphaEndAction != null) {
+ mPanelAlphaEndAction.run();
+ }
+ }).setCustomInterpolator(
mPanelAlphaAnimator.getProperty(), Interpolators.ALPHA_IN);
private final NotificationEntryManager mEntryManager;
@@ -528,19 +530,10 @@ public class NotificationPanelViewController extends PanelViewController {
private final MediaDataManager mMediaDataManager;
private final SysUiState mSysUiState;
- private NotificationShadeDepthController mDepthController;
- private int mDisplayId;
+ private final NotificationShadeDepthController mDepthController;
+ private final int mDisplayId;
- /**
- * Cache the resource id of the theme to avoid unnecessary work in onThemeChanged.
- *
- * onThemeChanged is forced when the theme might not have changed. So, to avoid unncessary
- * work, check the current id with the cached id.
- */
- private int mThemeResId;
private KeyguardIndicationController mKeyguardIndicationController;
- private int mShelfHeight;
- private int mDarkIconSize;
private int mHeadsUpInset;
private boolean mHeadsUpPinnedMode;
private boolean mAllowExpandForSmallExpansion;
@@ -646,6 +639,7 @@ public class NotificationPanelViewController extends PanelViewController {
private int mScrimCornerRadius;
private int mScreenCornerRadius;
private boolean mQSAnimatingHiddenFromCollapsed;
+ private boolean mUseLargeScreenShadeHeader;
private int mQsClipTop;
private int mQsClipBottom;
@@ -653,25 +647,29 @@ public class NotificationPanelViewController extends PanelViewController {
private final ContentResolver mContentResolver;
private float mMinFraction;
- private final Executor mUiExecutor;
- private final SecureSettings mSecureSettings;
-
- private KeyguardMediaController mKeyguardMediaController;
+ private final KeyguardMediaController mKeyguardMediaController;
private boolean mStatusViewCentered = true;
- private Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
- private Optional<NotificationPanelUnfoldAnimationController>
+ private final Optional<KeyguardUnfoldTransition> mKeyguardUnfoldTransition;
+ private final Optional<NotificationPanelUnfoldAnimationController>
mNotificationPanelUnfoldAnimationController;
+ /** The drag distance required to fully expand the split shade. */
+ private int mSplitShadeFullTransitionDistance;
+
private final NotificationListContainer mNotificationListContainer;
private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
private final NPVCDownEventState.Buffer mLastDownEvents;
- private View.AccessibilityDelegate mAccessibilityDelegate = new View.AccessibilityDelegate() {
+ private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
+ () -> mKeyguardBottomArea.setVisibility(View.GONE);
+
+ private final AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
@Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ public void onInitializeAccessibilityNodeInfo(View host,
+ AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
@@ -679,7 +677,8 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
+ if (action
+ == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
|| action
== AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
mStatusBarKeyguardViewManager.showBouncer(true);
@@ -702,9 +701,10 @@ public class NotificationPanelViewController extends PanelViewController {
}
};
+ private final CameraGestureHelper mCameraGestureHelper;
+
@Inject
public NotificationPanelViewController(NotificationPanelView view,
- @Main Resources resources,
@Main Handler handler,
LayoutInflater layoutInflater,
FeatureFlags featureFlags,
@@ -754,8 +754,6 @@ public class NotificationPanelViewController extends PanelViewController {
QuickAccessWalletController quickAccessWalletController,
QRCodeScannerController qrCodeScannerController,
RecordingController recordingController,
- @Main Executor uiExecutor,
- SecureSettings secureSettings,
LargeScreenShadeHeaderController largeScreenShadeHeaderController,
ScreenOffAnimationController screenOffAnimationController,
LockscreenGestureLogger lockscreenGestureLogger,
@@ -766,13 +764,15 @@ public class NotificationPanelViewController extends PanelViewController {
InteractionJankMonitor interactionJankMonitor,
QsFrameTranslateController qsFrameTranslateController,
SysUiState sysUiState,
+ Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
NotificationListContainer notificationListContainer,
PanelEventsEmitter panelEventsEmitter,
NotificationStackSizeCalculator notificationStackSizeCalculator,
UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
ShadeTransitionController shadeTransitionController,
- SystemClock systemClock) {
+ SystemClock systemClock,
+ CameraGestureHelper cameraGestureHelper) {
super(view,
falsingManager,
dozeLog,
@@ -806,6 +806,7 @@ public class NotificationPanelViewController extends PanelViewController {
mNotificationsQSContainerController = notificationsQSContainerController;
mNotificationListContainer = notificationListContainer;
mNotificationStackSizeCalculator = notificationStackSizeCalculator;
+ mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
mNotificationsQSContainerController.init();
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mNotificationIconAreaController = notificationIconAreaController;
@@ -839,8 +840,6 @@ public class NotificationPanelViewController extends PanelViewController {
mUserManager = userManager;
mMediaDataManager = mediaDataManager;
mTapAgainViewController = tapAgainViewController;
- mUiExecutor = uiExecutor;
- mSecureSettings = secureSettings;
mInteractionJankMonitor = interactionJankMonitor;
mSysUiState = sysUiState;
mPanelEventsEmitter = panelEventsEmitter;
@@ -850,7 +849,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
});
statusBarWindowStateController.addListener(this::onStatusBarWindowStateChanged);
- mThemeResId = mView.getContext().getThemeResId();
mKeyguardBypassController = bypassController;
mUpdateMonitor = keyguardUpdateMonitor;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
@@ -897,14 +895,14 @@ public class NotificationPanelViewController extends PanelViewController {
mView.getOverlay().add(new DebugDrawable());
}
- mKeyguardUnfoldTransition = unfoldComponent.map(c -> c.getKeyguardUnfoldTransition());
+ mKeyguardUnfoldTransition = unfoldComponent.map(
+ SysUIUnfoldComponent::getKeyguardUnfoldTransition);
mNotificationPanelUnfoldAnimationController = unfoldComponent.map(
SysUIUnfoldComponent::getNotificationPanelUnfoldAnimationController);
mQsFrameTranslateController = qsFrameTranslateController;
updateUserSwitcherFlags();
onFinishInflate();
- mUseCombinedQSHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS);
keyguardUnlockAnimationController.addKeyguardUnlockAnimationListener(
new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
@Override
@@ -956,6 +954,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
});
+ mCameraGestureHelper = cameraGestureHelper;
}
@VisibleForTesting
@@ -979,8 +978,8 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardStatusBarViewController =
mKeyguardStatusBarViewComponentFactory.build(
- mKeyguardStatusBar,
- mNotificationPanelViewStateProvider)
+ mKeyguardStatusBar,
+ mNotificationPanelViewStateProvider)
.getKeyguardStatusBarViewController();
mKeyguardStatusBarViewController.init();
@@ -1003,8 +1002,6 @@ public class NotificationPanelViewController extends PanelViewController {
mOnEmptySpaceClickListener);
addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
- mPreviewContainer = mView.findViewById(R.id.preview_container);
- mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
initBottomArea();
@@ -1028,7 +1025,6 @@ public class NotificationPanelViewController extends PanelViewController {
mView.setRtlChangeListener(layoutDirection -> {
if (layoutDirection != mOldLayoutDirection) {
- mAffordanceHelper.onRtlPropertiesChanged();
mOldLayoutDirection = layoutDirection;
}
});
@@ -1039,6 +1035,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
mTapAgainViewController.init();
+ mLargeScreenShadeHeaderController.init();
mKeyguardUnfoldTransition.ifPresent(u -> u.setup(mView));
mNotificationPanelUnfoldAnimationController.ifPresent(controller ->
controller.setup(mNotificationContainerParent));
@@ -1054,12 +1051,8 @@ public class NotificationPanelViewController extends PanelViewController {
mQsPeekHeight = mResources.getDimensionPixelSize(R.dimen.qs_peek_height);
mClockPositionAlgorithm.loadDimens(mResources);
mQsFalsingThreshold = mResources.getDimensionPixelSize(R.dimen.qs_falsing_threshold);
- mPositionMinSideMargin = mResources.getDimensionPixelSize(
- R.dimen.notification_panel_min_side_margin);
mIndicationBottomPadding = mResources.getDimensionPixelSize(
R.dimen.keyguard_indication_bottom_padding);
- mShelfHeight = mResources.getDimensionPixelSize(R.dimen.notification_shelf_height);
- mDarkIconSize = mResources.getDimensionPixelSize(R.dimen.status_bar_icon_drawing_size_dark);
int statusbarHeight = SystemBarUtils.getStatusBarHeight(mView.getContext());
mHeadsUpInset = statusbarHeight + mResources.getDimensionPixelSize(
R.dimen.heads_up_status_bar_padding);
@@ -1124,33 +1117,31 @@ public class NotificationPanelViewController extends PanelViewController {
private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
// TODO: this can be injected.
mCentralSurfaces = centralSurfaces;
- mKeyguardBottomArea.setCentralSurfaces(mCentralSurfaces);
}
public void updateResources() {
mSplitShadeNotificationsScrimMarginBottom =
mResources.getDimensionPixelSize(
R.dimen.split_shade_notifications_scrim_margin_bottom);
- mShelfAndLockIconOverlap =
- mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap);
-
final boolean newSplitShadeEnabled =
LargeScreenUtils.shouldUseSplitNotificationShade(mResources);
final boolean splitShadeChanged = mSplitShadeEnabled != newSplitShadeEnabled;
mSplitShadeEnabled = newSplitShadeEnabled;
- boolean useLargeScreenShadeHeader =
- LargeScreenUtils.shouldUseLargeScreenShadeHeader(mView.getResources());
if (mQs != null) {
mQs.setInSplitShade(mSplitShadeEnabled);
}
+
+ mUseLargeScreenShadeHeader =
+ LargeScreenUtils.shouldUseLargeScreenShadeHeader(mView.getResources());
+
mLargeScreenShadeHeaderHeight =
mResources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height);
- mQuickQsHeaderHeight = useLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
+ mQuickQsHeaderHeight = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
SystemBarUtils.getQuickQsOffsetHeight(mView.getContext());
- int topMargin = useLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
+ int topMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight :
mResources.getDimensionPixelSize(R.dimen.notification_panel_margin_top);
- mLargeScreenShadeHeaderController.setActive(useLargeScreenShadeHeader);
+ mLargeScreenShadeHeaderController.setLargeScreenActive(mUseLargeScreenShadeHeader);
mAmbientState.setStackTopMargin(topMargin);
mNotificationsQSContainerController.updateResources();
@@ -1159,15 +1150,31 @@ public class NotificationPanelViewController extends PanelViewController {
mKeyguardMediaController.refreshMediaPosition();
if (splitShadeChanged) {
- // when we switch from split shade to regular shade we want to enforce setting qs to
- // the default state: expanded for split shade and collapsed otherwise
- if (!isOnKeyguard() && mPanelExpanded) {
- setQsExpanded(mSplitShadeEnabled);
- }
- updateClockAppearance();
- updateQsState();
- mNotificationStackScrollLayoutController.updateFooter();
+ onSplitShadeEnabledChanged();
}
+
+ mSplitShadeFullTransitionDistance =
+ mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance);
+ }
+
+ private void onSplitShadeEnabledChanged() {
+ // when we switch between split shade and regular shade we want to enforce setting qs to
+ // the default state: expanded for split shade and collapsed otherwise
+ if (!isOnKeyguard() && mPanelExpanded) {
+ setQsExpanded(mSplitShadeEnabled);
+ }
+ if (isOnKeyguard() && mQsExpanded && mSplitShadeEnabled) {
+ // In single column keyguard - when you swipe from the top - QS is fully expanded and
+ // StatusBarState is KEYGUARD. That state doesn't make sense for split shade,
+ // where notifications are always visible and we effectively go to fully expanded
+ // shade, that is SHADE_LOCKED.
+ // Also we might just be switching from regular expanded shade, so we don't want
+ // to force state transition if it's already correct.
+ mStatusBarStateController.setState(StatusBarState.SHADE_LOCKED, /* force= */false);
+ }
+ updateClockAppearance();
+ updateQsState();
+ mNotificationStackScrollLayoutController.updateFooter();
}
private View reInflateStub(int viewId, int stubId, int layoutId, boolean enabled) {
@@ -1241,10 +1248,8 @@ public class NotificationPanelViewController extends PanelViewController {
int index = mView.indexOfChild(mKeyguardBottomArea);
mView.removeView(mKeyguardBottomArea);
KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
- mKeyguardBottomArea = (KeyguardBottomAreaView) mLayoutInflater.inflate(
- R.layout.keyguard_bottom_area, mView, false);
+ mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
mKeyguardBottomArea.initFrom(oldBottomArea);
- mKeyguardBottomArea.setPreviewContainer(mPreviewContainer);
mView.addView(mKeyguardBottomArea, index);
initBottomArea();
mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
@@ -1281,15 +1286,11 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void initBottomArea() {
- mAffordanceHelper = new KeyguardAffordanceHelper(
- mKeyguardAffordanceHelperCallback, mView.getContext(), mFalsingManager);
- mKeyguardBottomArea.setAffordanceHelper(mAffordanceHelper);
- mKeyguardBottomArea.setCentralSurfaces(mCentralSurfaces);
- mKeyguardBottomArea.setUserSetupComplete(mUserSetupComplete);
- mKeyguardBottomArea.setFalsingManager(mFalsingManager);
- mKeyguardBottomArea.initWallet(mQuickAccessWalletController);
- mKeyguardBottomArea.initControls(mControlsComponent);
- mKeyguardBottomArea.initQRCodeScanner(mQRCodeScannerController);
+ mKeyguardBottomArea.init(
+ mFalsingManager,
+ mQuickAccessWalletController,
+ mControlsComponent,
+ mQRCodeScannerController);
}
@VisibleForTesting
@@ -1299,7 +1300,7 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateMaxDisplayedNotifications(boolean recompute) {
if (recompute) {
- mMaxAllowedKeyguardNotifications = Math.max(computeMaxKeyguardNotifications(), 1);
+ setMaxDisplayedNotifications(Math.max(computeMaxKeyguardNotifications(), 1));
} else {
if (SPEW_LOGCAT) Log.d(TAG, "Skipping computeMaxKeyguardNotifications() by request");
}
@@ -1327,7 +1328,7 @@ public class NotificationPanelViewController extends PanelViewController {
private void updateGestureExclusionRect() {
Rect exclusionRect = calculateGestureExclusionRect();
- mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.EMPTY_LIST
+ mView.setSystemGestureExclusionRects(exclusionRect.isEmpty() ? Collections.emptyList()
: Collections.singletonList(exclusionRect));
}
@@ -1355,14 +1356,11 @@ public class NotificationPanelViewController extends PanelViewController {
mQsSizeChangeAnimator = ValueAnimator.ofInt(oldHeight, newHeight);
mQsSizeChangeAnimator.setDuration(300);
mQsSizeChangeAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mQsSizeChangeAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- requestScrollerTopPaddingUpdate(false /* animate */);
- requestPanelHeightUpdate();
- int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
- mQs.setHeightOverride(height);
- }
+ mQsSizeChangeAnimator.addUpdateListener(animation -> {
+ requestScrollerTopPaddingUpdate(false /* animate */);
+ requestPanelHeightUpdate();
+ int height = (int) mQsSizeChangeAnimator.getAnimatedValue();
+ mQs.setHeightOverride(height);
});
mQsSizeChangeAnimator.addListener(new AnimatorListenerAdapter() {
@Override
@@ -1647,14 +1645,9 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansionEnabled();
}
- @Override
public void resetViews(boolean animate) {
mIsLaunchTransitionFinished = false;
mBlockTouches = false;
- if (!mLaunchingAffordance) {
- mAffordanceHelper.reset(false);
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- }
mCentralSurfaces.getGutsManager().closeAndSaveGuts(true /* leavebehind */, true /* force */,
true /* controls */, -1 /* x */, -1 /* y */, true /* resetMenu */);
if (animate && !isFullyCollapsed()) {
@@ -1708,18 +1701,30 @@ public class NotificationPanelViewController extends PanelViewController {
setQsExpansion(mQsMinExpansionHeight);
}
+ @Override
+ @VisibleForTesting
+ protected void cancelHeightAnimator() {
+ super.cancelHeightAnimator();
+ }
+
public void cancelAnimation() {
mView.animate().cancel();
}
-
/**
* Animate QS closing by flinging it.
* If QS is expanded, it will collapse into QQS and stop.
+ * If in split shade, it will collapse the whole shade.
*
* @param animateAway Do not stop when QS becomes QQS. Fling until QS isn't visible anymore.
*/
public void animateCloseQs(boolean animateAway) {
+ if (mSplitShadeEnabled) {
+ collapsePanel(
+ /* animate= */true, /* delayed= */false, /* speedUpFactor= */1.0f);
+ return;
+ }
+
if (mQsExpansionAnimator != null) {
if (!mQsAnimatorExpand) {
return;
@@ -1912,12 +1917,8 @@ public class NotificationPanelViewController extends PanelViewController {
mListenForHeadsUp = mCollapsedOnDown && mHeadsUpManager.hasPinnedHeadsUp();
mAllowExpandForSmallExpansion = mExpectingSynthesizedDown;
mTouchSlopExceededBeforeDown = mExpectingSynthesizedDown;
- if (mExpectingSynthesizedDown) {
- mLastEventSynthesizedDown = true;
- } else {
- // down but not synthesized motion event.
- mLastEventSynthesizedDown = false;
- }
+ // When false, down but not synthesized motion event.
+ mLastEventSynthesizedDown = mExpectingSynthesizedDown;
mLastDownEvents.insert(
mSystemClock.currentTimeMillis(),
mDownX,
@@ -1942,7 +1943,6 @@ public class NotificationPanelViewController extends PanelViewController {
*
* @param downX the x location where the touch started
* @param downY the y location where the touch started
- *
* @return true if the panel could be collapsed because it stared on QQS
*/
private boolean canPanelCollapseOnQQS(float downX, float downY) {
@@ -1951,7 +1951,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
View header = mQs == null ? mKeyguardStatusBar : mQs.getHeader();
return downX >= mQsFrame.getX() && downX <= mQsFrame.getX() + mQsFrame.getWidth()
- && downY <= header.getBottom();
+ && downY <= header.getBottom();
}
@@ -2096,7 +2096,7 @@ public class NotificationPanelViewController extends PanelViewController {
return false;
}
return y <= mNotificationStackScrollLayoutController.getBottomMostNotificationBottom()
- || y <= mQs.getView().getY() + mQs.getView().getHeight();
+ || y <= mQs.getView().getY() + mQs.getView().getHeight();
}
private boolean isOpenQsEvent(MotionEvent event) {
@@ -2197,11 +2197,6 @@ public class NotificationPanelViewController extends PanelViewController {
return isFullyCollapsed() || mBarState != StatusBarState.SHADE;
}
- @Override
- protected boolean shouldGestureIgnoreXTouchSlop(float x, float y) {
- return !mAffordanceHelper.isOnAffordanceIcon(x, y);
- }
-
private void onQsTouch(MotionEvent event) {
int pointerIndex = event.findPointerIndex(mTrackingPointer);
if (pointerIndex < 0) {
@@ -2280,15 +2275,11 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void onQsExpansionStarted() {
- onQsExpansionStarted(0);
- }
-
- protected void onQsExpansionStarted(int overscrollAmount) {
cancelQsAnimation();
cancelHeightAnimator();
// Reset scroll position and apply that position to the expanded height.
- float height = mQsExpansionHeight - overscrollAmount;
+ float height = mQsExpansionHeight;
setQsExpansion(height);
requestPanelHeightUpdate();
mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
@@ -2300,7 +2291,8 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- @VisibleForTesting void setQsExpanded(boolean expanded) {
+ @VisibleForTesting
+ void setQsExpanded(boolean expanded) {
boolean changed = mQsExpanded != expanded;
if (changed) {
mQsExpanded = expanded;
@@ -2325,13 +2317,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable = new Runnable() {
- @Override
- public void run() {
- mKeyguardBottomArea.setVisibility(View.GONE);
- }
- };
-
private void setKeyguardBottomAreaVisibility(int statusBarState, boolean goingToFullShade) {
mKeyguardBottomArea.animate().cancel();
if (goingToFullShade) {
@@ -2437,7 +2422,7 @@ public class NotificationPanelViewController extends PanelViewController {
: getExpandedFraction();
mLargeScreenShadeHeaderController.setShadeExpandedFraction(shadeExpandedFraction);
mLargeScreenShadeHeaderController.setQsExpandedFraction(qsExpansionFraction);
- mLargeScreenShadeHeaderController.setShadeExpanded(mQsVisible);
+ mLargeScreenShadeHeaderController.setQsVisible(mQsVisible);
}
private void onStackYChanged(boolean shouldAnimate) {
@@ -2449,7 +2434,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
setQSClippingBounds();
}
- };
+ }
private void onNotificationScrolled(int newScrollPosition) {
updateQSExpansionEnabledAmbient();
@@ -2463,8 +2448,8 @@ public class NotificationPanelViewController extends PanelViewController {
}
/**
- * Updates scrim bounds, QS clipping, and KSV clipping as well based on the bounds of the shade
- * and QS state.
+ * Updates scrim bounds, QS clipping, notifications clipping and keyguard status view clipping
+ * as well based on the bounds of the shade and QS state.
*/
private void setQSClippingBounds() {
final int qsPanelBottomY = calculateQsBottomPosition(computeQsExpansionFraction());
@@ -2540,6 +2525,13 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
+ /**
+ * Applies clipping to quick settings, notifications layout and
+ * updates bounds of the notifications background (notifications scrim).
+ *
+ * The parameters are bounds of the notifications area rectangle, this function
+ * calculates bounds for the QS clipping based on the notifications bounds.
+ */
private void applyQSClippingBounds(int left, int top, int right, int bottom,
boolean qsVisible) {
if (!mAnimateNextNotificationBounds || mKeyguardStatusAreaClipBounds.isEmpty()) {
@@ -2607,7 +2599,7 @@ public class NotificationPanelViewController extends PanelViewController {
boolean pulseExpanding = mPulseExpansionHandler.isExpanding();
if (mTransitioningToFullShadeProgress > 0.0f || pulseExpanding
|| (mQsClippingAnimation != null
- && (mIsQsTranslationResetAnimator || mIsPulseExpansionResetAnimator))) {
+ && (mIsQsTranslationResetAnimator || mIsPulseExpansionResetAnimator))) {
if (pulseExpanding || mIsPulseExpansionResetAnimator) {
// qsTranslation should only be positive during pulse expansion because it's
// already translating in from the top
@@ -2626,8 +2618,8 @@ public class NotificationPanelViewController extends PanelViewController {
mQs.setFancyClipping(
mQsClipTop,
mQsClipBottom,
- radius, qsVisible
- && !mSplitShadeEnabled);
+ radius,
+ qsVisible && !mSplitShadeEnabled);
}
mKeyguardStatusViewController.setClipBounds(
clipStatusView ? mKeyguardStatusAreaClipBounds : null);
@@ -2675,12 +2667,10 @@ public class NotificationPanelViewController extends PanelViewController {
if (mTransitioningToFullShadeProgress > 0.0f) {
return mTransitionToFullShadeQSPosition;
} else {
- int qsBottomY = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
- if (qsExpansionFraction != 0.0) {
- qsBottomY = (int) MathUtils.lerp(
- qsBottomY, mQs.getDesiredHeight(), qsExpansionFraction);
- }
- return qsBottomY;
+ int qsBottomYFrom = (int) getHeaderTranslation() + mQs.getQsMinExpansionHeight();
+ int expandedTopMargin = mUseLargeScreenShadeHeader ? mLargeScreenShadeHeaderHeight : 0;
+ int qsBottomYTo = mQs.getDesiredHeight() + expandedTopMargin;
+ return (int) MathUtils.lerp(qsBottomYFrom, qsBottomYTo, qsExpansionFraction);
}
}
@@ -2699,8 +2689,8 @@ public class NotificationPanelViewController extends PanelViewController {
}
private float calculateNotificationsTopPadding() {
- if (mSplitShadeEnabled && !mKeyguardShowing) {
- return 0;
+ if (mSplitShadeEnabled) {
+ return mKeyguardShowing ? getKeyguardNotificationStaticPadding() : 0;
}
if (mKeyguardShowing && (mQsExpandImmediate
|| mIsExpanding && mQsExpandedWhenExpandingStarted)) {
@@ -2753,8 +2743,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
-
- protected void requestScrollerTopPaddingUpdate(boolean animate) {
+ private void requestScrollerTopPaddingUpdate(boolean animate) {
mNotificationStackScrollLayoutController.updateTopPadding(
calculateNotificationsTopPadding(), animate);
if (mKeyguardShowing && mKeyguardBypassController.getBypassEnabled()) {
@@ -2774,6 +2763,9 @@ public class NotificationPanelViewController extends PanelViewController {
mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
}
+ if (mSplitShadeEnabled) {
+ updateQsExpansionForLockscreenToShadeTransition(pxAmount);
+ }
float endPosition = 0;
if (pxAmount > 0.0f) {
if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
@@ -2813,6 +2805,18 @@ public class NotificationPanelViewController extends PanelViewController {
updateQsExpansion();
}
+ private void updateQsExpansionForLockscreenToShadeTransition(float pxAmount) {
+ float qsExpansion = 0;
+ if (pxAmount > 0.0f) {
+ qsExpansion = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight,
+ mLockscreenShadeTransitionController.getQSDragProgress());
+ }
+ // SHADE_LOCKED means transition is over and we don't want further updates
+ if (mBarState != SHADE_LOCKED) {
+ setQsExpansion(qsExpansion);
+ }
+ }
+
/**
* Notify the panel that the pulse expansion has finished and that we're going to the full
* shade
@@ -2887,7 +2891,7 @@ public class NotificationPanelViewController extends PanelViewController {
* @param onFinishRunnable Runnable to be executed at the end of animation.
* @param isClick If originated by click (different interpolator and duration.)
*/
- protected void flingSettings(float vel, int type, final Runnable onFinishRunnable,
+ private void flingSettings(float vel, int type, final Runnable onFinishRunnable,
boolean isClick) {
float target;
switch (type) {
@@ -2929,11 +2933,11 @@ public class NotificationPanelViewController extends PanelViewController {
if (oppositeDirection) {
animator.setDuration(350);
}
- animator.addUpdateListener(animation -> {
- setQsExpansion((Float) animation.getAnimatedValue());
- });
+ animator.addUpdateListener(
+ animation -> setQsExpansion((Float) animation.getAnimatedValue()));
animator.addListener(new AnimatorListenerAdapter() {
private boolean mIsCanceled;
+
@Override
public void onAnimationStart(Animator animation) {
notifyExpandingStarted();
@@ -3007,7 +3011,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
@Override
- protected int getMaxPanelHeight() {
+ public int getMaxPanelHeight() {
int min = mStatusBarMinHeight;
if (!(mBarState == KEYGUARD)
&& mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
@@ -3016,13 +3020,22 @@ public class NotificationPanelViewController extends PanelViewController {
}
int maxHeight;
if (mQsExpandImmediate || mQsExpanded || mIsExpanding && mQsExpandedWhenExpandingStarted
- || mPulsing) {
- maxHeight = calculatePanelHeightQsExpanded();
+ || mPulsing || mSplitShadeEnabled) {
+ if (mSplitShadeEnabled && mBarState == SHADE) {
+ // Max panel height is used to calculate the fraction of the shade expansion.
+ // Traditionally the value is based on the number of notifications.
+ // On split-shade, we want the required distance to be a specific and constant
+ // value, to make sure the expansion motion has the expected speed.
+ // We also only want this on non-lockscreen for now.
+ maxHeight = mSplitShadeFullTransitionDistance;
+ } else {
+ maxHeight = calculatePanelHeightQsExpanded();
+ }
} else {
maxHeight = calculatePanelHeightShade();
}
maxHeight = Math.max(min, maxHeight);
- if (maxHeight == 0 || isNaN(maxHeight)) {
+ if (maxHeight == 0) {
Log.wtf(TAG, "maxPanelHeight is invalid. mOverExpansion: "
+ mOverExpansion + ", calculatePanelHeightQsExpanded: "
+ calculatePanelHeightQsExpanded() + ", calculatePanelHeightShade: "
@@ -3102,7 +3115,7 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- boolean isPanelExpanded() {
+ public boolean isPanelExpanded() {
return mPanelExpanded;
}
@@ -3187,7 +3200,7 @@ public class NotificationPanelViewController extends PanelViewController {
updateQsExpansion();
}
- protected float getHeaderTranslation() {
+ private float getHeaderTranslation() {
if (mBarState == KEYGUARD && !mKeyguardBypassController.getBypassEnabled()) {
return -mQs.getQsMinExpansionHeight();
}
@@ -3218,14 +3231,7 @@ public class NotificationPanelViewController extends PanelViewController {
getExpandedFraction());
float alpha = Math.min(expansionAlpha, 1 - computeQsExpansionFraction());
alpha *= mBottomAreaShadeAlpha;
- mKeyguardBottomArea.setAffordanceAlpha(alpha);
- mKeyguardBottomArea.setImportantForAccessibility(
- alpha == 0f ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
- : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- View ambientIndicationContainer = mCentralSurfaces.getAmbientIndicationContainer();
- if (ambientIndicationContainer != null) {
- ambientIndicationContainer.setAlpha(alpha);
- }
+ mKeyguardBottomArea.setComponentAlphas(alpha);
mLockIconViewController.setAlpha(alpha);
}
@@ -3257,22 +3263,13 @@ public class NotificationPanelViewController extends PanelViewController {
mMediaHierarchyManager.setCollapsingShadeFromQS(false);
mMediaHierarchyManager.setQsExpanded(mQsExpanded);
if (isFullyCollapsed()) {
- DejankUtils.postAfterTraversal(new Runnable() {
- @Override
- public void run() {
- setListening(false);
- }
- });
+ DejankUtils.postAfterTraversal(() -> setListening(false));
// Workaround b/22639032: Make sure we invalidate something because else RenderThread
// thinks we are actually drawing a frame put in reality we don't, so RT doesn't go
// ahead with rendering and we jank.
- mView.postOnAnimation(new Runnable() {
- @Override
- public void run() {
- mView.getParent().invalidateChild(mView, M_DUMMY_DIRTY_RECT);
- }
- });
+ mView.postOnAnimation(
+ () -> mView.getParent().invalidateChild(mView, M_DUMMY_DIRTY_RECT));
} else {
setListening(true);
}
@@ -3337,9 +3334,6 @@ public class NotificationPanelViewController extends PanelViewController {
mQsExpandImmediate = true;
setShowShelfOnly(true);
}
- if (mBarState == KEYGUARD || mBarState == StatusBarState.SHADE_LOCKED) {
- mAffordanceHelper.animateHideLeftRightIcon();
- }
mNotificationStackScrollLayoutController.onPanelTrackingStarted();
cancelPendingPanelCollapse();
}
@@ -3353,12 +3347,6 @@ public class NotificationPanelViewController extends PanelViewController {
true /* animate */);
}
mNotificationStackScrollLayoutController.onPanelTrackingStopped();
- if (expand && (mBarState == KEYGUARD
- || mBarState == StatusBarState.SHADE_LOCKED)) {
- if (!mHintAnimationRunning) {
- mAffordanceHelper.reset(true);
- }
- }
// If we unlocked from a swipe, the user's finger might still be down after the
// unlock animation ends. We need to wait until ACTION_UP to enable blurs again.
@@ -3409,19 +3397,11 @@ public class NotificationPanelViewController extends PanelViewController {
return mQsExpanded;
}
- public boolean isQsDetailShowing() {
- return mQs.isShowingDetail();
- }
-
/** Returns whether the QS customizer is currently active. */
public boolean isQsCustomizing() {
return mQs.isCustomizing();
}
- public void closeQsDetail() {
- mQs.closeDetail();
- }
-
/** Close the QS customizer if it is open. */
public void closeQsCustomizer() {
mQs.closeCustomizer();
@@ -3431,10 +3411,6 @@ public class NotificationPanelViewController extends PanelViewController {
return mIsLaunchTransitionFinished;
}
- public boolean isLaunchTransitionRunning() {
- return mIsLaunchTransitionRunning;
- }
-
@Override
public void setIsLaunchAnimationRunning(boolean running) {
boolean wasRunning = mIsLaunchAnimationRunning;
@@ -3453,10 +3429,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- public void setLaunchTransitionEndRunnable(Runnable r) {
- mLaunchAnimationEndRunnable = r;
- }
-
private void updateDozingVisibilities(boolean animate) {
mKeyguardBottomArea.setDozing(mDozing, animate);
if (!mDozing && animate) {
@@ -3489,13 +3461,13 @@ public class NotificationPanelViewController extends PanelViewController {
if (mUpdateMonitor.isFaceEnrolled()
&& !mUpdateMonitor.isFaceDetectionRunning()
&& !mUpdateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser())) {
+ KeyguardUpdateMonitor.getCurrentUser())) {
mUpdateMonitor.requestFaceAuth(true);
} else {
mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_HINT,
0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
mLockscreenGestureLogger
- .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
+ .log(LockscreenUiEvent.LOCKSCREEN_LOCK_SHOW_HINT);
startUnlockHintAnimation();
}
if (mUpdateMonitor.isFaceEnrolled()) {
@@ -3574,7 +3546,7 @@ public class NotificationPanelViewController extends PanelViewController {
mNotificationStackScrollLayoutController.forceNoOverlappingRendering(closing);
}
- protected void updateExpandedHeight(float expandedHeight) {
+ private void updateExpandedHeight(float expandedHeight) {
if (mTracking) {
mNotificationStackScrollLayoutController
.setExpandingVelocity(getCurrentExpandVelocity());
@@ -3597,12 +3569,10 @@ public class NotificationPanelViewController extends PanelViewController {
}
private void updateStatusBarIcons() {
- boolean
- showIconsWhenExpanded =
+ boolean showIconsWhenExpanded =
(isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
&& getExpandedHeight() < getOpeningHeight();
- boolean noVisibleNotifications = true;
- if (showIconsWhenExpanded && noVisibleNotifications && isOnKeyguard()) {
+ if (showIconsWhenExpanded && isOnKeyguard()) {
showIconsWhenExpanded = false;
}
if (showIconsWhenExpanded != mShowIconsWhenExpanded) {
@@ -3635,30 +3605,13 @@ public class NotificationPanelViewController extends PanelViewController {
&& mBarState == StatusBarState.SHADE;
}
- public void launchCamera(boolean animate, int source) {
- if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP;
- } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_WIGGLE) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_WIGGLE;
- } else if (source == StatusBarManager.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER) {
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER;
- } else {
-
- // Default.
- mLastCameraLaunchSource = KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE;
- }
-
- // If we are launching it when we are occluded already we don't want it to animate,
- // nor setting these flags, since the occluded state doesn't change anymore, hence it's
- // never reset.
+ /** Launches the camera. */
+ public void launchCamera(int source) {
if (!isFullyCollapsed()) {
setLaunchingAffordance(true);
- } else {
- animate = false;
}
- mAffordanceHasPreview = mKeyguardBottomArea.getRightPreview() != null;
- mAffordanceHelper.launchAffordance(
- animate, mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL);
+
+ mCameraGestureHelper.launchCamera(source);
}
public void onAffordanceLaunchEnded() {
@@ -3671,9 +3624,6 @@ public class NotificationPanelViewController extends PanelViewController {
*/
private void setLaunchingAffordance(boolean launchingAffordance) {
mLaunchingAffordance = launchingAffordance;
- mKeyguardAffordanceHelperCallback.getLeftIcon().setLaunchingAffordance(launchingAffordance);
- mKeyguardAffordanceHelperCallback.getRightIcon().setLaunchingAffordance(
- launchingAffordance);
mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
}
@@ -3681,34 +3631,14 @@ public class NotificationPanelViewController extends PanelViewController {
* Return true when a bottom affordance is launching an occluded activity with a splash screen.
*/
public boolean isLaunchingAffordanceWithPreview() {
- return mLaunchingAffordance && mAffordanceHasPreview;
+ return mLaunchingAffordance;
}
/**
* Whether the camera application can be launched for the camera launch gesture.
*/
public boolean canCameraGestureBeLaunched() {
- if (!mCentralSurfaces.isCameraAllowedByAdmin()) {
- return false;
- }
-
- ResolveInfo resolveInfo = mKeyguardBottomArea.resolveCameraIntent();
- String
- packageToLaunch =
- (resolveInfo == null || resolveInfo.activityInfo == null) ? null
- : resolveInfo.activityInfo.packageName;
- return packageToLaunch != null && (mBarState != StatusBarState.SHADE || !isForegroundApp(
- packageToLaunch)) && !mAffordanceHelper.isSwipingInProgress();
- }
-
- /**
- * Return true if the applications with the package name is running in foreground.
- *
- * @param pkgName application package name.
- */
- private boolean isForegroundApp(String pkgName) {
- List<ActivityManager.RunningTaskInfo> tasks = mActivityManager.getRunningTasks(1);
- return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName());
+ return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
}
public boolean hideStatusBarIconsWhenExpanded() {
@@ -3787,9 +3717,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void setTouchAndAnimationDisabled(boolean disabled) {
super.setTouchAndAnimationDisabled(disabled);
- if (disabled && mAffordanceHelper.isSwipingInProgress() && !mIsLaunchTransitionRunning) {
- mAffordanceHelper.reset(false /* animate */);
- }
mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled);
}
@@ -3818,6 +3745,8 @@ public class NotificationPanelViewController extends PanelViewController {
final float dozeAmount = dozing ? 1 : 0;
mStatusBarStateController.setAndInstrumentDozeAmount(mView, dozeAmount, animate);
+
+ updateKeyguardStatusViewAlignment(animate);
}
public void setPulsing(boolean pulsing) {
@@ -3872,11 +3801,6 @@ public class NotificationPanelViewController extends PanelViewController {
return mKeyguardBottomArea;
}
- public void setUserSetupComplete(boolean userSetupComplete) {
- mUserSetupComplete = userSetupComplete;
- mKeyguardBottomArea.setUserSetupComplete(userSetupComplete);
- }
-
public void applyLaunchAnimationProgress(float linearProgress) {
boolean hideIcons = LaunchAnimator.getProgress(ActivityLaunchAnimator.TIMINGS,
linearProgress, ANIMATION_DELAY_ICON_FADE_IN, 100) == 0.0f;
@@ -3942,21 +3866,22 @@ public class NotificationPanelViewController extends PanelViewController {
*/
public void startFoldToAodAnimation(Runnable endAction) {
mView.animate()
- .translationX(0)
- .alpha(1f)
- .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
- .setInterpolator(EMPHASIZED_DECELERATE)
- .setListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationCancel(Animator animation) {
- endAction.run();
- }
- @Override
- public void onAnimationEnd(Animator animation) {
- endAction.run();
- }
- })
- .start();
+ .translationX(0)
+ .alpha(1f)
+ .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+ .setInterpolator(EMPHASIZED_DECELERATE)
+ .setListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ endAction.run();
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ endAction.run();
+ }
+ })
+ .start();
mKeyguardStatusViewController.animateFoldToAod();
}
@@ -3970,7 +3895,6 @@ public class NotificationPanelViewController extends PanelViewController {
resetTranslation();
}
- /** */
public void setImportantForAccessibility(int mode) {
mView.setImportantForAccessibility(mode);
}
@@ -4018,7 +3942,6 @@ public class NotificationPanelViewController extends PanelViewController {
* {@link ShadeViewManager}.
*/
public void updateNotificationViews(String reason) {
- mNotificationStackScrollLayoutController.updateSectionBoundaries(reason);
mNotificationStackScrollLayoutController.updateFooter();
mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
@@ -4230,10 +4153,6 @@ public class NotificationPanelViewController extends PanelViewController {
mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
}
boolean handled = false;
- if ((!mIsExpanding || mHintAnimationRunning) && !mQsExpanded
- && mBarState != StatusBarState.SHADE && !mDozing) {
- handled |= mAffordanceHelper.onTouchEvent(event);
- }
if (mOnlyAffordanceInThisMotion) {
return true;
}
@@ -4309,8 +4228,7 @@ public class NotificationPanelViewController extends PanelViewController {
};
@Override
- protected PanelViewController.OnConfigurationChangedListener
- createOnConfigurationChangedListener() {
+ protected OnConfigurationChangedListener createOnConfigurationChangedListener() {
return new OnConfigurationChangedListener();
}
@@ -4367,7 +4285,7 @@ public class NotificationPanelViewController extends PanelViewController {
+ isFullyExpanded() + " inQs=" + isInSettings());
}
mSysUiState.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
- isFullyExpanded() && !isInSettings())
+ isFullyExpanded() && !isInSettings())
.setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED, isInSettings())
.commitUpdate(mDisplayId);
}
@@ -4476,147 +4394,6 @@ public class NotificationPanelViewController extends PanelViewController {
}
}
- private class KeyguardAffordanceHelperCallback implements KeyguardAffordanceHelper.Callback {
- @Override
- public void onAnimationToSideStarted(boolean rightPage, float translation, float vel) {
- boolean
- start =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? rightPage
- : !rightPage;
- mIsLaunchTransitionRunning = true;
- mLaunchAnimationEndRunnable = null;
- float displayDensity = mCentralSurfaces.getDisplayDensity();
- int lengthDp = Math.abs((int) (translation / displayDensity));
- int velocityDp = Math.abs((int) (vel / displayDensity));
- if (start) {
- mLockscreenGestureLogger.write(MetricsEvent.ACTION_LS_DIALER, lengthDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_DIALER);
- mFalsingCollector.onLeftAffordanceOn();
- if (mFalsingCollector.shouldEnforceBouncer()) {
- mCentralSurfaces.executeRunnableDismissingKeyguard(
- () -> mKeyguardBottomArea.launchLeftAffordance(), null,
- true /* dismissShade */, false /* afterKeyguardGone */,
- true /* deferred */);
- } else {
- mKeyguardBottomArea.launchLeftAffordance();
- }
- } else {
- if (KeyguardBottomAreaView.CAMERA_LAUNCH_SOURCE_AFFORDANCE.equals(
- mLastCameraLaunchSource)) {
- mLockscreenGestureLogger.write(
- MetricsEvent.ACTION_LS_CAMERA, lengthDp, velocityDp);
- mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_CAMERA);
- }
- mFalsingCollector.onCameraOn();
- if (mFalsingCollector.shouldEnforceBouncer()) {
- mCentralSurfaces.executeRunnableDismissingKeyguard(
- () -> mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource), null,
- true /* dismissShade */, false /* afterKeyguardGone */,
- true /* deferred */);
- } else {
- mKeyguardBottomArea.launchCamera(mLastCameraLaunchSource);
- }
- }
- mCentralSurfaces.startLaunchTransitionTimeout();
- mBlockTouches = true;
- }
-
- @Override
- public void onAnimationToSideEnded() {
- mIsLaunchTransitionRunning = false;
- mIsLaunchTransitionFinished = true;
- if (mLaunchAnimationEndRunnable != null) {
- mLaunchAnimationEndRunnable.run();
- mLaunchAnimationEndRunnable = null;
- }
- mCentralSurfaces.readyForKeyguardDone();
- }
-
- @Override
- public float getMaxTranslationDistance() {
- return (float) Math.hypot(mView.getWidth(), getHeight());
- }
-
- @Override
- public void onSwipingStarted(boolean rightIcon) {
- mFalsingCollector.onAffordanceSwipingStarted(rightIcon);
- boolean
- camera =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
- : rightIcon;
- if (camera) {
- mKeyguardBottomArea.bindCameraPrewarmService();
- }
- mView.requestDisallowInterceptTouchEvent(true);
- mOnlyAffordanceInThisMotion = true;
- mQsTracking = false;
- }
-
- @Override
- public void onSwipingAborted() {
- mFalsingCollector.onAffordanceSwipingAborted();
- mKeyguardBottomArea.unbindCameraPrewarmService(false /* launched */);
- }
-
- @Override
- public void onIconClicked(boolean rightIcon) {
- if (mHintAnimationRunning) {
- return;
- }
- mHintAnimationRunning = true;
- mAffordanceHelper.startHintAnimation(rightIcon, () -> {
- mHintAnimationRunning = false;
- mCentralSurfaces.onHintFinished();
- });
- rightIcon =
- mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? !rightIcon
- : rightIcon;
- if (rightIcon) {
- mCentralSurfaces.onCameraHintStarted();
- } else {
- if (mKeyguardBottomArea.isLeftVoiceAssist()) {
- mCentralSurfaces.onVoiceAssistHintStarted();
- } else {
- mCentralSurfaces.onPhoneHintStarted();
- }
- }
- }
-
- @Override
- public KeyguardAffordanceView getLeftIcon() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getRightView() : mKeyguardBottomArea.getLeftView();
- }
-
- @Override
- public KeyguardAffordanceView getRightIcon() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getLeftView() : mKeyguardBottomArea.getRightView();
- }
-
- @Override
- public View getLeftPreview() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getRightPreview() : mKeyguardBottomArea.getLeftPreview();
- }
-
- @Override
- public View getRightPreview() {
- return mView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL
- ? mKeyguardBottomArea.getLeftPreview() : mKeyguardBottomArea.getRightPreview();
- }
-
- @Override
- public float getAffordanceFalsingFactor() {
- return mCentralSurfaces.isWakeUpComingFromTouch() ? 1.5f : 1.0f;
- }
-
- @Override
- public boolean needsAntiFalsing() {
- return mBarState == KEYGUARD;
- }
- }
-
private class OnEmptySpaceClickListener implements
NotificationStackScrollLayout.OnEmptySpaceClickListener {
@Override
@@ -4684,7 +4461,6 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onThemeChanged() {
if (DEBUG_LOGCAT) Log.d(TAG, "onThemeChanged");
- mThemeResId = mView.getContext().getThemeResId();
reInflateViews();
}
@@ -4772,11 +4548,6 @@ public class NotificationPanelViewController extends PanelViewController {
duration = StackStateAnimator.ANIMATION_DURATION_STANDARD;
}
mKeyguardStatusBarViewController.animateKeyguardStatusBarOut(startDelay, duration);
- if (mSplitShadeEnabled) {
- // temporary workaround for QS height not being updated during lockscreen to
- // shade transition
- setQsExpanded(true);
- }
updateQSMinHeight();
} else if (oldState == StatusBarState.SHADE_LOCKED
&& statusBarState == KEYGUARD) {
@@ -4839,6 +4610,7 @@ public class NotificationPanelViewController extends PanelViewController {
public interface NotificationPanelViewStateProvider {
/** Returns the expanded height of the panel view. */
float getPanelViewExpandedHeight();
+
/**
* Returns true if heads up should be visible.
*
@@ -4900,7 +4672,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public void onViewAttachedToWindow(View v) {
mFragmentService.getFragmentHostManager(mView)
- .addTagListener(QS.TAG, mFragmentListener);
+ .addTagListener(QS.TAG, mFragmentListener);
mStatusBarStateController.addCallback(mStatusBarStateListener);
mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
mConfigurationController.addCallback(mConfigurationListener);
@@ -4917,7 +4689,7 @@ public class NotificationPanelViewController extends PanelViewController {
public void onViewDetachedFromWindow(View v) {
unregisterSettingsChangeListener();
mFragmentService.getFragmentHostManager(mView)
- .removeTagListener(QS.TAG, mFragmentListener);
+ .removeTagListener(QS.TAG, mFragmentListener);
mStatusBarStateController.removeCallback(mStatusBarStateListener);
mConfigurationController.removeCallback(mConfigurationListener);
mFalsingManager.removeTapListener(mFalsingTapListener);
@@ -4935,7 +4707,7 @@ public class NotificationPanelViewController extends PanelViewController {
setIsFullWidth(mNotificationStackScrollLayoutController.getWidth() == mView.getWidth());
// Update Clock Pivot
- mKeyguardStatusViewController.setPivotX(mView.getWidth() / 2);
+ mKeyguardStatusViewController.setPivotX(((float) mView.getWidth()) / 2f);
mKeyguardStatusViewController.setPivotY(
(FONT_HEIGHT - CAP_HEIGHT) / 2048f
* mKeyguardStatusViewController.getClockTextSize());
@@ -5002,7 +4774,7 @@ public class NotificationPanelViewController extends PanelViewController {
private final Paint mDebugPaint = new Paint();
@Override
- public void draw(@NonNull Canvas canvas) {
+ public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
mDebugTextUsedYPositions.clear();
mDebugPaint.setColor(Color.RED);
@@ -5047,7 +4819,8 @@ public class NotificationPanelViewController extends PanelViewController {
mDebugPaint.setColor(color);
canvas.drawLine(/* startX= */ 0, /* startY= */ y, /* stopX= */ mView.getWidth(),
/* stopY= */ y, mDebugPaint);
- canvas.drawText(label, /* x= */ 0, /* y= */ computeDebugYTextPosition(y), mDebugPaint);
+ canvas.drawText(label + " = " + y + "px", /* x= */ 0,
+ /* y= */ computeDebugYTextPosition(y), mDebugPaint);
}
private int computeDebugYTextPosition(int lineY) {
@@ -5075,16 +4848,7 @@ public class NotificationPanelViewController extends PanelViewController {
@Override
public int getOpacity() {
- return 0;
- }
- }
-
- private class OnConfigurationChangedListener extends
- PanelViewController.OnConfigurationChangedListener {
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- mAffordanceHelper.onConfigurationChanged();
+ return PixelFormat.UNKNOWN;
}
}
@@ -5138,6 +4902,16 @@ public class NotificationPanelViewController extends PanelViewController {
return mStatusBarViewTouchEventHandler;
}
+ @VisibleForTesting
+ StatusBarStateController getStatusBarStateController() {
+ return mStatusBarStateController;
+ }
+
+ @VisibleForTesting
+ boolean isHintAnimationRunning() {
+ return mHintAnimationRunning;
+ }
+
private void onStatusBarWindowStateChanged(@StatusBarManager.WindowVisibleState int state) {
if (state != WINDOW_STATE_SHOWING
&& mStatusBarStateController.getState() == StatusBarState.SHADE) {
@@ -5154,15 +4928,16 @@ public class NotificationPanelViewController extends PanelViewController {
private final ListenerSet<Listener> mListeners = new ListenerSet<>();
@Inject
- PanelEventsEmitter() {}
+ PanelEventsEmitter() {
+ }
@Override
- public void registerListener(@NonNull Listener listener) {
+ public void registerListener(@androidx.annotation.NonNull @NonNull Listener listener) {
mListeners.addIfAbsent(listener);
}
@Override
- public void unregisterListener(@NonNull Listener listener) {
+ public void unregisterListener(@androidx.annotation.NonNull @NonNull Listener listener) {
mListeners.remove(listener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
index 1e3a02b0606b..121d69d34678 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.view.WindowInsets.Type.systemBars;
@@ -52,6 +52,7 @@ import android.widget.FrameLayout;
import com.android.internal.view.FloatingActionMode;
import com.android.internal.widget.floatingtoolbar.FloatingToolbar;
import com.android.systemui.R;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
/**
* Combined keyguard and notification panel view. Also holding backdrop and scrims.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index be5b33eb0da0..b8546df75f5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.app.StatusBarManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -46,6 +46,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index ebedbf987aa2..13a5615a8b54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
import android.view.View
import android.view.ViewGroup
@@ -6,11 +6,7 @@ import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.view.WindowInsets
import androidx.annotation.VisibleForTesting
import androidx.constraintlayout.widget.ConstraintSet
-import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
-import androidx.constraintlayout.widget.ConstraintSet.END
-import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
-import androidx.constraintlayout.widget.ConstraintSet.START
-import androidx.constraintlayout.widget.ConstraintSet.TOP
+import androidx.constraintlayout.widget.ConstraintSet.*
import com.android.systemui.R
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
index 2446cf7ba412..587e0e6dd834 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationsQuickSettingsContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQuickSettingsContainer.java
@@ -14,7 +14,7 @@
* limitations under the License
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.app.Fragment;
import android.content.Context;
diff --git a/packages/SystemUI/src/com/android/systemui/shade/OWNERS b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
new file mode 100644
index 000000000000..7dc9dc7efeb7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/OWNERS
@@ -0,0 +1,14 @@
+per-file *Notification* = set noparent
+per-file *Notification* = file:../statusbar/notification/OWNERS
+
+per-file NotificationsQuickSettingsContainer.java = kozynski@google.com, asc@google.com
+per-file NotificationsQSContainerController.kt = kozynski@google.com, asc@google.com
+per-file *ShadeHeader* = kozynski@google.com, asc@google.com
+
+per-file NotificationShadeWindowViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com
+per-file NotificationShadeWindowView.java = pixel@google.com, cinek@google.com, juliacr@google.com
+
+per-file NotificationPanelUnfoldAnimationController.kt = alexflo@google.com, jeffdq@google.com, juliacr@google.com
+
+per-file NotificationPanelView.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com
+per-file NotificationPanelViewController.java = pixel@google.com, cinek@google.com, juliacr@google.com, justinweir@google.com \ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
index 45dc943de2f8..1082967d3b5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelView.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import android.content.Context;
import android.content.res.Configuration;
@@ -22,6 +22,10 @@ import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+
public abstract class PanelView extends FrameLayout {
public static final boolean DEBUG = false;
public static final String TAG = PanelView.class.getSimpleName();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
index 5d417e0b59e2..229acf4ed061 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/PanelViewController.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
@@ -41,6 +41,7 @@ import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
import android.view.ViewTreeObserver;
import android.view.animation.Interpolator;
@@ -59,13 +60,21 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
+import com.android.systemui.statusbar.phone.BounceInterpolator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.time.SystemClock;
import com.android.wm.shell.animation.FlingAnimationUtils;
import java.io.PrintWriter;
+import java.util.List;
public abstract class PanelViewController {
public static final boolean DEBUG = PanelView.DEBUG;
@@ -386,7 +395,7 @@ public abstract class PanelViewController {
return Math.abs(yDiff) >= Math.abs(xDiff);
}
- protected void startExpandMotion(float newX, float newY, boolean startTracking,
+ public void startExpandMotion(float newX, float newY, boolean startTracking,
float expandedHeight) {
if (!mHandlingPointerUp && !mStatusBarStateController.isDozing()) {
beginJankMonitoring(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE);
@@ -430,6 +439,8 @@ public abstract class PanelViewController {
// situations, such keeping your finger down while swiping to unlock to an app
// that is locked in landscape (the rotation will cancel the touch event).
expand = false;
+ } else if (mCentralSurfaces.isBouncerShowingOverDream()) {
+ expand = false;
} else {
// If we get a cancel, put the shade back to the state it was in when the
// gesture started
@@ -482,8 +493,6 @@ public abstract class PanelViewController {
protected abstract boolean shouldGestureWaitForTouchSlop();
- protected abstract boolean shouldGestureIgnoreXTouchSlop(float x, float y);
-
protected void onTrackingStopped(boolean expand) {
mTracking = false;
mCentralSurfaces.onTrackingStopped(expand);
@@ -715,7 +724,7 @@ public abstract class PanelViewController {
animator.start();
}
- void onFlingEnd(boolean cancelled) {
+ protected void onFlingEnd(boolean cancelled) {
mIsFlinging = false;
// No overshoot when the animation ends
setOverExpansionInternal(0, false /* isFromGesture */);
@@ -804,6 +813,7 @@ public abstract class PanelViewController {
mExpansionDragDownAmountPx = h;
mExpandedFraction = Math.min(1f,
maxPanelHeight == 0 ? 0 : mExpandedHeight / maxPanelHeight);
+ mAmbientState.setExpansionFraction(mExpandedFraction);
onHeightUpdated(mExpandedHeight);
updatePanelExpansionAndVisibility();
});
@@ -1031,16 +1041,19 @@ public abstract class PanelViewController {
animator.start();
setAnimator(animator);
- View[] viewsToAnimate = {
- mKeyguardBottomArea.getIndicationArea(),
- mCentralSurfaces.getAmbientIndicationContainer()};
- for (View v : viewsToAnimate) {
- if (v == null) {
- continue;
- }
- v.animate().translationY(-mHintDistance).setDuration(250).setInterpolator(
- Interpolators.FAST_OUT_SLOW_IN).withEndAction(() -> v.animate().translationY(
- 0).setDuration(450).setInterpolator(mBounceInterpolator).start()).start();
+ final List<ViewPropertyAnimator> indicationAnimators =
+ mKeyguardBottomArea.getIndicationAreaAnimators();
+ for (final ViewPropertyAnimator indicationAreaAnimator : indicationAnimators) {
+ indicationAreaAnimator
+ .translationY(-mHintDistance)
+ .setDuration(250)
+ .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
+ .withEndAction(() -> indicationAreaAnimator
+ .translationY(0)
+ .setDuration(450)
+ .setInterpolator(mBounceInterpolator)
+ .start())
+ .start();
}
}
@@ -1107,7 +1120,7 @@ public abstract class PanelViewController {
}
/** Returns true if {@link PanelView} should be visible. */
- abstract boolean shouldPanelBeVisible();
+ abstract protected boolean shouldPanelBeVisible();
/**
* Updates the panel expansion and {@link PanelView} visibility if necessary.
@@ -1158,8 +1171,6 @@ public abstract class PanelViewController {
mTouchDisabled ? "T" : "f"));
}
- public abstract void resetViews(boolean animate);
-
public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
mHeadsUpManager = headsUpManager;
}
@@ -1330,7 +1341,7 @@ public abstract class PanelViewController {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
mGestureWaitForTouchSlop = shouldGestureWaitForTouchSlop();
- mIgnoreXTouchSlop = isFullyCollapsed() || shouldGestureIgnoreXTouchSlop(x, y);
+ mIgnoreXTouchSlop = true;
}
switch (event.getActionMasked()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
index 2789db874249..f4db3ab9289b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/NoOpOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.statusbar.phone.shade.transition
+package com.android.systemui.shade.transition
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
new file mode 100644
index 000000000000..afd57daca10b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -0,0 +1,106 @@
+package com.android.systemui.shade.transition
+
+import android.content.res.Configuration
+import android.content.res.Resources
+import android.util.MathUtils.constrain
+import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
+import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.LargeScreenUtils
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/** Controls the scrim properties during the shade expansion transition on non-lockscreen. */
+@SysUISingleton
+class ScrimShadeTransitionController
+@Inject
+constructor(
+ configurationController: ConfigurationController,
+ dumpManager: DumpManager,
+ private val scrimController: ScrimController,
+ @Main private val resources: Resources,
+ private val statusBarStateController: SysuiStatusBarStateController,
+) {
+
+ private var inSplitShade = false
+ private var splitShadeScrimTransitionDistance = 0
+ private var lastExpansionFraction: Float? = null
+ private var lastExpansionEvent: PanelExpansionChangeEvent? = null
+ private var currentPanelState: Int? = null
+
+ init {
+ updateResources()
+ configurationController.addCallback(
+ object : ConfigurationController.ConfigurationListener {
+ override fun onConfigChanged(newConfig: Configuration?) {
+ updateResources()
+ }
+ })
+ dumpManager.registerDumpable(
+ ScrimShadeTransitionController::class.java.simpleName, this::dump)
+ }
+
+ private fun updateResources() {
+ inSplitShade = LargeScreenUtils.shouldUseSplitNotificationShade(resources)
+ splitShadeScrimTransitionDistance =
+ resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+ }
+
+ fun onPanelStateChanged(@PanelState state: Int) {
+ currentPanelState = state
+ onStateChanged()
+ }
+
+ fun onPanelExpansionChanged(panelExpansionChangeEvent: PanelExpansionChangeEvent) {
+ lastExpansionEvent = panelExpansionChangeEvent
+ onStateChanged()
+ }
+
+ private fun onStateChanged() {
+ val expansionEvent = lastExpansionEvent ?: return
+ val panelState = currentPanelState
+ val expansionFraction = calculateScrimExpansionFraction(expansionEvent, panelState)
+ scrimController.setRawPanelExpansionFraction(expansionFraction)
+ lastExpansionFraction = expansionFraction
+ }
+
+ private fun calculateScrimExpansionFraction(
+ expansionEvent: PanelExpansionChangeEvent,
+ @PanelState panelState: Int?
+ ): Float {
+ return if (canUseCustomFraction(panelState)) {
+ constrain(expansionEvent.dragDownPxAmount / splitShadeScrimTransitionDistance, 0f, 1f)
+ } else {
+ expansionEvent.fraction
+ }
+ }
+
+ private fun canUseCustomFraction(panelState: Int?) =
+ inSplitShade && isScreenUnlocked() && panelState == STATE_OPENING
+
+ private fun isScreenUnlocked() =
+ statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
+
+ private fun dump(printWriter: PrintWriter, args: Array<String>) {
+ printWriter.println(
+ """
+ ScrimShadeTransitionController:
+ Resources:
+ inSplitShade: $inSplitShade
+ isScreenUnlocked: ${isScreenUnlocked()}
+ splitShadeScrimTransitionDistance: $splitShadeScrimTransitionDistance
+ State:
+ currentPanelState: $currentPanelState
+ lastExpansionFraction: $lastExpansionFraction
+ lastExpansionEvent: $lastExpansionEvent
+ """.trimIndent())
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
index f1cedeb21e0a..6c3a028c2380 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.statusbar.phone.shade.transition
+package com.android.systemui.shade.transition
import com.android.systemui.statusbar.phone.panelstate.PanelState
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 2762b9a38e92..58acfb40ee44 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -1,16 +1,21 @@
-package com.android.systemui.statusbar.phone.shade.transition
+package com.android.systemui.shade.transition
import android.content.Context
import android.content.res.Configuration
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.phone.panelstate.PanelState
+import com.android.systemui.statusbar.phone.panelstate.panelStateToString
import com.android.systemui.statusbar.policy.ConfigurationController
+import java.io.PrintWriter
import javax.inject.Inject
/** Controls the shade expansion transition on non-lockscreen. */
@@ -20,9 +25,12 @@ class ShadeTransitionController
constructor(
configurationController: ConfigurationController,
panelExpansionStateManager: PanelExpansionStateManager,
+ dumpManager: DumpManager,
private val context: Context,
private val splitShadeOverScrollerFactory: SplitShadeOverScroller.Factory,
- private val noOpOverScroller: NoOpOverScroller
+ private val noOpOverScroller: NoOpOverScroller,
+ private val scrimShadeTransitionController: ScrimShadeTransitionController,
+ private val statusBarStateController: SysuiStatusBarStateController,
) {
lateinit var notificationPanelViewController: NotificationPanelViewController
@@ -30,13 +38,15 @@ constructor(
lateinit var qs: QS
private var inSplitShade = false
+ private var currentPanelState: Int? = null
+ private var lastPanelExpansionChangeEvent: PanelExpansionChangeEvent? = null
private val splitShadeOverScroller by lazy {
- splitShadeOverScrollerFactory.create(qs, notificationStackScrollLayoutController)
+ splitShadeOverScrollerFactory.create({ qs }, { notificationStackScrollLayoutController })
}
private val shadeOverScroller: ShadeOverScroller
get() =
- if (inSplitShade && propertiesInitialized()) {
+ if (inSplitShade && isScreenUnlocked() && propertiesInitialized()) {
splitShadeOverScroller
} else {
noOpOverScroller
@@ -52,6 +62,9 @@ constructor(
})
panelExpansionStateManager.addExpansionListener(this::onPanelExpansionChanged)
panelExpansionStateManager.addStateListener(this::onPanelStateChanged)
+ dumpManager.registerDumpable("ShadeTransitionController") { printWriter, _ ->
+ dump(printWriter)
+ }
}
private fun updateResources() {
@@ -59,15 +72,36 @@ constructor(
}
private fun onPanelStateChanged(@PanelState state: Int) {
+ currentPanelState = state
shadeOverScroller.onPanelStateChanged(state)
+ scrimShadeTransitionController.onPanelStateChanged(state)
}
private fun onPanelExpansionChanged(event: PanelExpansionChangeEvent) {
+ lastPanelExpansionChangeEvent = event
shadeOverScroller.onDragDownAmountChanged(event.dragDownPxAmount)
+ scrimShadeTransitionController.onPanelExpansionChanged(event)
}
private fun propertiesInitialized() =
this::qs.isInitialized &&
this::notificationPanelViewController.isInitialized &&
this::notificationStackScrollLayoutController.isInitialized
+
+ private fun dump(pw: PrintWriter) {
+ pw.println(
+ """
+ ShadeTransitionController:
+ inSplitShade: $inSplitShade
+ isScreenUnlocked: ${isScreenUnlocked()}
+ currentPanelState: ${currentPanelState?.panelStateToString()}
+ lastPanelExpansionChangeEvent: $lastPanelExpansionChangeEvent
+ qs.isInitialized: ${this::qs.isInitialized}
+ npvc.isInitialized: ${this::notificationPanelViewController.isInitialized}
+ nssl.isInitialized: ${this::notificationStackScrollLayoutController.isInitialized}
+ """.trimIndent())
+ }
+
+ private fun isScreenUnlocked() =
+ statusBarStateController.currentOrUpcomingState == StatusBarState.SHADE
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index 71050f2e7c67..204dd3c07d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.statusbar.phone.shade.transition
+package com.android.systemui.shade.transition
import android.animation.Animator
import android.animation.ValueAnimator
@@ -28,8 +28,8 @@ constructor(
dumpManager: DumpManager,
private val context: Context,
private val scrimController: ScrimController,
- @Assisted private val qS: QS,
- @Assisted private val nsslController: NotificationStackScrollLayoutController
+ @Assisted private val qSProvider: () -> QS,
+ @Assisted private val nsslControllerProvider: () -> NotificationStackScrollLayoutController
) : ShadeOverScroller {
private var releaseOverScrollDuration = 0L
@@ -39,6 +39,12 @@ constructor(
@PanelState private var panelState: Int = STATE_CLOSED
private var releaseOverScrollAnimator: Animator? = null
+ private val qS: QS
+ get() = qSProvider()
+
+ private val nsslController: NotificationStackScrollLayoutController
+ get() = nsslControllerProvider()
+
init {
updateResources()
configurationController.addCallback(
@@ -47,7 +53,9 @@ constructor(
updateResources()
}
})
- dumpManager.registerDumpable(this::dump)
+ dumpManager.registerDumpable("SplitShadeOverScroller") { printWriter, _ ->
+ dump(printWriter)
+ }
}
private fun updateResources() {
@@ -118,7 +126,7 @@ constructor(
releaseOverScrollAnimator = null
}
- private fun dump(pw: PrintWriter, strings: Array<String>) {
+ private fun dump(pw: PrintWriter) {
pw.println(
"""
SplitShadeOverScroller:
@@ -135,8 +143,8 @@ constructor(
@AssistedFactory
fun interface Factory {
fun create(
- qS: QS,
- nsslController: NotificationStackScrollLayoutController
+ qSProvider: () -> QS,
+ nsslControllerProvider: () -> NotificationStackScrollLayoutController
): SplitShadeOverScroller
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
index d3ae198e8e35..236ba1f92d1f 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceViewComponent.kt
@@ -56,6 +56,8 @@ interface SmartspaceViewComponent {
):
BcSmartspaceDataPlugin.SmartspaceView {
val ssView = plugin.getView(parent)
+ // Currently, this is only used to provide SmartspaceView on Dream surface.
+ ssView.setIsDreaming(true)
ssView.registerDataProvider(plugin)
ssView.setIntentStarter(object : BcSmartspaceDataPlugin.IntentStarter {
@@ -81,4 +83,4 @@ interface SmartspaceViewComponent {
return ssView
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
index 6cfbb43fa25a..07455a02b2cf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java
@@ -66,7 +66,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @param entry entry to show
*/
public void showNotification(@NonNull NotificationEntry entry) {
- mLogger.logShowNotification(entry.getKey());
+ mLogger.logShowNotification(entry);
addAlertEntry(entry);
updateNotification(entry.getKey(), true /* alert */);
entry.setInterruption();
@@ -320,7 +320,7 @@ public abstract class AlertingNotificationManager implements NotificationLifetim
* @param updatePostTime whether or not to refresh the post time
*/
public void updateEntry(boolean updatePostTime) {
- mLogger.logUpdateEntry(mEntry.getKey(), updatePostTime);
+ mLogger.logUpdateEntry(mEntry, updatePostTime);
long currentTime = mClock.currentTimeMillis();
mEarliestRemovaltime = currentTime + mMinimumDisplayTime;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index cc3121ddc0e9..e99244048fd6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -66,6 +66,7 @@ import com.android.internal.os.SomeArgs;
import com.android.internal.statusbar.IAddTileResultCallback;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.GcUtils;
import com.android.internal.view.AppearanceRegion;
@@ -361,7 +362,7 @@ public class CommandQueue extends IStatusBar.Stub implements
default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
@Behavior int behavior, InsetsVisibilities requestedVisibilities,
- String packageName) { }
+ String packageName, LetterboxDetails[] letterboxDetails) { }
/**
* @see IStatusBar#showTransient(int, int[], boolean).
@@ -1090,7 +1091,8 @@ public class CommandQueue extends IStatusBar.Stub implements
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ LetterboxDetails[] letterboxDetails) {
synchronized (mLock) {
SomeArgs args = SomeArgs.obtain();
args.argi1 = displayId;
@@ -1100,6 +1102,7 @@ public class CommandQueue extends IStatusBar.Stub implements
args.argi4 = behavior;
args.arg2 = requestedVisibilities;
args.arg3 = packageName;
+ args.arg4 = letterboxDetails;
mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
}
}
@@ -1561,7 +1564,8 @@ public class CommandQueue extends IStatusBar.Stub implements
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
(AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
- (InsetsVisibilities) args.arg2, (String) args.arg3);
+ (InsetsVisibilities) args.arg2, (String) args.arg3,
+ (LetterboxDetails[]) args.arg4);
}
args.recycle();
break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
index 9e029095ea6b..ca147286a301 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyguardIndicationController.java
@@ -900,16 +900,36 @@ public class KeyguardIndicationController {
mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
}
} else {
- if (!mAccessibilityManager.isEnabled()
- && !mAccessibilityManager.isTouchExplorationEnabled()
- && mKeyguardUpdateMonitor.isUdfpsSupported()
- && mKeyguardUpdateMonitor.getUserCanSkipBouncer(
- KeyguardUpdateMonitor.getCurrentUser())) {
- final int stringId = mKeyguardUpdateMonitor.getIsFaceAuthenticated()
- ? R.string.keyguard_face_successful_unlock_press
- : R.string.keyguard_unlock_press;
- showBiometricMessage(mContext.getString(stringId));
+ final boolean canSkipBouncer = mKeyguardUpdateMonitor.getUserCanSkipBouncer(
+ KeyguardUpdateMonitor.getCurrentUser());
+ if (canSkipBouncer) {
+ final boolean faceAuthenticated = mKeyguardUpdateMonitor.getIsFaceAuthenticated();
+ final boolean udfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported();
+ final boolean a11yEnabled = mAccessibilityManager.isEnabled()
+ || mAccessibilityManager.isTouchExplorationEnabled();
+ if (udfpsSupported && faceAuthenticated) { // co-ex
+ if (a11yEnabled) {
+ showBiometricMessage(mContext.getString(
+ R.string.keyguard_face_successful_unlock_swipe));
+ } else {
+ showBiometricMessage(mContext.getString(
+ R.string.keyguard_face_successful_unlock_press));
+ }
+ } else if (faceAuthenticated) { // face-only
+ showBiometricMessage(mContext.getString(
+ R.string.keyguard_face_successful_unlock_swipe));
+ } else if (udfpsSupported) { // udfps-only
+ if (a11yEnabled) {
+ showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+ } else {
+ showBiometricMessage(mContext.getString(
+ R.string.keyguard_unlock_press));
+ }
+ } else { // no security or unlocked by a trust agent
+ showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
+ }
} else {
+ // suggest swiping up for the primary authentication bouncer
showBiometricMessage(mContext.getString(R.string.keyguard_unlock));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
index 01eb4446ea8e..886ad684649f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeKeyguardTransitionController.kt
@@ -6,7 +6,7 @@ import android.util.MathUtils
import com.android.systemui.R
import com.android.systemui.dump.DumpManager
import com.android.systemui.media.MediaHierarchyManager
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index c1ea6bf7cec8..74d8f1beaec1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -29,6 +29,7 @@ import com.android.systemui.plugins.ActivityStarter.OnDismissAction
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.ExpandableView
@@ -37,7 +38,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.util.LargeScreenUtils
import java.io.PrintWriter
@@ -178,7 +178,7 @@ class LockscreenShadeTransitionController @Inject constructor(
val touchHelper = DragDownHelper(falsingManager, falsingCollector, this, context)
private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy {
- splitShadeOverScrollerFactory.create(qS, nsslController)
+ splitShadeOverScrollerFactory.create({ qS }, { nsslController })
}
private val phoneShadeOverScroller: SingleShadeLockScreenOverScroller by lazy {
@@ -911,4 +911,4 @@ class DragDownHelper(
host.getLocationOnScreen(temp2)
return expandCallback.getChildAtRawPosition(x + temp2[0], y + temp2[1])
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index 38a208b72edc..f4ca7edb146d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -642,6 +642,8 @@ public class NotificationLockscreenUserManagerImpl implements
// - device keyguard is shown in secure mode;
// - profile is locked with a work challenge.
SparseArray<UserInfo> currentProfiles = getCurrentProfiles();
+ SparseBooleanArray oldPublicModes = mLockscreenPublicMode.clone();
+ SparseBooleanArray oldWorkChallenges = mUsersWithSeparateWorkChallenge.clone();
mUsersWithSeparateWorkChallenge.clear();
for (int i = currentProfiles.size() - 1; i >= 0; i--) {
final int userId = currentProfiles.valueAt(i).id;
@@ -660,7 +662,10 @@ public class NotificationLockscreenUserManagerImpl implements
}
getEntryManager().updateNotifications("NotificationLockscreenUserManager.updatePublicMode");
// TODO(b/234738798): Migrate KeyguardNotificationVisibilityProvider to use this listener
- // notifyNotificationStateChanged();
+ if (!mLockscreenPublicMode.equals(oldPublicModes)
+ || !mUsersWithSeparateWorkChallenge.equals(oldWorkChallenges)) {
+ notifyNotificationStateChanged();
+ }
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 1ce05ec7e7ad..c900c5a2ff0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -36,7 +36,6 @@ import android.media.session.MediaSession;
import android.media.session.PlaybackState;
import android.os.AsyncTask;
import android.os.Trace;
-import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
@@ -44,7 +43,6 @@ import android.util.Log;
import android.view.View;
import android.widget.ImageView;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dependency;
import com.android.systemui.Dumpable;
import com.android.systemui.animation.Interpolators;
@@ -56,9 +54,6 @@ import com.android.systemui.media.MediaDataManager;
import com.android.systemui.media.SmartspaceMediaData;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.CentralSurfacesModule;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -112,11 +107,9 @@ public class NotificationMediaManager implements Dumpable {
}
private final NotificationVisibilityProvider mVisibilityProvider;
- private final NotificationEntryManager mEntryManager;
private final MediaDataManager mMediaDataManager;
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
- private final boolean mUsingNotifPipeline;
@Nullable
private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
@@ -180,12 +173,10 @@ public class NotificationMediaManager implements Dumpable {
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- NotifPipelineFlags notifPipelineFlags,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
DumpManager dumpManager) {
@@ -197,19 +188,12 @@ public class NotificationMediaManager implements Dumpable {
mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
mNotificationShadeWindowController = notificationShadeWindowController;
mVisibilityProvider = visibilityProvider;
- mEntryManager = notificationEntryManager;
mMainExecutor = mainExecutor;
mMediaDataManager = mediaDataManager;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
- if (!notifPipelineFlags.isNewPipelineEnabled()) {
- setupNEM();
- mUsingNotifPipeline = false;
- } else {
- setupNotifPipeline();
- mUsingNotifPipeline = true;
- }
+ setupNotifPipeline();
dumpManager.registerDumpable(this);
}
@@ -273,79 +257,6 @@ public class NotificationMediaManager implements Dumpable {
});
}
- private void setupNEM() {
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
-
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
- }
-
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- mMediaDataManager.onNotificationAdded(entry.getKey(), entry.getSbn());
- }
-
- @Override
- public void onEntryInflated(NotificationEntry entry) {
- findAndUpdateMediaNotifications();
- }
-
- @Override
- public void onEntryReinflated(NotificationEntry entry) {
- findAndUpdateMediaNotifications();
- }
-
- @Override
- public void onEntryRemoved(
- @NonNull NotificationEntry entry,
- @Nullable NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- removeEntry(entry);
- }
- });
-
- // Pending entries are never inflated, and will never generate a call to onEntryRemoved().
- // This can happen when notifications are added and canceled before inflation. Add this
- // separate listener for cleanup, since media inflation occurs onPendingEntryAdded().
- mEntryManager.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryCleanUp(@NonNull NotificationEntry entry) {
- removeEntry(entry);
- }
- });
-
- mMediaDataManager.addListener(new MediaDataManager.Listener() {
- @Override
- public void onMediaDataLoaded(@NonNull String key,
- @Nullable String oldKey, @NonNull MediaData data, boolean immediately,
- int receivedSmartspaceCardLatency, boolean isSsReactivated) {
- }
-
- @Override
- public void onSmartspaceMediaDataLoaded(@NonNull String key,
- @NonNull SmartspaceMediaData data, boolean shouldPrioritize) {
-
- }
-
- @Override
- public void onMediaDataRemoved(@NonNull String key) {
- NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
- if (entry != null) {
- // TODO(b/160713608): "removing" this notification won't happen and
- // won't send the 'deleteIntent' if the notification is ongoing.
- mEntryManager.performRemoveNotification(entry.getSbn(),
- getDismissedByUserStats(entry),
- NotificationListenerService.REASON_CANCEL);
- }
- }
-
- @Override
- public void onSmartspaceMediaDataRemoved(@NonNull String key, boolean immediately) {}
- });
- }
-
private DismissedByUserStats getDismissedByUserStats(NotificationEntry entry) {
return new DismissedByUserStats(
NotificationStats.DISMISSAL_SHADE, // Add DISMISSAL_MEDIA?
@@ -401,22 +312,10 @@ public class NotificationMediaManager implements Dumpable {
if (mMediaNotificationKey == null) {
return null;
}
- if (mUsingNotifPipeline) {
- return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
- .map(entry -> entry.getIcons().getShelfIcon())
- .map(StatusBarIconView::getSourceIcon)
- .orElse(null);
- } else {
- synchronized (mEntryManager) {
- NotificationEntry entry = mEntryManager
- .getActiveNotificationUnfiltered(mMediaNotificationKey);
- if (entry == null || entry.getIcons().getShelfIcon() == null) {
- return null;
- }
-
- return entry.getIcons().getShelfIcon().getSourceIcon();
- }
- }
+ return Optional.ofNullable(mNotifPipeline.getEntry(mMediaNotificationKey))
+ .map(entry -> entry.getIcons().getShelfIcon())
+ .map(StatusBarIconView::getSourceIcon)
+ .orElse(null);
}
public void addCallback(MediaListener callback) {
@@ -431,21 +330,9 @@ public class NotificationMediaManager implements Dumpable {
public void findAndUpdateMediaNotifications() {
boolean metaDataChanged;
- if (mUsingNotifPipeline) {
- // TODO(b/169655907): get the semi-filtered notifications for current user
- Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
- metaDataChanged = findPlayingMediaNotification(allNotifications);
- } else {
- synchronized (mEntryManager) {
- Collection<NotificationEntry> allNotifications = mEntryManager.getAllNotifs();
- metaDataChanged = findPlayingMediaNotification(allNotifications);
- }
-
- if (metaDataChanged) {
- mEntryManager.updateNotifications("NotificationMediaManager - metaDataChanged");
- }
-
- }
+ // TODO(b/169655907): get the semi-filtered notifications for current user
+ Collection<NotificationEntry> allNotifications = mNotifPipeline.getAllNotifs();
+ metaDataChanged = findPlayingMediaNotification(allNotifications);
dispatchUpdateMediaMetaData(metaDataChanged, true /* allowEnterAnimation */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
index d71dec8ff396..78b3b0a65351 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationRemoteInputManager.java
@@ -68,6 +68,7 @@ import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
import com.android.systemui.statusbar.policy.RemoteInputView;
import com.android.systemui.util.DumpUtilsKt;
+import com.android.systemui.util.ListenerSet;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -75,6 +76,7 @@ import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.Consumer;
import dagger.Lazy;
@@ -118,6 +120,8 @@ public class NotificationRemoteInputManager implements Dumpable {
protected Callback mCallback;
private final List<RemoteInputController.Callback> mControllerCallbacks = new ArrayList<>();
+ private final ListenerSet<Consumer<NotificationEntry>> mActionPressListeners =
+ new ListenerSet<>();
private final InteractionHandler mInteractionHandler = new InteractionHandler() {
@@ -282,10 +286,6 @@ public class NotificationRemoteInputManager implements Dumpable {
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
mRebuilder = rebuilder;
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mRemoteInputListener = createLegacyRemoteInputLifetimeExtender(mainHandler,
- notificationEntryManager, smartReplyController);
- }
mKeyguardManager = context.getSystemService(KeyguardManager.class);
mStatusBarStateController = statusBarStateController;
mRemoteInputUriController = remoteInputUriController;
@@ -309,34 +309,19 @@ public class NotificationRemoteInputManager implements Dumpable {
int reason) {
// We're removing the notification, the smart controller can forget about it.
mSmartReplyController.stopSending(entry);
-
- if (removedByUser && entry != null) {
- onPerformRemoveNotification(entry, entry.getKey());
- }
}
});
}
/** Add a listener for various remote input events. Works with NEW pipeline only. */
public void setRemoteInputListener(@NonNull RemoteInputListener remoteInputListener) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- if (mRemoteInputListener != null) {
- throw new IllegalStateException("mRemoteInputListener is already set");
- }
- mRemoteInputListener = remoteInputListener;
- if (mRemoteInputController != null) {
- mRemoteInputListener.setRemoteInputController(mRemoteInputController);
- }
+ if (mRemoteInputListener != null) {
+ throw new IllegalStateException("mRemoteInputListener is already set");
+ }
+ mRemoteInputListener = remoteInputListener;
+ if (mRemoteInputController != null) {
+ mRemoteInputListener.setRemoteInputController(mRemoteInputController);
}
- }
-
- @NonNull
- @VisibleForTesting
- protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
- Handler mainHandler,
- NotificationEntryManager notificationEntryManager,
- SmartReplyController smartReplyController) {
- return new LegacyRemoteInputLifetimeExtender();
}
/** Initializes this component with the provided dependencies. */
@@ -377,12 +362,6 @@ public class NotificationRemoteInputManager implements Dumpable {
}
}
});
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mSmartReplyController.setCallback((entry, reply) -> {
- StatusBarNotification newSbn = mRebuilder.rebuildForSendingSmartReply(entry, reply);
- mEntryManager.updateNotification(newSbn, null /* ranking */);
- });
- }
}
public void addControllerCallback(RemoteInputController.Callback callback) {
@@ -401,6 +380,14 @@ public class NotificationRemoteInputManager implements Dumpable {
}
}
+ public void addActionPressListener(Consumer<NotificationEntry> listener) {
+ mActionPressListeners.addIfAbsent(listener);
+ }
+
+ public void removeActionPressListener(Consumer<NotificationEntry> listener) {
+ mActionPressListeners.remove(listener);
+ }
+
/**
* Activates a given {@link RemoteInput}
*
@@ -576,19 +563,6 @@ public class NotificationRemoteInputManager implements Dumpable {
return v.findViewWithTag(RemoteInputView.VIEW_TAG);
}
- public ArrayList<NotificationLifetimeExtender> getLifetimeExtenders() {
- // OLD pipeline code ONLY; can assume implementation
- return ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener).mLifetimeExtenders;
- }
-
- @VisibleForTesting
- void onPerformRemoveNotification(NotificationEntry entry, final String key) {
- // OLD pipeline code ONLY; can assume implementation
- ((LegacyRemoteInputLifetimeExtender) mRemoteInputListener)
- .mKeysKeptForRemoteInputHistory.remove(key);
- cleanUpRemoteInputForUserRemoval(entry);
- }
-
/**
* Disable remote input on the entry and remove the remote input view.
* This should be called when a user dismisses a notification that won't be lifetime extended.
@@ -634,6 +608,9 @@ public class NotificationRemoteInputManager implements Dumpable {
if (mRemoteInputListener != null) {
mRemoteInputListener.releaseNotificationIfKeptForRemoteInputHistory(entry);
}
+ for (Consumer<NotificationEntry> listener : mActionPressListeners) {
+ listener.accept(entry);
+ }
}
/** Returns whether the notification should be lifetime extended for smart reply history */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 0d604014e8f1..221428747558 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -229,6 +229,17 @@ public class NotificationShelf extends ActivatableNotificationView implements
mActualWidth = actualWidth;
}
+ @Override
+ public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
+ super.getBoundsOnScreen(outRect, clipToParent);
+ final int actualWidth = getActualWidth();
+ if (isLayoutRtl()) {
+ outRect.left = outRect.right - actualWidth;
+ } else {
+ outRect.right = outRect.left + actualWidth;
+ }
+ }
+
/**
* @return Actual width of shelf, accounting for possible ongoing width animation
*/
@@ -387,7 +398,14 @@ public class NotificationShelf extends ActivatableNotificationView implements
if (child instanceof ActivatableNotificationView) {
ActivatableNotificationView anv =
(ActivatableNotificationView) child;
- updateCornerRoundnessOnScroll(anv, viewStart, shelfStart);
+ // Because we show whole notifications on the lockscreen, the bottom notification is
+ // always "just about to enter the shelf" by normal scrolling rules. This is fine
+ // if the shelf is visible, but if the shelf is hidden, it causes incorrect curling.
+ // notificationClipEnd handles the discrepancy between a visible and hidden shelf,
+ // so we use that when on the keyguard (and while animating away) to reduce curling.
+ final float keyguardSafeShelfStart =
+ mAmbientState.isOnKeyguard() ? notificationClipEnd : shelfStart;
+ updateCornerRoundnessOnScroll(anv, viewStart, keyguardSafeShelfStart);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
index 96ce6b45dc71..13d8adb079de 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScroller.kt
@@ -9,6 +9,7 @@ import android.view.animation.PathInterpolator
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.ScrimController
@@ -16,16 +17,18 @@ import com.android.systemui.statusbar.policy.ConfigurationController
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
+import java.io.PrintWriter
class SplitShadeLockScreenOverScroller
@AssistedInject
constructor(
configurationController: ConfigurationController,
+ dumpManager: DumpManager,
private val context: Context,
private val scrimController: ScrimController,
private val statusBarStateController: SysuiStatusBarStateController,
- @Assisted private val qS: QS,
- @Assisted private val nsslController: NotificationStackScrollLayoutController
+ @Assisted private val qSProvider: () -> QS,
+ @Assisted private val nsslControllerProvider: () -> NotificationStackScrollLayoutController
) : LockScreenShadeOverScroller {
private var releaseOverScrollAnimator: Animator? = null
@@ -34,6 +37,12 @@ constructor(
private var maxOverScrollAmount = 0
private var previousOverscrollAmount = 0
+ private val qS: QS
+ get() = qSProvider()
+
+ private val nsslController: NotificationStackScrollLayoutController
+ get() = nsslControllerProvider()
+
init {
updateResources()
configurationController.addCallback(
@@ -42,6 +51,9 @@ constructor(
updateResources()
}
})
+ dumpManager.registerDumpable("SplitShadeLockscreenOverScroller") { printWriter, _ ->
+ dump(printWriter)
+ }
}
private fun updateResources() {
@@ -114,11 +126,25 @@ constructor(
releaseOverScrollAnimator = null
}
+ private fun dump(printWriter: PrintWriter) {
+ printWriter.println(
+ """
+ SplitShadeLockScreenOverScroller:
+ Resources:
+ transitionToFullShadeDistance: $transitionToFullShadeDistance
+ maxOverScrollAmount: $maxOverScrollAmount
+ releaseOverScrollDuration: $releaseOverScrollDuration
+ State:
+ previousOverscrollAmount: $previousOverscrollAmount
+ expansionDragDownAmount: $expansionDragDownAmount
+ """.trimIndent())
+ }
+
@AssistedFactory
fun interface Factory {
fun create(
- qS: QS,
- nsslController: NotificationStackScrollLayoutController
+ qSProvider: () -> QS,
+ nsslControllerProvider: () -> NotificationStackScrollLayoutController
): SplitShadeLockScreenOverScroller
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
index 718bc5caf414..f78b067279bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarState.java
@@ -22,18 +22,21 @@ package com.android.systemui.statusbar;
public class StatusBarState {
/**
- * The status bar is in the "normal" shade mode.
+ * The status bar is in the "normal", unlocked mode or the device is still locked but we're
+ * accessing camera from power button double-tap shortcut.
*/
public static final int SHADE = 0;
/**
- * Status bar is currently the Keyguard.
+ * Status bar is currently the Keyguard. In single column mode, when you swipe from the top of
+ * the keyguard to expand QS immediately, it's still KEYGUARD state.
*/
public static final int KEYGUARD = 1;
/**
- * Status bar is in the special mode, where it is fully interactive but still locked. So
- * dismissing the shade will still show the bouncer.
+ * Status bar is in the special mode, where it was transitioned from lockscreen to shade.
+ * Depending on user's security settings, dismissing the shade will either show the
+ * bouncer or go directly to unlocked {@link #SHADE} mode.
*/
public static final int SHADE_LOCKED = 2;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
index d49e1e6acc23..a1dc7b41d42b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/NetworkControllerImpl.java
@@ -73,6 +73,9 @@ import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
+import com.android.systemui.log.LogLevel;
+import com.android.systemui.log.dagger.StatusBarNetworkControllerLog;
import com.android.systemui.qs.tiles.dialog.InternetDialogFactory;
import com.android.systemui.settings.CurrentUserTracker;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -94,9 +97,12 @@ import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;
+import java.util.stream.Collectors;
import javax.inject.Inject;
+import kotlin.Unit;
+
/** Platform implementation of the network controller. **/
@SysUISingleton
public class NetworkControllerImpl extends BroadcastReceiver
@@ -133,6 +139,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
private final CarrierConfigTracker mCarrierConfigTracker;
private final FeatureFlags mFeatureFlags;
private final DumpManager mDumpManager;
+ private final LogBuffer mLogBuffer;
private TelephonyCallback.ActiveDataSubscriptionIdListener mPhoneStateListener;
private int mActiveMobileDataSubscription = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
@@ -231,7 +238,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
@Main Handler handler,
InternetDialogFactory internetDialogFactory,
FeatureFlags featureFlags,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @StatusBarNetworkControllerLog LogBuffer logBuffer) {
this(context, connectivityManager,
telephonyManager,
telephonyListenerManager,
@@ -251,7 +259,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
trackerFactory,
handler,
featureFlags,
- dumpManager);
+ dumpManager,
+ logBuffer);
mReceiverHandler.post(mRegisterListeners);
mInternetDialogFactory = internetDialogFactory;
}
@@ -276,7 +285,8 @@ public class NetworkControllerImpl extends BroadcastReceiver
WifiStatusTrackerFactory trackerFactory,
@Main Handler handler,
FeatureFlags featureFlags,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ LogBuffer logBuffer
) {
mContext = context;
mTelephonyListenerManager = telephonyListenerManager;
@@ -297,6 +307,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
mCarrierConfigTracker = carrierConfigTracker;
mFeatureFlags = featureFlags;
mDumpManager = dumpManager;
+ mLogBuffer = logBuffer;
// telephony
mPhone = telephonyManager;
@@ -770,6 +781,17 @@ public class NetworkControllerImpl extends BroadcastReceiver
Log.d(TAG, "onReceive: intent=" + intent);
}
final String action = intent.getAction();
+ mLogBuffer.log(
+ TAG,
+ LogLevel.INFO,
+ logMessage -> {
+ logMessage.setStr1(action);
+ return Unit.INSTANCE;
+ },
+ logMessage -> String.format(
+ Locale.US,
+ "Received broadcast with action \"%s\"",
+ logMessage.getStr1()));
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
updateConnectivity();
@@ -937,6 +959,12 @@ public class NetworkControllerImpl extends BroadcastReceiver
: lhs.getSimSlotIndex() - rhs.getSimSlotIndex();
}
});
+ Log.i(
+ TAG,
+ String.format(
+ Locale.US,
+ "Subscriptions changed: %s",
+ createSubscriptionChangeStatement(mCurrentSubscriptions, subscriptions)));
mCurrentSubscriptions = subscriptions;
SparseArray<MobileSignalController> cachedControllers =
@@ -1154,6 +1182,7 @@ public class NetworkControllerImpl extends BroadcastReceiver
pw.print(" hasVoiceCallingFeature()=");
pw.println(hasVoiceCallingFeature());
pw.println(" mListening=" + mListening);
+ pw.println(" mActiveMobileDataSubscription=" + mActiveMobileDataSubscription);
pw.println(" - connectivity ------");
pw.print(" mConnectedTransports=");
@@ -1473,4 +1502,23 @@ public class NetworkControllerImpl extends BroadcastReceiver
* get created will also run on the BG Looper.
*/
private final Runnable mRegisterListeners = () -> registerListeners();
+
+ /** Returns a logging statement for the given old and new list of {@link SubscriptionInfo} */
+ private static String createSubscriptionChangeStatement(
+ final @Nullable List<SubscriptionInfo> oldSubscriptions,
+ final @Nullable List<SubscriptionInfo> newSubscriptions) {
+ return String.format(
+ Locale.US,
+ "old=%s, new=%s",
+ toSubscriptionIds(oldSubscriptions),
+ toSubscriptionIds(newSubscriptions));
+ }
+
+ /** Returns to a list of subscription IDs for the given list of {@link SubscriptionInfo} */
+ @Nullable
+ private static List<Integer> toSubscriptionIds(
+ final @Nullable List<SubscriptionInfo> subscriptions) {
+ return subscriptions != null ? subscriptions.stream().map(
+ SubscriptionInfo::getSubscriptionId).collect(Collectors.toList()) : null;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
index a62a152ab7f4..5351024c2f9f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt
@@ -24,6 +24,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
import com.android.systemui.statusbar.window.StatusBarWindowController
import java.lang.IllegalStateException
import javax.inject.Inject
@@ -34,7 +35,8 @@ import javax.inject.Inject
*/
@CentralSurfacesScope
class StatusBarInitializer @Inject constructor(
- private val windowController: StatusBarWindowController
+ private val windowController: StatusBarWindowController,
+ private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>,
) {
var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null
@@ -56,6 +58,9 @@ class StatusBarInitializer @Inject constructor(
statusBarFragmentComponent.phoneStatusBarViewController,
statusBarFragmentComponent.phoneStatusBarTransitions
)
+ creationListeners.forEach { listener ->
+ listener.onStatusBarViewInitialized(statusBarFragmentComponent)
+ }
}
override fun onFragmentViewDestroyed(tag: String?, fragment: Fragment?) {
@@ -69,6 +74,17 @@ class StatusBarInitializer @Inject constructor(
.commit()
}
+ interface OnStatusBarViewInitializedListener {
+
+ /**
+ * The status bar view has been initialized.
+ *
+ * @param component Dagger component that is created when the status bar view is created.
+ * Can be used to retrieve dependencies from that scope, including the status bar root view.
+ */
+ fun onStatusBarViewInitialized(component: StatusBarFragmentComponent)
+ }
+
interface OnStatusBarViewUpdatedListener {
fun onStatusBarViewUpdated(
statusBarView: PhoneStatusBarView,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 29411e63b163..9e77dbc08c7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -21,6 +21,7 @@ import android.content.Context;
import android.os.Handler;
import android.service.dreams.IDreamManager;
+import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.statusbar.IStatusBarService;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.animation.ActivityLaunchAnimator;
@@ -67,6 +68,7 @@ import com.android.systemui.statusbar.phone.ManagedProfileController;
import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconControllerImpl;
+import com.android.systemui.statusbar.phone.StatusBarIconList;
import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallFlags;
@@ -138,12 +140,10 @@ public interface CentralSurfacesDependenciesModule {
Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
Lazy<NotificationShadeWindowController> notificationShadeWindowController,
NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager notificationEntryManager,
MediaArtworkProcessor mediaArtworkProcessor,
KeyguardBypassController keyguardBypassController,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
- NotifPipelineFlags notifPipelineFlags,
@Main DelayableExecutor mainExecutor,
MediaDataManager mediaDataManager,
DumpManager dumpManager) {
@@ -152,12 +152,10 @@ public interface CentralSurfacesDependenciesModule {
centralSurfacesOptionalLazy,
notificationShadeWindowController,
visibilityProvider,
- notificationEntryManager,
mediaArtworkProcessor,
keyguardBypassController,
notifPipeline,
notifCollection,
- notifPipelineFlags,
mainExecutor,
mediaDataManager,
dumpManager);
@@ -259,6 +257,16 @@ public interface CentralSurfacesDependenciesModule {
*/
@Provides
@SysUISingleton
+ static StatusBarIconList provideStatusBarIconList(Context context) {
+ return new StatusBarIconList(
+ context.getResources().getStringArray(
+ com.android.internal.R.array.config_statusBarIcons));
+ }
+
+ /**
+ */
+ @Provides
+ @SysUISingleton
static OngoingCallController provideOngoingCallController(
Context context,
CommonNotifCollection notifCollection,
@@ -317,7 +325,8 @@ public interface CentralSurfacesDependenciesModule {
*/
@Provides
@SysUISingleton
- static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager) {
- return new DialogLaunchAnimator(dreamManager);
+ static DialogLaunchAnimator provideDialogLaunchAnimator(IDreamManager dreamManager,
+ InteractionJankMonitor interactionJankMonitor) {
+ return new DialogLaunchAnimator(dreamManager, interactionJankMonitor);
}
}
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 a35bced819c0..a509fcaad4ee 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -37,6 +37,8 @@ import com.android.settingslib.Utils
import com.android.systemui.R
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
@@ -44,13 +46,11 @@ import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.DeviceProvisionedController
import com.android.systemui.util.concurrency.Execution
import com.android.systemui.util.settings.SecureSettings
-import java.lang.RuntimeException
import java.util.Optional
import java.util.concurrent.Executor
import javax.inject.Inject
@@ -71,6 +71,7 @@ class LockscreenSmartspaceController @Inject constructor(
private val configurationController: ConfigurationController,
private val statusBarStateController: StatusBarStateController,
private val deviceProvisionedController: DeviceProvisionedController,
+ private val bypassController: KeyguardBypassController,
private val execution: Execution,
@Main private val uiExecutor: Executor,
@Main private val handler: Handler,
@@ -154,6 +155,13 @@ class LockscreenSmartspaceController @Inject constructor(
}
}
+ private val bypassStateChangedListener =
+ object : KeyguardBypassController.OnBypassStateChangedListener {
+ override fun onBypassStateChanged(isEnabled: Boolean) {
+ updateBypassEnabled()
+ }
+ }
+
init {
deviceProvisionedController.addCallback(deviceProvisionedListener)
}
@@ -164,6 +172,11 @@ class LockscreenSmartspaceController @Inject constructor(
return featureFlags.isEnabled(Flags.SMARTSPACE) && plugin != null
}
+ private fun updateBypassEnabled() {
+ val bypassEnabled = bypassController.bypassEnabled
+ smartspaceViews.forEach { it.setKeyguardBypassEnabled(bypassEnabled) }
+ }
+
/**
* Constructs the smartspace view and connects it to the smartspace service.
*/
@@ -211,6 +224,7 @@ class LockscreenSmartspaceController @Inject constructor(
}
})
ssView.setFalsingManager(falsingManager)
+ ssView.setKeyguardBypassEnabled(bypassController.bypassEnabled)
return (ssView as View).apply { addOnAttachStateChangeListener(stateChangeListener) }
}
@@ -248,11 +262,13 @@ class LockscreenSmartspaceController @Inject constructor(
)
configurationController.addCallback(configChangeListener)
statusBarStateController.addCallback(statusBarStateListener)
+ bypassController.registerOnBypassStateChangedListener(bypassStateChangedListener)
plugin.registerSmartspaceEventNotifier {
e -> session?.notifySmartspaceEvent(e)
}
+ updateBypassEnabled()
reloadSmartspace()
}
@@ -276,6 +292,7 @@ class LockscreenSmartspaceController @Inject constructor(
contentResolver.unregisterContentObserver(settingsObserver)
configurationController.removeCallback(configChangeListener)
statusBarStateController.removeCallback(statusBarStateListener)
+ bypassController.unregisterOnBypassStateChangedListener(bypassStateChangedListener)
session = null
plugin?.registerSmartspaceEventNotifier(null)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 5aeab84b677c..d24896148095 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -31,7 +31,6 @@ import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -129,11 +128,9 @@ class AnimatedImageNotificationManager @Inject constructor(
*/
@SysUISingleton
class ConversationNotificationManager @Inject constructor(
- private val bindEventManager: BindEventManager,
- private val notificationGroupManager: NotificationGroupManagerLegacy,
+ bindEventManager: BindEventManager,
private val context: Context,
private val notifCollection: CommonNotifCollection,
- private val featureFlags: NotifPipelineFlags,
@Main private val mainHandler: Handler
) {
// Need this state to be thread safe, since it's accessed from the ui thread
@@ -172,12 +169,10 @@ class ConversationNotificationManager @Inject constructor(
layout.setIsImportantConversation(important, false)
}
}
- if (changed && !featureFlags.isNewPipelineEnabled()) {
- notificationGroupManager.updateIsolation(entry)
- }
}
}
}
+
fun onEntryViewBound(entry: NotificationEntry) {
if (!entry.ranking.isConversation) {
return
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
index a0ccd5726c75..1be4c04ef804 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/DynamicPrivacyController.java
@@ -80,7 +80,7 @@ public class DynamicPrivacyController implements KeyguardStateController.Callbac
@VisibleForTesting
boolean isDynamicPrivacyEnabled() {
- return !mLockscreenUserManager.userAllowsPrivateNotificationsInPublic(
+ return mLockscreenUserManager.userAllowsNotificationsInPublic(
mLockscreenUserManager.getCurrentUserId());
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
index df412ed93f55..8cb18a06057e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/InstantAppNotifier.java
@@ -260,7 +260,7 @@ public class InstantAppNotifier extends CoreStartable
Intent browserIntent = getTaskIntent(taskId, userId);
Notification.Builder builder =
- new Notification.Builder(mContext, NotificationChannels.GENERAL);
+ new Notification.Builder(mContext, NotificationChannels.INSTANT);
if (browserIntent != null && browserIntent.isWebIntent()) {
// Make sure that this doesn't resolve back to an instant app
browserIntent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
index f6a55e6b75c6..0c6a91fe61f1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotifPipelineFlags.kt
@@ -48,8 +48,7 @@ class NotifPipelineFlags @Inject constructor(
fun assertLegacyPipelineEnabled(): Unit =
check(!isNewPipelineEnabled()) { "Old pipeline code running w/ new pipeline enabled" }
- fun isNewPipelineEnabled(): Boolean =
- featureFlags.isEnabled(Flags.NEW_NOTIFICATION_PIPELINE_RENDERING)
+ fun isNewPipelineEnabled(): Boolean = true
fun isDevLoggingEnabled(): Boolean =
featureFlags.isEnabled(Flags.NOTIFICATION_PIPELINE_DEVELOPER_LOGGING)
@@ -57,4 +56,10 @@ class NotifPipelineFlags @Inject constructor(
fun isSmartspaceDedupingEnabled(): Boolean =
featureFlags.isEnabled(Flags.SMARTSPACE) &&
featureFlags.isEnabled(Flags.SMARTSPACE_DEDUPING)
-} \ No newline at end of file
+
+ fun removeUnrankedNotifs(): Boolean =
+ featureFlags.isEnabled(Flags.REMOVE_UNRANKED_NOTIFICATIONS)
+
+ fun fullScreenIntentRequiresKeyguard(): Boolean =
+ featureFlags.isEnabled(Flags.FSI_REQUIRES_KEYGUARD)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index c86bf93628dd..852d5f7a16e6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -41,6 +41,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationLifetimeExtender;
import com.android.systemui.statusbar.NotificationListener;
@@ -73,6 +74,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.Executor;
import dagger.Lazy;
@@ -113,6 +115,7 @@ public class NotificationEntryManager implements
private final IStatusBarService mStatusBarService;
private final NotifLiveDataStoreImpl mNotifLiveDataStore;
private final DumpManager mDumpManager;
+ private final Executor mBgExecutor;
private final Set<NotificationEntry> mAllNotifications = new ArraySet<>();
private final Set<NotificationEntry> mReadOnlyAllNotifications =
@@ -159,7 +162,8 @@ public class NotificationEntryManager implements
LeakDetector leakDetector,
IStatusBarService statusBarService,
NotifLiveDataStoreImpl notifLiveDataStore,
- DumpManager dumpManager
+ DumpManager dumpManager,
+ @Background Executor bgExecutor
) {
mLogger = logger;
mGroupManager = groupManager;
@@ -170,6 +174,7 @@ public class NotificationEntryManager implements
mStatusBarService = statusBarService;
mNotifLiveDataStore = notifLiveDataStore;
mDumpManager = dumpManager;
+ mBgExecutor = bgExecutor;
}
/** Once called, the NEM will start processing notification events from system server. */
@@ -566,17 +571,19 @@ public class NotificationEntryManager implements
private void sendNotificationRemovalToServer(
StatusBarNotification notification,
DismissedByUserStats dismissedByUserStats) {
- try {
- mStatusBarService.onNotificationClear(
- notification.getPackageName(),
- notification.getUser().getIdentifier(),
- notification.getKey(),
- dismissedByUserStats.dismissalSurface,
- dismissedByUserStats.dismissalSentiment,
- dismissedByUserStats.notificationVisibility);
- } catch (RemoteException ex) {
- // system process is dead if we're here.
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mStatusBarService.onNotificationClear(
+ notification.getPackageName(),
+ notification.getUser().getIdentifier(),
+ notification.getKey(),
+ dismissedByUserStats.dismissalSurface,
+ dismissedByUserStats.dismissalSentiment,
+ dismissedByUserStats.notificationVisibility);
+ } catch (RemoteException ex) {
+ // system process is dead if we're here.
+ }
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
index 2397005a1a61..52dcf02e3a19 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManagerLogger.kt
@@ -17,18 +17,32 @@
package com.android.systemui.statusbar.notification
import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogMessage
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.util.Compile
import javax.inject.Inject
/** Logger for [NotificationEntryManager]. */
class NotificationEntryManagerLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
+ private val devLoggingEnabled by lazy { notifPipelineFlags.isDevLoggingEnabled() }
+
+ private inline fun devLog(
+ level: LogLevel,
+ initializer: LogMessage.() -> Unit,
+ noinline printer: LogMessage.() -> String
+ ) {
+ if (Compile.IS_DEBUG && devLoggingEnabled) buffer.log(TAG, level, initializer, printer)
+ }
+
fun logNotifAdded(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF ADDED $str1"
@@ -36,7 +50,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifUpdated(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF UPDATED $str1"
@@ -44,7 +58,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logInflationAborted(key: String, status: String, reason: String) {
- buffer.log(TAG, DEBUG, {
+ devLog(DEBUG, {
str1 = key
str2 = status
str3 = reason
@@ -54,7 +68,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifInflated(key: String, isNew: Boolean) {
- buffer.log(TAG, DEBUG, {
+ devLog(DEBUG, {
str1 = key
bool1 = isNew
}, {
@@ -63,7 +77,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logRemovalIntercepted(key: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
}, {
"NOTIF REMOVE INTERCEPTED for $str1"
@@ -71,7 +85,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logLifetimeExtended(key: String, extenderName: String, status: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
str2 = extenderName
str3 = status
@@ -81,7 +95,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logNotifRemoved(key: String, removedByUser: Boolean) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = key
bool1 = removedByUser
}, {
@@ -90,7 +104,7 @@ class NotificationEntryManagerLogger @Inject constructor(
}
fun logFilterAndSort(reason: String) {
- buffer.log(TAG, INFO, {
+ devLog(INFO, {
str1 = reason
}, {
"FILTER AND SORT reason=$str1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index b764c271de45..31b21c9b5321 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -4,10 +4,10 @@ import android.view.ViewGroup
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.animation.ActivityLaunchAnimator
import com.android.systemui.animation.LaunchAnimator
+import com.android.systemui.shade.NotificationShadeWindowViewController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
-import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
import com.android.systemui.statusbar.policy.HeadsUpUtil
import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
new file mode 100644
index 000000000000..ed80f33be701
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/OWNERS
@@ -0,0 +1,14 @@
+set noparent
+
+# Bug component: 78010
+
+aaliomer@google.com
+aroederer@google.com
+jeffdq@google.com
+juliacr@google.com
+juliatuttle@google.com
+lynhan@google.com
+steell@google.com
+yurilin@google.com
+
+per-file MediaNotificationProcessor.java = ethibodeau@google.com, asc@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
index 6be8a491eead..f6a572ec6ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ListDumper.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.collection.NotificationEntry.DismissState.NOT_DISMISSED;
@@ -52,7 +53,7 @@ public class ListDumper {
sb,
true,
includeRecordKeeping,
- interactionTracker.hasUserInteractedWith(entry.getKey()));
+ interactionTracker.hasUserInteractedWith(logKey(entry)));
if (entry instanceof GroupEntry) {
GroupEntry ge = (GroupEntry) entry;
NotificationEntry summary = ge.getSummary();
@@ -63,7 +64,7 @@ public class ListDumper {
sb,
true,
includeRecordKeeping,
- interactionTracker.hasUserInteractedWith(summary.getKey()));
+ interactionTracker.hasUserInteractedWith(logKey(summary)));
}
List<NotificationEntry> children = ge.getChildren();
for (int childIndex = 0; childIndex < children.size(); childIndex++) {
@@ -74,7 +75,7 @@ public class ListDumper {
sb,
true,
includeRecordKeeping,
- interactionTracker.hasUserInteractedWith(child.getKey()));
+ interactionTracker.hasUserInteractedWith(logKey(child)));
}
}
}
@@ -115,12 +116,19 @@ public class ListDumper {
) {
sb.append(indent)
.append("[").append(index).append("] ")
- .append(entry.getKey());
+ .append(index.length() == 1 ? " " : "")
+ .append(logKey(entry));
if (includeParent) {
sb.append(" (parent=")
- .append(entry.getParent() != null ? entry.getParent().getKey() : null)
+ .append(logKey(entry.getParent()))
.append(")");
+
+ NotificationEntry notifEntry = entry.getRepresentativeEntry();
+ if (notifEntry != null) {
+ sb.append(" rank=")
+ .append(notifEntry.getRanking().getRank());
+ }
}
if (entry.getSection() != null) {
@@ -178,15 +186,15 @@ public class ListDumper {
if (notifEntry.getAttachState().getSuppressedChanges().getParent() != null) {
rksb.append("suppressedParent=")
- .append(notifEntry.getAttachState().getSuppressedChanges()
- .getParent().getKey())
+ .append(logKey(notifEntry.getAttachState().getSuppressedChanges()
+ .getParent()))
.append(" ");
}
if (notifEntry.getAttachState().getSuppressedChanges().getSection() != null) {
rksb.append("suppressedSection=")
.append(notifEntry.getAttachState().getSuppressedChanges()
- .getSection())
+ .getSection().getLabel())
.append(" ");
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index 6085096ee124..351a4bea2947 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -70,6 +70,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.dump.LogBufferEulogizer;
@@ -89,6 +90,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.In
import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLoggerKt;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifEvent;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
@@ -104,11 +106,14 @@ import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@@ -143,6 +148,7 @@ public class NotifCollection implements Dumpable {
private final NotifPipelineFlags mNotifPipelineFlags;
private final NotifCollectionLogger mLogger;
private final Handler mMainHandler;
+ private final Executor mBgExecutor;
private final LogBufferEulogizer mEulogizer;
private final DumpManager mDumpManager;
@@ -156,6 +162,8 @@ public class NotifCollection implements Dumpable {
private final List<NotifLifetimeExtender> mLifetimeExtenders = new ArrayList<>();
private final List<NotifDismissInterceptor> mDismissInterceptors = new ArrayList<>();
+ private Set<String> mNotificationsWithoutRankings = Collections.emptySet();
+
private Queue<NotifEvent> mEventQueue = new ArrayDeque<>();
private boolean mAttached = false;
@@ -169,6 +177,7 @@ public class NotifCollection implements Dumpable {
NotifPipelineFlags notifPipelineFlags,
NotifCollectionLogger logger,
@Main Handler mainHandler,
+ @Background Executor bgExecutor,
LogBufferEulogizer logBufferEulogizer,
DumpManager dumpManager) {
mStatusBarService = statusBarService;
@@ -176,6 +185,7 @@ public class NotifCollection implements Dumpable {
mNotifPipelineFlags = notifPipelineFlags;
mLogger = logger;
mMainHandler = mainHandler;
+ mBgExecutor = bgExecutor;
mEulogizer = logBufferEulogizer;
mDumpManager = dumpManager;
}
@@ -266,13 +276,14 @@ public class NotifCollection implements Dumpable {
requireNonNull(stats);
NotificationEntry storedEntry = mNotificationSet.get(entry.getKey());
if (storedEntry == null) {
- mLogger.logNonExistentNotifDismissed(entry.getKey());
+ mLogger.logNonExistentNotifDismissed(entry);
continue;
}
if (entry != storedEntry) {
throw mEulogizer.record(
new IllegalStateException("Invalid entry: "
- + "different stored and dismissed entries for " + entry.getKey()));
+ + "different stored and dismissed entries for " + logKey(entry)
+ + " stored=@" + Integer.toHexString(storedEntry.hashCode())));
}
if (entry.getDismissState() == DISMISSED) {
@@ -281,30 +292,32 @@ public class NotifCollection implements Dumpable {
updateDismissInterceptors(entry);
if (isDismissIntercepted(entry)) {
- mLogger.logNotifDismissedIntercepted(entry.getKey());
+ mLogger.logNotifDismissedIntercepted(entry);
continue;
}
entriesToLocallyDismiss.add(entry);
if (!isCanceled(entry)) {
// send message to system server if this notification hasn't already been cancelled
- try {
- mStatusBarService.onNotificationClear(
- entry.getSbn().getPackageName(),
- entry.getSbn().getUser().getIdentifier(),
- entry.getSbn().getKey(),
- stats.dismissalSurface,
- stats.dismissalSentiment,
- stats.notificationVisibility);
- } catch (RemoteException e) {
- // system process is dead if we're here.
- mLogger.logRemoteExceptionOnNotificationClear(entry.getKey(), e);
- }
+ mBgExecutor.execute(() -> {
+ try {
+ mStatusBarService.onNotificationClear(
+ entry.getSbn().getPackageName(),
+ entry.getSbn().getUser().getIdentifier(),
+ entry.getSbn().getKey(),
+ stats.dismissalSurface,
+ stats.dismissalSentiment,
+ stats.notificationVisibility);
+ } catch (RemoteException e) {
+ // system process is dead if we're here.
+ mLogger.logRemoteExceptionOnNotificationClear(entry, e);
+ }
+ });
}
}
locallyDismissNotifications(entriesToLocallyDismiss);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("dismissNotifications");
}
/**
@@ -341,14 +354,14 @@ public class NotifCollection implements Dumpable {
// interceptors the chance to filter the notification
updateDismissInterceptors(entry);
if (isDismissIntercepted(entry)) {
- mLogger.logNotifClearAllDismissalIntercepted(entry.getKey());
+ mLogger.logNotifClearAllDismissalIntercepted(entry);
}
entries.remove(i);
}
}
locallyDismissNotifications(entries);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("dismissAllNotifications");
}
/**
@@ -362,7 +375,7 @@ public class NotifCollection implements Dumpable {
NotificationEntry entry = entries.get(i);
entry.setDismissState(DISMISSED);
- mLogger.logNotifDismissed(entry.getKey());
+ mLogger.logNotifDismissed(entry);
if (isCanceled(entry)) {
canceledEntries.add(entry);
@@ -395,7 +408,7 @@ public class NotifCollection implements Dumpable {
postNotification(sbn, requireRanking(rankingMap, sbn.getKey()));
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationPosted");
}
private void onNotificationGroupPosted(List<CoalescedEvent> batch) {
@@ -406,7 +419,7 @@ public class NotifCollection implements Dumpable {
for (CoalescedEvent event : batch) {
postNotification(event.getSbn(), event.getRanking());
}
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationGroupPosted");
}
private void onNotificationRemoved(
@@ -415,26 +428,26 @@ public class NotifCollection implements Dumpable {
int reason) {
Assert.isMainThread();
- mLogger.logNotifRemoved(sbn.getKey(), reason);
+ mLogger.logNotifRemoved(sbn, reason);
final NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
// TODO (b/160008901): Throw an exception here
- mLogger.logNoNotificationToRemoveWithKey(sbn.getKey(), reason);
+ mLogger.logNoNotificationToRemoveWithKey(sbn, reason);
return;
}
entry.mCancellationReason = reason;
tryRemoveNotification(entry);
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationRemoved");
}
private void onNotificationRankingUpdate(RankingMap rankingMap) {
Assert.isMainThread();
mEventQueue.add(new RankingUpdatedEvent(rankingMap));
applyRanking(rankingMap);
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationRankingUpdate");
}
private void onNotificationChannelModified(
@@ -444,7 +457,7 @@ public class NotifCollection implements Dumpable {
int modificationType) {
Assert.isMainThread();
mEventQueue.add(new ChannelChangedEvent(pkgName, user, channel, modificationType));
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onNotificationChannelModified");
}
private void onNotificationsInitialized() {
@@ -463,7 +476,7 @@ public class NotifCollection implements Dumpable {
mEventQueue.add(new BindEntryEvent(entry, sbn));
mNotificationSet.put(sbn.getKey(), entry);
- mLogger.logNotifPosted(sbn.getKey());
+ mLogger.logNotifPosted(entry);
mEventQueue.add(new EntryAddedEvent(entry));
} else {
@@ -482,7 +495,7 @@ public class NotifCollection implements Dumpable {
entry.setSbn(sbn);
mEventQueue.add(new BindEntryEvent(entry, sbn));
- mLogger.logNotifUpdated(sbn.getKey());
+ mLogger.logNotifUpdated(entry);
mEventQueue.add(new EntryUpdatedEvent(entry, true /* fromSystem */));
}
}
@@ -497,12 +510,12 @@ public class NotifCollection implements Dumpable {
if (mNotificationSet.get(entry.getKey()) != entry) {
throw mEulogizer.record(
new IllegalStateException("No notification to remove with key "
- + entry.getKey()));
+ + logKey(entry)));
}
if (!isCanceled(entry)) {
throw mEulogizer.record(
- new IllegalStateException("Cannot remove notification " + entry.getKey()
+ new IllegalStateException("Cannot remove notification " + logKey(entry)
+ ": has not been marked for removal"));
}
@@ -513,7 +526,7 @@ public class NotifCollection implements Dumpable {
}
if (!isLifetimeExtended(entry)) {
- mLogger.logNotifReleased(entry.getKey());
+ mLogger.logNotifReleased(entry);
mNotificationSet.remove(entry.getKey());
cancelDismissInterception(entry);
mEventQueue.add(new EntryRemovedEvent(entry, entry.mCancellationReason));
@@ -558,6 +571,7 @@ public class NotifCollection implements Dumpable {
}
private void applyRanking(@NonNull RankingMap rankingMap) {
+ ArrayMap<String, NotificationEntry> currentEntriesWithoutRankings = null;
for (NotificationEntry entry : mNotificationSet.values()) {
if (!isCanceled(entry)) {
@@ -571,22 +585,37 @@ public class NotifCollection implements Dumpable {
// TODO: (b/145659174) update the sbn's overrideGroupKey in
// NotificationEntry.setRanking instead of here once we fully migrate to the
// NewNotifPipeline
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- final String newOverrideGroupKey = ranking.getOverrideGroupKey();
- if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
- newOverrideGroupKey)) {
- entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
- }
+ final String newOverrideGroupKey = ranking.getOverrideGroupKey();
+ if (!Objects.equals(entry.getSbn().getOverrideGroupKey(),
+ newOverrideGroupKey)) {
+ entry.getSbn().setOverrideGroupKey(newOverrideGroupKey);
}
} else {
- mLogger.logRankingMissing(entry.getKey(), rankingMap);
+ if (currentEntriesWithoutRankings == null) {
+ currentEntriesWithoutRankings = new ArrayMap<>();
+ }
+ currentEntriesWithoutRankings.put(entry.getKey(), entry);
}
}
}
+ NotifCollectionLoggerKt.maybeLogInconsistentRankings(
+ mLogger,
+ mNotificationsWithoutRankings,
+ currentEntriesWithoutRankings,
+ rankingMap
+ );
+ mNotificationsWithoutRankings = currentEntriesWithoutRankings == null
+ ? Collections.emptySet() : currentEntriesWithoutRankings.keySet();
+ if (currentEntriesWithoutRankings != null && mNotifPipelineFlags.removeUnrankedNotifs()) {
+ for (NotificationEntry entry : currentEntriesWithoutRankings.values()) {
+ entry.mCancellationReason = REASON_UNKNOWN;
+ tryRemoveNotification(entry);
+ }
+ }
mEventQueue.add(new RankingAppliedEvent());
}
- private void dispatchEventsAndRebuildList() {
+ private void dispatchEventsAndRebuildList(String reason) {
Trace.beginSection("NotifCollection.dispatchEventsAndRebuildList");
mAmDispatchingToOtherCode = true;
while (!mEventQueue.isEmpty()) {
@@ -595,7 +624,7 @@ public class NotifCollection implements Dumpable {
mAmDispatchingToOtherCode = false;
if (mBuildListener != null) {
- mBuildListener.onBuildList(mReadOnlyNotificationSet);
+ mBuildListener.onBuildList(mReadOnlyNotificationSet, reason);
}
Trace.endSection();
}
@@ -609,22 +638,28 @@ public class NotifCollection implements Dumpable {
}
checkForReentrantCall();
+ NotificationEntry collectionEntry = getEntry(entry.getKey());
+ String logKey = logKey(entry);
+ String collectionEntryIs = collectionEntry == null ? "null"
+ : entry == collectionEntry ? "same" : "different";
+
+ if (entry != collectionEntry) {
+ // TODO: We should probably make this throw, but that's too risky right now
+ mLogger.logEntryBeingExtendedNotInCollection(entry, extender, collectionEntryIs);
+ }
+
if (!entry.mLifetimeExtenders.remove(extender)) {
throw mEulogizer.record(new IllegalStateException(
- String.format(
- "Cannot end lifetime extension for extender \"%s\" (%s)",
- extender.getName(),
- extender)));
+ String.format("Cannot end lifetime extension for extender \"%s\""
+ + " of entry %s (collection entry is %s)",
+ extender.getName(), logKey, collectionEntryIs)));
}
- mLogger.logLifetimeExtensionEnded(
- entry.getKey(),
- extender,
- entry.mLifetimeExtenders.size());
+ mLogger.logLifetimeExtensionEnded(entry, extender, entry.mLifetimeExtenders.size());
if (!isLifetimeExtended(entry)) {
if (tryRemoveNotification(entry)) {
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("onEndLifetimeExtension");
}
}
}
@@ -647,7 +682,7 @@ public class NotifCollection implements Dumpable {
mAmDispatchingToOtherCode = true;
for (NotifLifetimeExtender extender : mLifetimeExtenders) {
if (extender.maybeExtendLifetime(entry, entry.mCancellationReason)) {
- mLogger.logLifetimeExtended(entry.getKey(), extender);
+ mLogger.logLifetimeExtended(entry, extender);
entry.mLifetimeExtenders.add(extender);
}
}
@@ -820,16 +855,19 @@ public class NotifCollection implements Dumpable {
@Override
public void dump(PrintWriter pw, @NonNull String[] args) {
final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
+ entries.sort(Comparator.comparing(NotificationEntry::getKey));
- pw.println("\t" + TAG + " unsorted/unfiltered notifications:");
- if (entries.size() == 0) {
- pw.println("\t\t None");
- }
+ pw.println("\t" + TAG + " unsorted/unfiltered notifications: " + entries.size());
pw.println(
ListDumper.dumpList(
entries,
true,
"\t\t"));
+
+ pw.println("\n\tmNotificationsWithoutRankings: " + mNotificationsWithoutRankings.size());
+ for (String key : mNotificationsWithoutRankings) {
+ pw.println("\t * : " + key);
+ }
}
private final BatchableNotificationHandler mNotifHandler = new BatchableNotificationHandler() {
@@ -908,21 +946,21 @@ public class NotifCollection implements Dumpable {
// Make sure we have the notification to update
NotificationEntry entry = mNotificationSet.get(sbn.getKey());
if (entry == null) {
- mLogger.logNotifInternalUpdateFailed(sbn.getKey(), name, reason);
+ mLogger.logNotifInternalUpdateFailed(sbn, name, reason);
return;
}
- mLogger.logNotifInternalUpdate(sbn.getKey(), name, reason);
+ mLogger.logNotifInternalUpdate(entry, name, reason);
// First do the pieces of postNotification which are not about assuming the notification
// was sent by the app
entry.setSbn(sbn);
mEventQueue.add(new BindEntryEvent(entry, sbn));
- mLogger.logNotifUpdated(sbn.getKey());
+ mLogger.logNotifUpdated(entry);
mEventQueue.add(new EntryUpdatedEvent(entry, false /* fromSystem */));
// Skip the applyRanking step and go straight to dispatching the events
- dispatchEventsAndRebuildList();
+ dispatchEventsAndRebuildList("updateNotificationInternally");
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 4daed77c0954..d1aa01bb2125 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -75,8 +75,13 @@ public class NotifInflaterImpl implements NotifInflater {
}
@Override
- public void abortInflation(NotificationEntry entry) {
- entry.abortTask();
+ public boolean abortInflation(NotificationEntry entry) {
+ return entry.abortTask();
+ }
+
+ @Override
+ public void releaseViews(@NonNull NotificationEntry entry) {
+ requireBinder().releaseViews(entry);
}
private NotificationContentInflater.InflationCallback wrapInflationCallback(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
index e3ee8139d71c..f662a040fae6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.kt
@@ -16,7 +16,6 @@
package com.android.systemui.statusbar.notification.collection
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderGroupListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderListListener
@@ -76,7 +75,6 @@ import javax.inject.Inject
*/
@SysUISingleton
class NotifPipeline @Inject constructor(
- notifPipelineFlags: NotifPipelineFlags,
private val mNotifCollection: NotifCollection,
private val mShadeListBuilder: ShadeListBuilder,
private val mRenderStageManager: RenderStageManager
@@ -107,8 +105,6 @@ class NotifPipeline @Inject constructor(
return mNotifCollection.getEntry(key)
}
- val isNewPipelineEnabled: Boolean = notifPipelineFlags.isNewPipelineEnabled()
-
/**
* Registers a lifetime extender. Lifetime extenders can cause notifications that have been
* dismissed or retracted by system server to be temporarily retained in the collection.
@@ -253,4 +249,4 @@ class NotifPipeline @Inject constructor(
fun getInternalNotifUpdater(name: String?): InternalNotifUpdater {
return mNotifCollection.getInternalNotifUpdater(name)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index aedbd1b56622..b6392f705c49 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -175,6 +175,10 @@ public final class NotificationEntry extends ListEntry {
public boolean mRemoteEditImeAnimatingAway;
public boolean mRemoteEditImeVisible;
private boolean mExpandAnimationRunning;
+ /**
+ * Flag to determine if the entry is blockable by DnD filters
+ */
+ private boolean mBlockable;
/**
* @param sbn the StatusBarNotification from system server
@@ -253,6 +257,7 @@ public final class NotificationEntry extends ListEntry {
}
mRanking = ranking.withAudiblyAlertedInfo(mRanking);
+ updateIsBlockable();
}
/*
@@ -471,11 +476,13 @@ public final class NotificationEntry extends ListEntry {
/**
* Abort all existing inflation tasks
*/
- public void abortTask() {
+ public boolean abortTask() {
if (mRunningTask != null) {
mRunningTask.abort();
mRunningTask = null;
+ return true;
}
+ return false;
}
public void setInflationTask(InflationTask abortableTask) {
@@ -781,15 +788,20 @@ public final class NotificationEntry extends ListEntry {
* or is not in an allowList).
*/
public boolean isBlockable() {
+ return mBlockable;
+ }
+
+ private void updateIsBlockable() {
if (getChannel() == null) {
- return false;
+ mBlockable = false;
+ return;
}
if (getChannel().isImportanceLockedByCriticalDeviceFunction()
&& !getChannel().isBlockable()) {
- return false;
+ mBlockable = false;
+ return;
}
-
- return true;
+ mBlockable = true;
}
private boolean shouldSuppressVisualEffect(int effect) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
index df2fe4e8511f..075a0dc7555e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilder.java
@@ -304,70 +304,72 @@ public class ShadeListBuilder implements Dumpable {
private final CollectionReadyForBuildListener mReadyForBuildListener =
new CollectionReadyForBuildListener() {
@Override
- public void onBuildList(Collection<NotificationEntry> entries) {
+ public void onBuildList(Collection<NotificationEntry> entries, String reason) {
Assert.isMainThread();
mPipelineState.requireIsBefore(STATE_BUILD_STARTED);
- mLogger.logOnBuildList();
+ mLogger.logOnBuildList(reason);
mAllEntries = entries;
mChoreographer.schedule();
}
};
- private void onPreRenderInvalidated(Invalidator invalidator) {
+ private void onPreRenderInvalidated(Invalidator invalidator, @Nullable String reason) {
Assert.isMainThread();
- mLogger.logPreRenderInvalidated(invalidator.getName(), mPipelineState.getState());
+ mLogger.logPreRenderInvalidated(invalidator, mPipelineState.getState(), reason);
rebuildListIfBefore(STATE_FINALIZING);
}
- private void onPreGroupFilterInvalidated(NotifFilter filter) {
+ private void onPreGroupFilterInvalidated(NotifFilter filter, @Nullable String reason) {
Assert.isMainThread();
- mLogger.logPreGroupFilterInvalidated(filter.getName(), mPipelineState.getState());
+ mLogger.logPreGroupFilterInvalidated(filter, mPipelineState.getState(), reason);
rebuildListIfBefore(STATE_PRE_GROUP_FILTERING);
}
- private void onReorderingAllowedInvalidated(NotifStabilityManager stabilityManager) {
+ private void onReorderingAllowedInvalidated(NotifStabilityManager stabilityManager,
+ @Nullable String reason) {
Assert.isMainThread();
mLogger.logReorderingAllowedInvalidated(
- stabilityManager.getName(),
- mPipelineState.getState());
+ stabilityManager,
+ mPipelineState.getState(),
+ reason);
rebuildListIfBefore(STATE_GROUPING);
}
- private void onPromoterInvalidated(NotifPromoter promoter) {
+ private void onPromoterInvalidated(NotifPromoter promoter, @Nullable String reason) {
Assert.isMainThread();
- mLogger.logPromoterInvalidated(promoter.getName(), mPipelineState.getState());
+ mLogger.logPromoterInvalidated(promoter, mPipelineState.getState(), reason);
rebuildListIfBefore(STATE_TRANSFORMING);
}
- private void onNotifSectionInvalidated(NotifSectioner section) {
+ private void onNotifSectionInvalidated(NotifSectioner section, @Nullable String reason) {
Assert.isMainThread();
- mLogger.logNotifSectionInvalidated(section.getName(), mPipelineState.getState());
+ mLogger.logNotifSectionInvalidated(section, mPipelineState.getState(), reason);
rebuildListIfBefore(STATE_SORTING);
}
- private void onFinalizeFilterInvalidated(NotifFilter filter) {
+ private void onFinalizeFilterInvalidated(NotifFilter filter, @Nullable String reason) {
Assert.isMainThread();
- mLogger.logFinalizeFilterInvalidated(filter.getName(), mPipelineState.getState());
+ mLogger.logFinalizeFilterInvalidated(filter, mPipelineState.getState(), reason);
rebuildListIfBefore(STATE_FINALIZE_FILTERING);
}
- private void onNotifComparatorInvalidated(NotifComparator comparator) {
+ private void onNotifComparatorInvalidated(NotifComparator comparator, @Nullable String reason) {
Assert.isMainThread();
- mLogger.logNotifComparatorInvalidated(comparator.getName(), mPipelineState.getState());
+ mLogger.logNotifComparatorInvalidated(comparator, mPipelineState.getState(), reason);
rebuildListIfBefore(STATE_SORTING);
}
@@ -456,7 +458,8 @@ public class ShadeListBuilder implements Dumpable {
mLogger.logEndBuildList(
mIterationCount,
mReadOnlyNotifList.size(),
- countChildren(mReadOnlyNotifList));
+ countChildren(mReadOnlyNotifList),
+ /* enforcedVisualStability */ !mNotifStabilityManager.isEveryChangeAllowed());
if (mAlwaysLogList || mIterationCount % 10 == 0) {
Trace.beginSection("ShadeListBuilder.logFinalList");
mLogger.logFinalList(mNotifList);
@@ -579,11 +582,7 @@ public class ShadeListBuilder implements Dumpable {
if (existingSummary == null) {
group.setSummary(entry);
} else {
- mLogger.logDuplicateSummary(
- mIterationCount,
- group.getKey(),
- existingSummary.getKey(),
- entry.getKey());
+ mLogger.logDuplicateSummary(mIterationCount, group, existingSummary, entry);
// Use whichever one was posted most recently
if (entry.getSbn().getPostTime()
@@ -990,7 +989,7 @@ public class ShadeListBuilder implements Dumpable {
// Check for suppressed order changes
if (!getStabilityManager().isEveryChangeAllowed()) {
mForceReorderable = true;
- boolean isSorted = isSorted(mNotifList, mTopLevelComparator);
+ boolean isSorted = isShadeSorted();
mForceReorderable = false;
if (!isSorted) {
getStabilityManager().onEntryReorderSuppressed();
@@ -999,9 +998,23 @@ public class ShadeListBuilder implements Dumpable {
Trace.endSection();
}
+ private boolean isShadeSorted() {
+ if (!isSorted(mNotifList, mTopLevelComparator)) {
+ return false;
+ }
+ for (ListEntry entry : mNotifList) {
+ if (entry instanceof GroupEntry) {
+ if (!isSorted(((GroupEntry) entry).getChildren(), mGroupChildrenComparator)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
/** Determine whether the items in the list are sorted according to the comparator */
@VisibleForTesting
- public static <T> boolean isSorted(List<T> items, Comparator<T> comparator) {
+ public static <T> boolean isSorted(List<T> items, Comparator<? super T> comparator) {
if (items.size() <= 1) {
return true;
}
@@ -1070,7 +1083,7 @@ public class ShadeListBuilder implements Dumpable {
if (!Objects.equals(curr, prev)) {
mLogger.logEntryAttachStateChanged(
mIterationCount,
- entry.getKey(),
+ entry,
prev.getParent(),
curr.getParent());
@@ -1209,7 +1222,7 @@ public class ShadeListBuilder implements Dumpable {
};
- private final Comparator<ListEntry> mGroupChildrenComparator = (o1, o2) -> {
+ private final Comparator<NotificationEntry> mGroupChildrenComparator = (o1, o2) -> {
int index1 = canReorder(o1) ? -1 : o1.getPreviousAttachState().getStableIndex();
int index2 = canReorder(o2) ? -1 : o2.getPreviousAttachState().getStableIndex();
int cmp = Integer.compare(index1, index2);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
index b923fdfe6be8..3627b4084ec7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/BubbleCoordinator.java
@@ -127,14 +127,6 @@ public class BubbleCoordinator implements Coordinator {
DismissedByUserStats dismissedByUserStats,
int reason
) {
- if (!mNotifPipeline.isNewPipelineEnabled()) {
- // The `entry` will be from whichever pipeline is active, so if the old pipeline is
- // running, make sure that we use the new pipeline's entry (if it still exists).
- NotificationEntry newPipelineEntry = mNotifPipeline.getEntry(entry.getKey());
- if (newPipelineEntry != null) {
- entry = newPipelineEntry;
- }
- }
if (isInterceptingDismissal(entry)) {
mInterceptedDismissalEntries.remove(entry.getKey());
mOnEndDismissInterception.onEndDismissInterception(mDismissInterceptor, entry,
@@ -150,7 +142,7 @@ public class BubbleCoordinator implements Coordinator {
@Override
public void invalidateNotifications(String reason) {
- mNotifFilter.invalidateList();
+ mNotifFilter.invalidateList(reason);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
index df54ccd85e73..240540515705 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DebugModeCoordinator.kt
@@ -30,12 +30,12 @@ class DebugModeCoordinator @Inject constructor(
) : Coordinator {
override fun attach(pipeline: NotifPipeline) {
- pipeline.addPreGroupFilter(preGroupFilter)
- debugModeFilterProvider.registerInvalidationListener(preGroupFilter::invalidateList)
+ pipeline.addPreGroupFilter(filter)
+ debugModeFilterProvider.registerInvalidationListener { filter.invalidateList(null) }
}
- private val preGroupFilter = object : NotifFilter("DebugModeCoordinator") {
+ private val filter = object : NotifFilter("DebugModeFilter") {
override fun shouldFilterOut(entry: NotificationEntry, now: Long) =
debugModeFilterProvider.shouldFilterOut(entry)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
index e8652493da6b..058042c4bccd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/DeviceProvisionedCoordinator.java
@@ -90,7 +90,7 @@ public class DeviceProvisionedCoordinator implements Coordinator {
new DeviceProvisionedController.DeviceProvisionedListener() {
@Override
public void onDeviceProvisionedChanged() {
- mNotifFilter.invalidateList();
+ mNotifFilter.invalidateList("onDeviceProvisionedChanged");
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index da0169bd6dc4..8278b549a7a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.app.Notification
import android.app.Notification.GROUP_ALERT_SUMMARY
import android.util.ArrayMap
-import android.util.ArraySet
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationRemoteInputManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
@@ -36,11 +35,13 @@ import com.android.systemui.statusbar.notification.collection.render.NodeControl
import com.android.systemui.statusbar.notification.dagger.IncomingHeader
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
+import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.statusbar.notification.stack.BUCKET_HEADS_UP
import com.android.systemui.statusbar.policy.HeadsUpManager
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
+import java.util.function.Consumer
import javax.inject.Inject
/**
@@ -85,6 +86,7 @@ class HeadsUpCoordinator @Inject constructor(
pipeline.addOnBeforeFinalizeFilterListener(::onBeforeFinalizeFilter)
pipeline.addPromoter(mNotifPromoter)
pipeline.addNotificationLifetimeExtender(mLifetimeExtender)
+ mRemoteInputManager.addActionPressListener(mActionPressListener)
}
private fun onHeadsUpViewBound(entry: NotificationEntry) {
@@ -277,8 +279,8 @@ class HeadsUpCoordinator @Inject constructor(
.firstOrNull()
?.let { posted ->
posted.entry.takeIf { entry ->
- locationLookupByKey(entry.key) == GroupLocation.Isolated
- && entry.sbn.notification.groupAlertBehavior == GROUP_ALERT_SUMMARY
+ locationLookupByKey(entry.key) == GroupLocation.Isolated &&
+ entry.sbn.notification.groupAlertBehavior == GROUP_ALERT_SUMMARY
}
}
@@ -448,6 +450,14 @@ class HeadsUpCoordinator @Inject constructor(
(entry.sbn.notification.flags and Notification.FLAG_ONLY_ALERT_ONCE) == 0)
}
+ /** When an action is pressed on a notification, end HeadsUp lifetime extension. */
+ private val mActionPressListener = Consumer<NotificationEntry> { entry ->
+ if (mNotifsExtendingLifetime.contains(entry)) {
+ val removeInMillis = mHeadsUpManager.getEarliestRemovalTime(entry.key)
+ mExecutor.executeDelayed({ endNotifLifetimeExtensionIfExtended(entry) }, removeInMillis)
+ }
+ }
+
private val mLifetimeExtender = object : NotifLifetimeExtender {
override fun getName() = TAG
@@ -503,6 +513,7 @@ class HeadsUpCoordinator @Inject constructor(
private val mOnHeadsUpChangedListener = object : OnHeadsUpChangedListener {
override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
if (!isHeadsUp) {
+ mNotifPromoter.invalidateList("headsUpEnded: ${entry.logKey}")
mHeadsUpViewBinder.unbindHeadsUpView(entry)
endNotifLifetimeExtensionIfExtended(entry)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
index 7b5cf8511900..e4e2bc16b77e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinator.java
@@ -41,14 +41,11 @@ import javax.inject.Inject;
@CoordinatorScope
public class HideNotifsForOtherUsersCoordinator implements Coordinator {
private final NotificationLockscreenUserManager mLockscreenUserManager;
- private final SharedCoordinatorLogger mLogger;
@Inject
public HideNotifsForOtherUsersCoordinator(
- NotificationLockscreenUserManager lockscreenUserManager,
- SharedCoordinatorLogger logger) {
+ NotificationLockscreenUserManager lockscreenUserManager) {
mLockscreenUserManager = lockscreenUserManager;
- mLogger = logger;
}
@Override
@@ -70,23 +67,19 @@ public class HideNotifsForOtherUsersCoordinator implements Coordinator {
// changes
@Override
public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) {
- mLogger.logUserOrProfileChanged(
- mLockscreenUserManager.getCurrentUserId(),
- profileIdsToStr(currentProfiles));
- mFilter.invalidateList();
+ StringBuilder sb = new StringBuilder("onCurrentProfilesChanged:");
+ sb.append(" user=").append(mLockscreenUserManager.getCurrentUserId());
+ sb.append(" profiles=");
+ sb.append("{");
+ for (int i = 0; i < currentProfiles.size(); i++) {
+ if (i != 0) {
+ sb.append(",");
+ }
+ sb.append(currentProfiles.keyAt(i));
+ }
+ sb.append("}");
+ mFilter.invalidateList(sb.toString());
}
};
- private String profileIdsToStr(SparseArray<UserInfo> currentProfiles) {
- StringBuilder sb = new StringBuilder();
- sb.append("{");
- for (int i = 0; i < currentProfiles.size(); i++) {
- sb.append(currentProfiles.keyAt(i));
- if (i < currentProfiles.size() - 1) {
- sb.append(",");
- }
- }
- sb.append("}");
- return sb.toString();
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
index ff1c70ca740b..e3d71c8b29d9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.java
@@ -16,15 +16,15 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import com.android.keyguard.KeyguardUpdateMonitor;
+import androidx.annotation.NonNull;
+
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
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;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import javax.inject.Inject;
@@ -36,27 +36,18 @@ import javax.inject.Inject;
@CoordinatorScope
public class KeyguardCoordinator implements Coordinator {
private static final String TAG = "KeyguardCoordinator";
- private final StatusBarStateController mStatusBarStateController;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final HighPriorityProvider mHighPriorityProvider;
- private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
private final KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
- private final SharedCoordinatorLogger mLogger;
+ private final SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
+ private final StatusBarStateController mStatusBarStateController;
@Inject
public KeyguardCoordinator(
- StatusBarStateController statusBarStateController,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
- HighPriorityProvider highPriorityProvider,
- SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider,
- SharedCoordinatorLogger logger) {
- mStatusBarStateController = statusBarStateController;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mHighPriorityProvider = highPriorityProvider;
- mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
+ SectionHeaderVisibilityProvider sectionHeaderVisibilityProvider,
+ StatusBarStateController statusBarStateController) {
mKeyguardNotificationVisibilityProvider = keyguardNotificationVisibilityProvider;
- mLogger = logger;
+ mSectionHeaderVisibilityProvider = sectionHeaderVisibilityProvider;
+ mStatusBarStateController = statusBarStateController;
}
@Override
@@ -72,7 +63,7 @@ public class KeyguardCoordinator implements Coordinator {
private final NotifFilter mNotifFilter = new NotifFilter(TAG) {
@Override
- public boolean shouldFilterOut(NotificationEntry entry, long now) {
+ public boolean shouldFilterOut(@NonNull NotificationEntry entry, long now) {
return mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry);
}
};
@@ -84,9 +75,8 @@ public class KeyguardCoordinator implements Coordinator {
}
private void invalidateListFromFilter(String reason) {
- mLogger.logKeyguardCoordinatorInvalidated(reason);
updateSectionHeadersVisibility();
- mNotifFilter.invalidateList();
+ mNotifFilter.invalidateList(reason);
}
private void updateSectionHeadersVisibility() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
index 359e2028d9f3..891e25ef6c25 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/NotifCoordinators.kt
@@ -72,9 +72,7 @@ class NotifCoordinatorsImpl @Inject constructor(
// pipeline, such as this DataStoreCoordinator which cannot be removed, as it's a critical
// glue between the pipeline and parts of SystemUI which depend on pipeline output via the
// NotifLiveDataStore.
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- mCoordinators.add(dataStoreCoordinator)
- }
+ mCoordinators.add(dataStoreCoordinator)
// Attach normal coordinators.
mCoordinators.add(hideLocallyDismissedNotifsCoordinator)
@@ -97,18 +95,14 @@ class NotifCoordinatorsImpl @Inject constructor(
if (notifPipelineFlags.isSmartspaceDedupingEnabled()) {
mCoordinators.add(smartspaceDedupingCoordinator)
}
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- mCoordinators.add(headsUpCoordinator)
- mCoordinators.add(gutsCoordinator)
- mCoordinators.add(preparationCoordinator)
- mCoordinators.add(remoteInputCoordinator)
- }
+ mCoordinators.add(headsUpCoordinator)
+ mCoordinators.add(gutsCoordinator)
+ mCoordinators.add(preparationCoordinator)
+ mCoordinators.add(remoteInputCoordinator)
// Manually add Ordered Sections
// HeadsUp > FGS > People > Alerting > Silent > Minimized > Unknown/Default
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- mOrderedSections.add(headsUpCoordinator.sectioner) // HeadsUp
- }
+ mOrderedSections.add(headsUpCoordinator.sectioner)
mOrderedSections.add(appOpsCoordinator.sectioner) // ForegroundService
mOrderedSections.add(conversationCoordinator.sectioner) // People
mOrderedSections.add(rankingCoordinator.alertingSectioner) // Alerting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
index 210fe8fd0421..ef1e57b4cd3b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinator.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import static com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
import static java.util.Objects.requireNonNull;
@@ -147,7 +148,8 @@ public class PreparationCoordinator implements Coordinator {
@Override
public void attach(NotifPipeline pipeline) {
mNotifErrorManager.addInflationErrorListener(mInflationErrorListener);
- mAdjustmentProvider.addDirtyListener(mNotifInflatingFilter::invalidateList);
+ mAdjustmentProvider.addDirtyListener(
+ () -> mNotifInflatingFilter.invalidateList("adjustmentProviderChanged"));
pipeline.addCollectionListener(mNotifCollectionListener);
// Inflate after grouping/sorting since that affects what views to inflate.
@@ -245,12 +247,13 @@ public class PreparationCoordinator implements Coordinator {
} catch (RemoteException ex) {
// System server is dead, nothing to do about that
}
- mNotifInflationErrorFilter.invalidateList();
+ mNotifInflationErrorFilter.invalidateList("onNotifInflationError for " + logKey(entry));
}
@Override
public void onNotifInflationErrorCleared(NotificationEntry entry) {
- mNotifInflationErrorFilter.invalidateList();
+ mNotifInflationErrorFilter.invalidateList(
+ "onNotifInflationErrorCleared for " + logKey(entry));
}
};
@@ -360,22 +363,25 @@ public class PreparationCoordinator implements Coordinator {
}
private void abortInflation(NotificationEntry entry, String reason) {
- mLogger.logInflationAborted(entry.getKey(), reason);
- mNotifInflater.abortInflation(entry);
- mInflatingNotifs.remove(entry);
+ final boolean taskAborted = mNotifInflater.abortInflation(entry);
+ final boolean wasInflating = mInflatingNotifs.remove(entry);
+ if (taskAborted || wasInflating) {
+ mLogger.logInflationAborted(entry, reason);
+ }
}
private void onInflationFinished(NotificationEntry entry, NotifViewController controller) {
- mLogger.logNotifInflated(entry.getKey());
+ mLogger.logNotifInflated(entry);
mInflatingNotifs.remove(entry);
mViewBarn.registerViewForEntry(entry, controller);
mInflationStates.put(entry, STATE_INFLATED);
mBindEventManager.notifyViewBound(entry);
- mNotifInflatingFilter.invalidateList();
+ mNotifInflatingFilter.invalidateList("onInflationFinished for " + logKey(entry));
}
private void freeNotifViews(NotificationEntry entry) {
mViewBarn.removeViewForEntry(entry);
+ mNotifInflater.releaseViews(entry);
// TODO: clear the entry's row here, or even better, stop setting the row on the entry!
mInflationStates.put(entry, STATE_UNINFLATED);
}
@@ -397,20 +403,20 @@ public class PreparationCoordinator implements Coordinator {
return false;
}
if (isBeyondGroupInitializationWindow(group, now)) {
- mLogger.logGroupInflationTookTooLong(group.getKey());
+ mLogger.logGroupInflationTookTooLong(group);
return false;
}
if (mInflatingNotifs.contains(group.getSummary())) {
- mLogger.logDelayingGroupRelease(group.getKey(), group.getSummary().getKey());
+ mLogger.logDelayingGroupRelease(group, group.getSummary());
return true;
}
for (NotificationEntry child : group.getChildren()) {
if (mInflatingNotifs.contains(child) && !child.wasAttachedInPreviousPass()) {
- mLogger.logDelayingGroupRelease(group.getKey(), child.getKey());
+ mLogger.logDelayingGroupRelease(group, child);
return true;
}
}
- mLogger.logDoneWaitingForGroupInflation(group.getKey());
+ mLogger.logDoneWaitingForGroupInflation(group);
return false;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
index f8352500923e..30f13152126c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorLogger.kt
@@ -19,48 +19,51 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class PreparationCoordinatorLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logNotifInflated(key: String) {
+ fun logNotifInflated(entry: NotificationEntry) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = key
+ str1 = entry.logKey
}, {
"NOTIF INFLATED $str1"
})
}
- fun logInflationAborted(key: String, reason: String) {
+ fun logInflationAborted(entry: NotificationEntry, reason: String) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"NOTIF INFLATION ABORTED $str1 reason=$str2"
})
}
- fun logDoneWaitingForGroupInflation(groupKey: String) {
+ fun logDoneWaitingForGroupInflation(group: GroupEntry) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = groupKey
+ str1 = group.logKey
}, {
"Finished inflating all members of group $str1, releasing group"
})
}
- fun logGroupInflationTookTooLong(groupKey: String) {
+ fun logGroupInflationTookTooLong(group: GroupEntry) {
buffer.log(TAG, LogLevel.WARNING, {
- str1 = groupKey
+ str1 = group.logKey
}, {
"Group inflation took too long for $str1, releasing children early"
})
}
- fun logDelayingGroupRelease(groupKey: String, childKey: String) {
+ fun logDelayingGroupRelease(group: GroupEntry, child: NotificationEntry) {
buffer.log(TAG, LogLevel.DEBUG, {
- str1 = groupKey
- str2 = childKey
+ str1 = group.logKey
+ str2 = child.logKey
}, {
"Delaying release of group $str1 because child $str2 is still inflating"
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
index 57fd1975e13a..67a8a63ad7da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinator.java
@@ -20,7 +20,6 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -28,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.coordinator.dagger
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
import com.android.systemui.statusbar.notification.dagger.AlertingHeader;
@@ -51,7 +51,7 @@ public class RankingCoordinator implements Coordinator {
public static final boolean SHOW_ALL_SECTIONS = false;
private final StatusBarStateController mStatusBarStateController;
private final HighPriorityProvider mHighPriorityProvider;
- private final SectionClassifier mSectionClassifier;
+ private final SectionStyleProvider mSectionStyleProvider;
private final NodeController mSilentNodeController;
private final SectionHeaderController mSilentHeaderController;
private final NodeController mAlertingHeaderController;
@@ -62,13 +62,13 @@ public class RankingCoordinator implements Coordinator {
public RankingCoordinator(
StatusBarStateController statusBarStateController,
HighPriorityProvider highPriorityProvider,
- SectionClassifier sectionClassifier,
+ SectionStyleProvider sectionStyleProvider,
@AlertingHeader NodeController alertingHeaderController,
@SilentHeader SectionHeaderController silentHeaderController,
@SilentHeader NodeController silentNodeController) {
mStatusBarStateController = statusBarStateController;
mHighPriorityProvider = highPriorityProvider;
- mSectionClassifier = sectionClassifier;
+ mSectionStyleProvider = sectionStyleProvider;
mAlertingHeaderController = alertingHeaderController;
mSilentNodeController = silentNodeController;
mSilentHeaderController = silentHeaderController;
@@ -77,7 +77,7 @@ public class RankingCoordinator implements Coordinator {
@Override
public void attach(NotifPipeline pipeline) {
mStatusBarStateController.addCallback(mStatusBarStateCallback);
- mSectionClassifier.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
+ mSectionStyleProvider.setMinimizedSections(Collections.singleton(mMinimizedNotifSectioner));
pipeline.addPreGroupFilter(mSuspendedFilter);
pipeline.addPreGroupFilter(mDndVisualEffectsFilter);
@@ -199,7 +199,7 @@ public class RankingCoordinator implements Coordinator {
new StatusBarStateController.StateListener() {
@Override
public void onDozingChanged(boolean isDozing) {
- mDndVisualEffectsFilter.invalidateList();
+ mDndVisualEffectsFilter.invalidateList("onDozingChanged to " + isDozing);
}
};
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
index 4e9d3ac07a96..1494574b26f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinator.kt
@@ -19,11 +19,11 @@ package com.android.systemui.statusbar.notification.collection.coordinator
import android.content.Context
import com.android.systemui.R
import com.android.systemui.statusbar.notification.AssistantFeedbackController
-import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.ListEntry
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
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import javax.inject.Inject
@@ -35,7 +35,7 @@ import javax.inject.Inject
class RowAppearanceCoordinator @Inject internal constructor(
context: Context,
private var mAssistantFeedbackController: AssistantFeedbackController,
- private var mSectionClassifier: SectionClassifier
+ private var mSectionStyleProvider: SectionStyleProvider
) : Coordinator {
private var entryToExpand: NotificationEntry? = null
@@ -55,7 +55,7 @@ class RowAppearanceCoordinator @Inject internal constructor(
private fun onBeforeRenderList(list: List<ListEntry>) {
entryToExpand = list.firstOrNull()?.representativeEntry?.takeIf { entry ->
- !mSectionClassifier.isMinimizedSection(entry.section!!)
+ !mSectionStyleProvider.isMinimizedSection(entry.section!!)
}
}
@@ -68,4 +68,4 @@ class RowAppearanceCoordinator @Inject internal constructor(
// Show the "alerted" bell icon
controller.setLastAudiblyAlertedMs(entry.lastAudiblyAlertedMs)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
index 3f8a39f62dfb..aeeeb4fb054d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinator.kt
@@ -64,7 +64,7 @@ private class SensitiveContentCoordinatorImpl @Inject constructor(
pipeline.addPreRenderInvalidator(this)
}
- override fun onDynamicPrivacyChanged(): Unit = invalidateList()
+ override fun onDynamicPrivacyChanged(): Unit = invalidateList("onDynamicPrivacyChanged")
override fun onBeforeRenderList(entries: List<ListEntry>) {
if (keyguardStateController.isKeyguardGoingAway() ||
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt
deleted file mode 100644
index 25bc641f0a84..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SharedCoordinatorLogger.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator
-
-import com.android.systemui.log.LogBuffer
-import com.android.systemui.log.LogLevel
-import com.android.systemui.log.dagger.NotificationLog
-import javax.inject.Inject
-
-/**
- * Shared logging class for coordinators that don't log enough to merit their own logger.
- */
-class SharedCoordinatorLogger @Inject constructor(
- @NotificationLog private val buffer: LogBuffer
-) {
- fun logUserOrProfileChanged(userId: Int, profiles: String) {
- buffer.log("NotCurrentUserFilter", LogLevel.INFO, {
- int1 = userId
- str1 = profiles
- }, {
- "Current user or profiles changed. Current user is $int1; profiles are $str1"
- })
- }
-
- fun logKeyguardCoordinatorInvalidated(reason: String) {
- buffer.log("KeyguardCoordinator", LogLevel.DEBUG, {
- str1 = reason
- }, {
- "KeyguardCoordinator invalidated: $str1"
- })
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
index 48f00ac4bf55..9d0f974ea68f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinator.kt
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
+import com.android.systemui.statusbar.notification.logKey
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.time.SystemClock
import java.util.concurrent.TimeUnit.SECONDS
@@ -65,13 +66,6 @@ class SmartspaceDedupingCoordinator @Inject constructor(
statusBarStateController.addCallback(statusBarStateListener)
smartspaceController.addListener(this::onNewSmartspaceTargets)
- if (!pipeline.isNewPipelineEnabled) {
- // TODO (b/173126564): Remove this once the old pipeline is no longer necessary
- notificationLockscreenUserManager.addKeyguardNotificationSuppressor { entry ->
- isDupedWithSmartspaceContent(entry)
- }
- }
-
recordStatusBarState(statusBarStateController.state)
}
@@ -137,7 +131,7 @@ class SmartspaceDedupingCoordinator @Inject constructor(
}
if (changed) {
- filter.invalidateList()
+ filter.invalidateList("onNewSmartspaceTargets")
notificationEntryManager.updateNotifications("Smartspace targets changed")
}
@@ -174,7 +168,7 @@ class SmartspaceDedupingCoordinator @Inject constructor(
target.cancelTimeoutRunnable = executor.executeDelayed({
target.cancelTimeoutRunnable = null
target.shouldFilter = true
- filter.invalidateList()
+ filter.invalidateList("updateAlertException: ${entry.logKey}")
notificationEntryManager.updateNotifications("deduping timeout expired")
}, alertExceptionExpires - now)
}
@@ -191,7 +185,7 @@ class SmartspaceDedupingCoordinator @Inject constructor(
isOnLockscreen = newState == StatusBarState.KEYGUARD
if (isOnLockscreen != wasOnLockscreen) {
- filter.invalidateList()
+ filter.invalidateList("recordStatusBarState: " + StatusBarState.toString(newState))
// No need to call notificationEntryManager.updateNotifications; something else already
// does it for us when the keyguard state changes
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
index 31a13e6faa8c..657c394d4e30 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt
@@ -16,15 +16,18 @@
package com.android.systemui.statusbar.notification.collection.coordinator
+import android.util.Log
import com.android.internal.widget.MessagingGroup
import com.android.internal.widget.MessagingMessage
import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
import com.android.systemui.statusbar.NotificationLockscreenUserManager
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope
import com.android.systemui.statusbar.notification.row.NotificationGutsManager
import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.Compile
import javax.inject.Inject
/**
@@ -38,24 +41,50 @@ class ViewConfigCoordinator @Inject internal constructor(
private val mLockscreenUserManager: NotificationLockscreenUserManager,
private val mGutsManager: NotificationGutsManager,
private val mKeyguardUpdateMonitor: KeyguardUpdateMonitor
-) : Coordinator, UserChangedListener, ConfigurationController.ConfigurationListener {
+) : Coordinator, ConfigurationController.ConfigurationListener {
+ private var mIsSwitchingUser = false
private var mReinflateNotificationsOnUserSwitched = false
private var mDispatchUiModeChangeOnUserSwitched = false
private var mPipeline: NotifPipeline? = null
+ private val mKeyguardUpdateCallback = object : KeyguardUpdateMonitorCallback() {
+ override fun onUserSwitching(userId: Int) {
+ log { "ViewConfigCoordinator.onUserSwitching(userId=$userId)" }
+ mIsSwitchingUser = true
+ }
+
+ override fun onUserSwitchComplete(userId: Int) {
+ log { "ViewConfigCoordinator.onUserSwitchComplete(userId=$userId)" }
+ mIsSwitchingUser = false
+ applyChangesOnUserSwitched()
+ }
+ }
+
+ private val mUserChangedListener = object : UserChangedListener {
+ override fun onUserChanged(userId: Int) {
+ log { "ViewConfigCoordinator.onUserChanged(userId=$userId)" }
+ applyChangesOnUserSwitched()
+ }
+ }
+
override fun attach(pipeline: NotifPipeline) {
mPipeline = pipeline
- if (pipeline.isNewPipelineEnabled) {
- mLockscreenUserManager.addUserChangedListener(this)
- mConfigurationController.addCallback(this)
- }
+ mLockscreenUserManager.addUserChangedListener(mUserChangedListener)
+ mConfigurationController.addCallback(this)
+ mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateCallback)
}
override fun onDensityOrFontScaleChanged() {
+ log {
+ val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
+ "ViewConfigCoordinator.onDensityOrFontScaleChanged()" +
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ }
MessagingMessage.dropCache()
MessagingGroup.dropCache()
- if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+ if (!mIsSwitchingUser) {
updateNotificationsOnDensityOrFontScaleChanged()
} else {
mReinflateNotificationsOnUserSwitched = true
@@ -63,7 +92,13 @@ class ViewConfigCoordinator @Inject internal constructor(
}
override fun onUiModeChanged() {
- if (!mKeyguardUpdateMonitor.isSwitchingUser) {
+ log {
+ val keyguardIsSwitchingUser = mKeyguardUpdateMonitor.isSwitchingUser
+ "ViewConfigCoordinator.onUiModeChanged()" +
+ " isSwitchingUser=$mIsSwitchingUser" +
+ " keyguardUpdateMonitor.isSwitchingUser=$keyguardIsSwitchingUser"
+ }
+ if (!mIsSwitchingUser) {
updateNotificationsOnUiModeChanged()
} else {
mDispatchUiModeChangeOnUserSwitched = true
@@ -74,7 +109,7 @@ class ViewConfigCoordinator @Inject internal constructor(
onDensityOrFontScaleChanged()
}
- override fun onUserChanged(userId: Int) {
+ private fun applyChangesOnUserSwitched() {
if (mReinflateNotificationsOnUserSwitched) {
updateNotificationsOnDensityOrFontScaleChanged()
mReinflateNotificationsOnUserSwitched = false
@@ -86,9 +121,9 @@ class ViewConfigCoordinator @Inject internal constructor(
}
private fun updateNotificationsOnUiModeChanged() {
+ log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" }
mPipeline?.allNotifs?.forEach { entry ->
- val row = entry.row
- row?.onUiModeChanged()
+ entry.row?.onUiModeChanged()
}
}
@@ -101,4 +136,13 @@ class ViewConfigCoordinator @Inject internal constructor(
}
}
}
-} \ No newline at end of file
+
+ private inline fun log(message: () -> String) {
+ if (DEBUG) Log.d(TAG, message())
+ }
+
+ companion object {
+ private const val TAG = "ViewConfigCoordinator"
+ private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index d7bd95c0949e..d3bc257d8a54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -16,8 +16,9 @@
package com.android.systemui.statusbar.notification.collection.coordinator;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
+
+import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
@@ -27,14 +28,15 @@ import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotifPanelEvents;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.phone.NotifPanelEvents;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.Compile;
import com.android.systemui.util.concurrency.DelayableExecutor;
import java.io.PrintWriter;
@@ -54,6 +56,8 @@ import javax.inject.Inject;
@SysUISingleton
public class VisualStabilityCoordinator implements Coordinator, Dumpable,
NotifPanelEvents.Listener {
+ public static final String TAG = "VisualStability";
+ public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
private final DelayableExecutor mDelayableExecutor;
private final HeadsUpManager mHeadsUpManager;
private final NotifPanelEvents mNotifPanelEvents;
@@ -61,7 +65,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
private final VisualStabilityProvider mVisualStabilityProvider;
private final WakefulnessLifecycle mWakefulnessLifecycle;
- private boolean mScreenOn;
+ private boolean mSleepy = true;
+ private boolean mFullyDozed;
private boolean mPanelExpanded;
private boolean mPulsing;
private boolean mNotifPanelCollapsing;
@@ -104,8 +109,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
@Override
public void attach(NotifPipeline pipeline) {
mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
- mScreenOn = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_AWAKE
- || mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_WAKING;
+ mSleepy = mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP;
+ mFullyDozed = mStatusBarStateController.getDozeAmount() == 1f;
mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
mPulsing = mStatusBarStateController.isPulsing();
@@ -113,6 +118,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
pipeline.setVisualStabilityManager(mNotifStabilityManager);
}
+
// TODO(b/203826051): Ensure stability manager can allow reordering off-screen
// HUNs to the top of the shade
private final NotifStabilityManager mNotifStabilityManager =
@@ -174,14 +180,28 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
}
};
- private void updateAllowedStates() {
+ private void updateAllowedStates(String field, boolean value) {
+ boolean wasPipelineRunAllowed = mPipelineRunAllowed;
+ boolean wasReorderingAllowed = mReorderingAllowed;
mPipelineRunAllowed = !isPanelCollapsingOrLaunchingActivity();
mReorderingAllowed = isReorderingAllowed();
- if ((mPipelineRunAllowed && mIsSuppressingPipelineRun)
- || (mReorderingAllowed && (mIsSuppressingGroupChange
- || isSuppressingSectionChange()
- || mIsSuppressingEntryReorder))) {
- mNotifStabilityManager.invalidateList();
+ if (DEBUG && (wasPipelineRunAllowed != mPipelineRunAllowed
+ || wasReorderingAllowed != mReorderingAllowed)) {
+ Log.d(TAG, "Stability allowances changed:"
+ + " pipelineRunAllowed " + wasPipelineRunAllowed + "->" + mPipelineRunAllowed
+ + " reorderingAllowed " + wasReorderingAllowed + "->" + mReorderingAllowed
+ + " when setting " + field + "=" + value);
+ }
+ if (mPipelineRunAllowed && mIsSuppressingPipelineRun) {
+ mNotifStabilityManager.invalidateList("pipeline run suppression ended");
+ } else if (mReorderingAllowed && (mIsSuppressingGroupChange
+ || isSuppressingSectionChange()
+ || mIsSuppressingEntryReorder)) {
+ String reason = "reorder suppression ended for"
+ + " group=" + mIsSuppressingGroupChange
+ + " section=" + isSuppressingSectionChange()
+ + " sort=" + mIsSuppressingEntryReorder;
+ mNotifStabilityManager.invalidateList(reason);
}
mVisualStabilityProvider.setReorderingAllowed(mReorderingAllowed);
}
@@ -195,7 +215,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
}
private boolean isReorderingAllowed() {
- return (!mScreenOn || !mPanelExpanded) && !mPulsing;
+ return ((mFullyDozed && mSleepy) || !mPanelExpanded) && !mPulsing;
}
/**
@@ -226,7 +246,7 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
now + ALLOW_SECTION_CHANGE_TIMEOUT));
if (!wasSectionChangeAllowed) {
- mNotifStabilityManager.invalidateList();
+ mNotifStabilityManager.invalidateList("temporarilyAllowSectionChanges");
}
}
@@ -235,27 +255,37 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
@Override
public void onPulsingChanged(boolean pulsing) {
mPulsing = pulsing;
- updateAllowedStates();
+ updateAllowedStates("pulsing", pulsing);
}
@Override
public void onExpandedChanged(boolean expanded) {
mPanelExpanded = expanded;
- updateAllowedStates();
+ updateAllowedStates("panelExpanded", expanded);
+ }
+
+ @Override
+ public void onDozeAmountChanged(float linear, float eased) {
+ final boolean fullyDozed = linear == 1f;
+ mFullyDozed = fullyDozed;
+ updateAllowedStates("fullyDozed", fullyDozed);
}
};
final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedGoingToSleep() {
- mScreenOn = false;
- updateAllowedStates();
+ // NOTE: this method is called much earlier than what we consider "finished" going to
+ // sleep (the animation isn't done), so we also need to check the doze amount is not 1
+ // and use the combo to determine that the locked shade is not visible.
+ mSleepy = true;
+ updateAllowedStates("sleepy", true);
}
@Override
public void onStartedWakingUp() {
- mScreenOn = true;
- updateAllowedStates();
+ mSleepy = false;
+ updateAllowedStates("sleepy", false);
}
};
@@ -265,7 +295,8 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
pw.println(" notifPanelCollapsing: " + mNotifPanelCollapsing);
pw.println(" launchingNotifActivity: " + mNotifPanelLaunchingActivity);
pw.println("reorderingAllowed: " + mReorderingAllowed);
- pw.println(" screenOn: " + mScreenOn);
+ pw.println(" sleepy: " + mSleepy);
+ pw.println(" fullyDozed: " + mFullyDozed);
pw.println(" panelExpanded: " + mPanelExpanded);
pw.println(" pulsing: " + mPulsing);
pw.println("isSuppressingPipelineRun: " + mIsSuppressingPipelineRun);
@@ -285,12 +316,12 @@ public class VisualStabilityCoordinator implements Coordinator, Dumpable,
@Override
public void onPanelCollapsingChanged(boolean isCollapsing) {
mNotifPanelCollapsing = isCollapsing;
- updateAllowedStates();
+ updateAllowedStates("notifPanelCollapsing", isCollapsing);
}
@Override
public void onLaunchingActivityChanged(boolean isLaunchingActivity) {
mNotifPanelLaunchingActivity = isLaunchingActivity;
- updateAllowedStates();
+ updateAllowedStates("notifPanelLaunchingActivity", isLaunchingActivity);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
index d98e7f76a11b..08e21e8f668e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifInflater.kt
@@ -42,9 +42,14 @@ interface NotifInflater {
/**
* Request to stop the inflation of an entry. For example, called when a notification is
- * removed and no longer needs to be inflated.
+ * removed and no longer needs to be inflated. Returns whether anything may have been aborted.
*/
- fun abortInflation(entry: NotificationEntry)
+ fun abortInflation(entry: NotificationEntry): Boolean
+
+ /**
+ * Called to let the system remove the content views from the notification row.
+ */
+ fun releaseViews(entry: NotificationEntry)
/**
* Callback once all the views are inflated and bound for a given NotificationEntry.
@@ -57,4 +62,4 @@ interface NotifInflater {
* A class holding parameters used when inflating the notification row
*/
class Params(val isLowPriority: Boolean, val reason: String)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
index 3475fcf7c12f..ee0b00807e27 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustment.kt
@@ -31,6 +31,7 @@ class NotifUiAdjustment internal constructor(
val smartActions: List<Notification.Action>,
val smartReplies: List<CharSequence>,
val isConversation: Boolean,
+ val isSnoozeEnabled: Boolean,
val isMinimized: Boolean,
val needsRedaction: Boolean,
) {
@@ -42,6 +43,7 @@ class NotifUiAdjustment internal constructor(
): Boolean = when {
oldAdjustment === newAdjustment -> false
oldAdjustment.isConversation != newAdjustment.isConversation -> true
+ oldAdjustment.isSnoozeEnabled != newAdjustment.isSnoozeEnabled -> true
oldAdjustment.isMinimized != newAdjustment.isMinimized -> true
oldAdjustment.needsRedaction != newAdjustment.needsRedaction -> true
areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions) -> true
@@ -57,9 +59,9 @@ class NotifUiAdjustment internal constructor(
first.size != second.size -> true
else -> first.asSequence().zip(second.asSequence()).any {
(!TextUtils.equals(it.first.title, it.second.title)) ||
- (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
- (it.first.actionIntent != it.second.actionIntent) ||
- (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
+ (areDifferent(it.first.getIcon(), it.second.getIcon())) ||
+ (it.first.actionIntent != it.second.actionIntent) ||
+ (areDifferent(it.first.remoteInputs, it.second.remoteInputs))
}
}
@@ -78,7 +80,7 @@ class NotifUiAdjustment internal constructor(
first.size != second.size -> true
else -> first.asSequence().zip(second.asSequence()).any {
(!TextUtils.equals(it.first.label, it.second.label)) ||
- (areDifferent(it.first.choices, it.second.choices))
+ (areDifferent(it.first.choices, it.second.choices))
}
}
@@ -94,4 +96,4 @@ class NotifUiAdjustment internal constructor(
}
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
index f7b6376c717c..745d6fe1d624 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProvider.kt
@@ -16,12 +16,18 @@
package com.android.systemui.statusbar.notification.collection.inflation
+import android.database.ContentObserver
+import android.os.Handler
+import android.os.UserHandle
+import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.util.ListenerSet
+import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
/**
@@ -30,14 +36,23 @@ import javax.inject.Inject
*/
@SysUISingleton
class NotifUiAdjustmentProvider @Inject constructor(
+ @Main private val handler: Handler,
+ private val secureSettings: SecureSettings,
private val lockscreenUserManager: NotificationLockscreenUserManager,
- private val sectionClassifier: SectionClassifier,
+ private val sectionStyleProvider: SectionStyleProvider
) {
private val dirtyListeners = ListenerSet<Runnable>()
+ private var isSnoozeEnabled = false
fun addDirtyListener(listener: Runnable) {
if (dirtyListeners.isEmpty()) {
lockscreenUserManager.addNotificationStateChangedListener(notifStateChangedListener)
+ updateSnoozeEnabled()
+ secureSettings.registerContentObserverForUser(
+ SHOW_NOTIFICATION_SNOOZE,
+ settingsObserver,
+ UserHandle.USER_ALL
+ )
}
dirtyListeners.addIfAbsent(listener)
}
@@ -46,6 +61,7 @@ class NotifUiAdjustmentProvider @Inject constructor(
dirtyListeners.remove(listener)
if (dirtyListeners.isEmpty()) {
lockscreenUserManager.removeNotificationStateChangedListener(notifStateChangedListener)
+ secureSettings.unregisterContentObserver(settingsObserver)
}
}
@@ -54,10 +70,21 @@ class NotifUiAdjustmentProvider @Inject constructor(
dirtyListeners.forEach(Runnable::run)
}
+ private val settingsObserver = object : ContentObserver(handler) {
+ override fun onChange(selfChange: Boolean) {
+ updateSnoozeEnabled()
+ dirtyListeners.forEach(Runnable::run)
+ }
+ }
+
+ private fun updateSnoozeEnabled() {
+ isSnoozeEnabled = secureSettings.getInt(SHOW_NOTIFICATION_SNOOZE, 0) == 1
+ }
+
private fun isEntryMinimized(entry: NotificationEntry): Boolean {
val section = entry.section ?: error("Entry must have a section to determine if minimized")
val parent = entry.parent ?: error("Entry must have a parent to determine if minimized")
- val isMinimizedSection = sectionClassifier.isMinimizedSection(section)
+ val isMinimizedSection = sectionStyleProvider.isMinimizedSection(section)
val isTopLevelEntry = parent == GroupEntry.ROOT_ENTRY
val isGroupSummary = parent.summary == entry
return isMinimizedSection && (isTopLevelEntry || isGroupSummary)
@@ -73,7 +100,8 @@ class NotifUiAdjustmentProvider @Inject constructor(
smartActions = entry.ranking.smartActions,
smartReplies = entry.ranking.smartReplies,
isConversation = entry.ranking.isConversation,
+ isSnoozeEnabled = isSnoozeEnabled,
isMinimized = isEntryMinimized(entry),
needsRedaction = lockscreenUserManager.needsRedaction(entry),
)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
index 3a4701c9ac76..46b467e8962d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinder.java
@@ -50,4 +50,9 @@ public interface NotificationRowBinder {
NotificationUiAdjustment oldAdjustment,
NotificationUiAdjustment newAdjustment,
NotificationRowContentBinder.InflationCallback callback);
+
+ /**
+ * Called when a notification is no longer likely to be displayed and can have its views freed.
+ */
+ void releaseViews(NotificationEntry entry);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index b84a797bbb6f..528f7203347f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.collection.inflation;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
import static java.util.Objects.requireNonNull;
@@ -161,6 +163,18 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
}
}
+ @Override
+ public void releaseViews(NotificationEntry entry) {
+ if (!entry.rowExists()) {
+ return;
+ }
+ final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_CONTRACTED);
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_EXPANDED);
+ params.markContentViewsFreeable(FLAG_CONTENT_VIEW_PUBLIC);
+ mRowContentBindStage.requestRebind(entry, null);
+ }
+
/**
* Bind row to various controllers and managers. This is only called when the row is first
* created.
@@ -249,6 +263,8 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
}
RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+ params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
+ params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
params.setUseLowPriority(isLowPriority);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
index 7dd3672a6e30..a7719d3d82a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/OnUserInteractionCallbackImpl.java
@@ -23,6 +23,7 @@ import android.service.notification.NotificationStats;
import androidx.annotation.NonNull;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
@@ -33,10 +34,13 @@ import com.android.systemui.statusbar.notification.collection.render.Notificatio
import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import javax.inject.Inject;
+
/**
* Callback for when a user interacts with a {@see ExpandableNotificationRow}. Sends relevant
* information about the interaction to the notification pipeline.
*/
+@SysUISingleton
public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback {
private final NotificationVisibilityProvider mVisibilityProvider;
private final NotifCollection mNotifCollection;
@@ -44,6 +48,7 @@ public class OnUserInteractionCallbackImpl implements OnUserInteractionCallback
private final StatusBarStateController mStatusBarStateController;
private final VisualStabilityCoordinator mVisualStabilityCoordinator;
+ @Inject
public OnUserInteractionCallbackImpl(
NotificationVisibilityProvider visibilityProvider,
NotifCollection notifCollection,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
index 70b11569f50b..24ef5808b2e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/init/NotifPipelineInitializer.java
@@ -22,7 +22,6 @@ import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -53,7 +52,6 @@ public class NotifPipelineInitializer implements Dumpable {
private final NotifInflaterImpl mNotifInflater;
private final DumpManager mDumpManager;
private final ShadeViewManagerFactory mShadeViewManagerFactory;
- private final NotifPipelineFlags mNotifPipelineFlags;
@Inject
@@ -66,8 +64,7 @@ public class NotifPipelineInitializer implements Dumpable {
NotifCoordinators notifCoordinators,
NotifInflaterImpl notifInflater,
DumpManager dumpManager,
- ShadeViewManagerFactory shadeViewManagerFactory,
- NotifPipelineFlags notifPipelineFlags
+ ShadeViewManagerFactory shadeViewManagerFactory
) {
mPipelineWrapper = pipelineWrapper;
mGroupCoalescer = groupCoalescer;
@@ -78,7 +75,6 @@ public class NotifPipelineInitializer implements Dumpable {
mDumpManager = dumpManager;
mNotifInflater = notifInflater;
mShadeViewManagerFactory = shadeViewManagerFactory;
- mNotifPipelineFlags = notifPipelineFlags;
}
/** Hooks the new pipeline up to NotificationManager */
@@ -91,26 +87,22 @@ public class NotifPipelineInitializer implements Dumpable {
mDumpManager.registerDumpable("NotifPipeline", this);
// Setup inflation
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotifInflater.setRowBinder(rowBinder);
- }
+ mNotifInflater.setRowBinder(rowBinder);
// Wire up coordinators
mNotifPluggableCoordinators.attach(mPipelineWrapper);
// Wire up pipeline
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mShadeViewManagerFactory
- .create(listContainer, stackController)
- .attach(mRenderStageManager);
- }
+ mShadeViewManagerFactory
+ .create(listContainer, stackController)
+ .attach(mRenderStageManager);
mRenderStageManager.attach(mListBuilder);
mListBuilder.attach(mNotifCollection);
mNotifCollection.attach(mGroupCoalescer);
mGroupCoalescer.attach(notificationService);
Log.d(TAG, "Notif pipeline initialized."
- + " rendering=" + mNotifPipelineFlags.isNewPipelineEnabled());
+ + " rendering=" + true);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
deleted file mode 100644
index bdbb0eb48e8a..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/LegacyNotificationPresenterExtensions.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy;
-
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SPEW;
-
-import android.service.notification.StatusBarNotification;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
-
-import org.jetbrains.annotations.NotNull;
-
-import javax.inject.Inject;
-
-/**
- * This is some logic extracted from the
- * {@link com.android.systemui.statusbar.phone.StatusBarNotificationPresenter}
- * into a class that implements a new-pipeline interface so that the new pipeline can implement it
- * correctly.
- *
- * Specifically, this is the logic which updates notifications when uiMode and screen properties
- * change, and which closes the shade when the last notification disappears.
- */
-public class LegacyNotificationPresenterExtensions implements NotifShadeEventSource {
- private static final String TAG = "LegacyNotifPresenter";
- private final NotificationEntryManager mEntryManager;
- private boolean mEntryListenerAdded;
- private Runnable mShadeEmptiedCallback;
- private Runnable mNotifRemovedByUserCallback;
-
- @Inject
- public LegacyNotificationPresenterExtensions(NotificationEntryManager entryManager) {
- mEntryManager = entryManager;
- }
-
- private void ensureEntryListenerAdded() {
- if (mEntryListenerAdded) return;
- mEntryListenerAdded = true;
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onEntryRemoved(
- @NotNull NotificationEntry entry,
- NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- StatusBarNotification old = entry.getSbn();
- if (SPEW) {
- Log.d(TAG, "removeNotification key=" + entry.getKey()
- + " old=" + old + " reason=" + reason);
- }
-
- if (old != null && !mEntryManager.hasActiveNotifications()) {
- if (mShadeEmptiedCallback != null) mShadeEmptiedCallback.run();
- }
- if (removedByUser) {
- if (mNotifRemovedByUserCallback != null) mNotifRemovedByUserCallback.run();
- }
- }
- });
- }
-
- @Override
- public void setNotifRemovedByUserCallback(@NonNull Runnable callback) {
- if (mNotifRemovedByUserCallback != null) {
- throw new IllegalStateException("mNotifRemovedByUserCallback already set");
- }
- mNotifRemovedByUserCallback = callback;
- ensureEntryListenerAdded();
- }
-
- @Override
- public void setShadeEmptiedCallback(@NonNull Runnable callback) {
- if (mShadeEmptiedCallback != null) {
- throw new IllegalStateException("mShadeEmptiedCallback already set");
- }
- mShadeEmptiedCallback = callback;
- ensureEntryListenerAdded();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
deleted file mode 100644
index 103b14b09e9c..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/legacy/OnUserInteractionCallbackImplLegacy.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.legacy;
-
-import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-
-import android.service.notification.NotificationListenerService;
-import android.service.notification.NotificationStats;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.DismissedByUserStats;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.row.OnUserInteractionCallback;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-
-/**
- * Callback for when a user interacts with a {@see ExpandableNotificationRow}.
- */
-public class OnUserInteractionCallbackImplLegacy implements OnUserInteractionCallback {
- private final NotificationEntryManager mNotificationEntryManager;
- private final NotificationVisibilityProvider mVisibilityProvider;
- private final HeadsUpManager mHeadsUpManager;
- private final StatusBarStateController mStatusBarStateController;
- private final VisualStabilityManager mVisualStabilityManager;
- private final GroupMembershipManager mGroupMembershipManager;
-
- public OnUserInteractionCallbackImplLegacy(
- NotificationEntryManager notificationEntryManager,
- NotificationVisibilityProvider visibilityProvider,
- HeadsUpManager headsUpManager,
- StatusBarStateController statusBarStateController,
- VisualStabilityManager visualStabilityManager,
- GroupMembershipManager groupMembershipManager
- ) {
- mNotificationEntryManager = notificationEntryManager;
- mVisibilityProvider = visibilityProvider;
- mHeadsUpManager = headsUpManager;
- mStatusBarStateController = statusBarStateController;
- mVisualStabilityManager = visualStabilityManager;
- mGroupMembershipManager = groupMembershipManager;
- }
-
- /**
- * Callback triggered when a user:
- * 1. Manually dismisses a notification {@see ExpandableNotificationRow}.
- * 2. Clicks on a notification with flag {@link android.app.Notification#FLAG_AUTO_CANCEL}.
- * {@see StatusBarNotificationActivityStarter}
- *
- * @param groupSummaryToDismiss the group summary that should be dismissed
- * along with this dismissal. If null, does not additionally
- * dismiss any notifications.
- */
- private void onDismiss(
- NotificationEntry entry,
- @NotificationListenerService.NotificationCancelReason int cancellationReason,
- @Nullable NotificationEntry groupSummaryToDismiss
- ) {
- int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
- if (mHeadsUpManager.isAlerting(entry.getKey())) {
- dismissalSurface = NotificationStats.DISMISSAL_PEEK;
- } else if (mStatusBarStateController.isDozing()) {
- dismissalSurface = NotificationStats.DISMISSAL_AOD;
- }
-
- if (groupSummaryToDismiss != null) {
- onDismiss(groupSummaryToDismiss, cancellationReason, null);
- }
-
- mNotificationEntryManager.performRemoveNotification(
- entry.getSbn(),
- new DismissedByUserStats(
- dismissalSurface,
- DISMISS_SENTIMENT_NEUTRAL,
- mVisibilityProvider.obtain(entry, true)),
- cancellationReason
- );
-
- }
-
- @Override
- public void onImportanceChanged(NotificationEntry entry) {
- mVisualStabilityManager.temporarilyAllowReordering();
- }
-
- /**
- * @param entry that is being dismissed
- * @return the group summary to dismiss along with this entry if this is the last entry in
- * the group. Else, returns null.
- */
- @Nullable
- private NotificationEntry getGroupSummaryToDismiss(NotificationEntry entry) {
- if (mGroupMembershipManager.isOnlyChildInGroup(entry)) {
- NotificationEntry groupSummary = mGroupMembershipManager.getLogicalGroupSummary(entry);
- return groupSummary.isDismissable() ? groupSummary : null;
- }
- return null;
- }
-
- @Override
- @NonNull
- public Runnable registerFutureDismissal(@NonNull NotificationEntry entry,
- @CancellationReason int cancellationReason) {
- NotificationEntry groupSummaryToDismiss = getGroupSummaryToDismiss(entry);
- return () -> onDismiss(entry, cancellationReason, groupSummaryToDismiss);
- }
-}
-
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
index 263737e20a13..ea66f3b6dd42 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/NotifSection.kt
@@ -25,12 +25,9 @@ data class NotifSection(
val sectioner: NotifSectioner,
val index: Int
) {
- val label: String
- get() = "Section($index, $bucket, \"${sectioner.name}\")"
-
+ @PriorityBucket
+ val bucket: Int = sectioner.bucket
+ val label: String = "$index:$bucket:${sectioner.name}"
val headerController: NodeController? = sectioner.headerNodeController
-
val comparator: NotifComparator? = sectioner.comparator
-
- @PriorityBucket val bucket: Int = sectioner.bucket
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
index f8bf85f92eae..4c406e3ba0b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/ShadeListBuilderLogger.kt
@@ -21,101 +21,121 @@ import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.WARNING
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.StateName
+import com.android.systemui.statusbar.notification.collection.listbuilder.PipelineState.getStateName
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Invalidator
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.util.Compile
import javax.inject.Inject
class ShadeListBuilderLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
- fun logOnBuildList() {
+ fun logOnBuildList(reason: String?) {
buffer.log(TAG, INFO, {
+ str1 = reason
}, {
- "Request received from NotifCollection"
+ "Request received from NotifCollection for $str1"
})
}
- fun logEndBuildList(buildId: Int, topLevelEntries: Int, numChildren: Int) {
+ fun logEndBuildList(
+ buildId: Int,
+ topLevelEntries: Int,
+ numChildren: Int,
+ enforcedVisualStability: Boolean
+ ) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
int1 = topLevelEntries
int2 = numChildren
+ bool1 = enforcedVisualStability
}, {
- "(Build $long1) Build complete ($int1 top-level entries, $int2 children)"
+ "(Build $long1) Build complete ($int1 top-level entries, $int2 children)" +
+ " enforcedVisualStability=$bool1"
})
}
- fun logPreRenderInvalidated(filterName: String, pipelineState: Int) {
+ private fun logPluggableInvalidated(
+ type: String,
+ pluggable: Pluggable<*>,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) {
buffer.log(TAG, DEBUG, {
- str1 = filterName
+ str1 = type
+ str2 = pluggable.name
int1 = pipelineState
+ str3 = reason
}, {
- """Pre-render Invalidator "$str1" invalidated; pipeline state is $int1"""
+ """Invalidated while ${getStateName(int1)} by $str1 "$str2" because $str3"""
})
}
- fun logPreGroupFilterInvalidated(filterName: String, pipelineState: Int) {
- buffer.log(TAG, DEBUG, {
- str1 = filterName
- int1 = pipelineState
- }, {
- """Pre-group NotifFilter "$str1" invalidated; pipeline state is $int1"""
- })
- }
+ fun logPreRenderInvalidated(
+ invalidator: Invalidator,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("Pre-render Invalidator", invalidator, pipelineState, reason)
- fun logReorderingAllowedInvalidated(name: String, pipelineState: Int) {
- buffer.log(TAG, DEBUG, {
- str1 = name
- int1 = pipelineState
- }, {
- """ReorderingNowAllowed "$str1" invalidated; pipeline state is $int1"""
- })
- }
+ fun logPreGroupFilterInvalidated(
+ filter: NotifFilter,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("Pre-group NotifFilter", filter, pipelineState, reason)
- fun logPromoterInvalidated(name: String, pipelineState: Int) {
- buffer.log(TAG, DEBUG, {
- str1 = name
- int1 = pipelineState
- }, {
- """NotifPromoter "$str1" invalidated; pipeline state is $int1"""
- })
- }
+ fun logReorderingAllowedInvalidated(
+ stabilityManager: NotifStabilityManager,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("ReorderingNowAllowed", stabilityManager, pipelineState, reason)
- fun logNotifSectionInvalidated(name: String, pipelineState: Int) {
- buffer.log(TAG, DEBUG, {
- str1 = name
- int1 = pipelineState
- }, {
- """NotifSection "$str1" invalidated; pipeline state is $int1"""
- })
- }
+ fun logPromoterInvalidated(
+ promoter: NotifPromoter,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("NotifPromoter", promoter, pipelineState, reason)
- fun logNotifComparatorInvalidated(name: String, pipelineState: Int) {
- buffer.log(TAG, DEBUG, {
- str1 = name
- int1 = pipelineState
- }, {
- """NotifComparator "$str1" invalidated; pipeline state is $int1"""
- })
- }
+ fun logNotifSectionInvalidated(
+ sectioner: NotifSectioner,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("NotifSection", sectioner, pipelineState, reason)
- fun logFinalizeFilterInvalidated(name: String, pipelineState: Int) {
- buffer.log(TAG, DEBUG, {
- str1 = name
- int1 = pipelineState
- }, {
- """Finalize NotifFilter "$str1" invalidated; pipeline state is $int1"""
- })
- }
+ fun logNotifComparatorInvalidated(
+ comparator: NotifComparator,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("NotifComparator", comparator, pipelineState, reason)
- fun logDuplicateSummary(buildId: Int, groupKey: String, existingKey: String, newKey: String) {
+ fun logFinalizeFilterInvalidated(
+ filter: NotifFilter,
+ @StateName pipelineState: Int,
+ reason: String?
+ ) = logPluggableInvalidated("Finalize NotifFilter", filter, pipelineState, reason)
+
+ fun logDuplicateSummary(
+ buildId: Int,
+ group: GroupEntry,
+ existingSummary: NotificationEntry,
+ newSummary: NotificationEntry
+ ) {
buffer.log(TAG, WARNING, {
long1 = buildId.toLong()
- str1 = groupKey
- str2 = existingKey
- str3 = newKey
+ str1 = group.logKey
+ str2 = existingSummary.logKey
+ str3 = newSummary.logKey
}, {
"""(Build $long1) Duplicate summary for group "$str1": "$str2" vs. "$str3""""
})
@@ -124,7 +144,7 @@ class ShadeListBuilderLogger @Inject constructor(
fun logDuplicateTopLevelKey(buildId: Int, topLevelKey: String) {
buffer.log(TAG, WARNING, {
long1 = buildId.toLong()
- str1 = topLevelKey
+ str1 = logKey(topLevelKey)
}, {
"(Build $long1) Duplicate top-level key: $str1"
})
@@ -132,15 +152,15 @@ class ShadeListBuilderLogger @Inject constructor(
fun logEntryAttachStateChanged(
buildId: Int,
- key: String,
+ entry: ListEntry,
prevParent: GroupEntry?,
newParent: GroupEntry?
) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = key
- str2 = prevParent?.key
- str3 = newParent?.key
+ str1 = entry.logKey
+ str2 = prevParent?.logKey
+ str3 = newParent?.logKey
}, {
val action = if (str2 == null && str3 != null) {
@@ -160,8 +180,8 @@ class ShadeListBuilderLogger @Inject constructor(
fun logParentChanged(buildId: Int, prevParent: GroupEntry?, newParent: GroupEntry?) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = prevParent?.key
- str2 = newParent?.key
+ str1 = prevParent?.logKey
+ str2 = newParent?.logKey
}, {
if (str1 == null && str2 != null) {
"(Build $long1) Parent is {$str2}"
@@ -180,8 +200,8 @@ class ShadeListBuilderLogger @Inject constructor(
) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = suppressedParent?.key
- str2 = keepingParent?.key
+ str1 = suppressedParent?.logKey
+ str2 = keepingParent?.logKey
}, {
"(Build $long1) Change of parent to '$str1' suppressed; keeping parent '$str2'"
})
@@ -193,7 +213,7 @@ class ShadeListBuilderLogger @Inject constructor(
) {
buffer.log(TAG, INFO, {
long1 = buildId.toLong()
- str1 = keepingParent?.key
+ str1 = keepingParent?.logKey
}, {
"(Build $long1) Group pruning suppressed; keeping parent '$str1'"
})
@@ -273,6 +293,8 @@ class ShadeListBuilderLogger @Inject constructor(
})
}
+ val logRankInFinalList = Compile.IS_DEBUG && notifPipelineFlags.isDevLoggingEnabled()
+
fun logFinalList(entries: List<ListEntry>) {
if (entries.isEmpty()) {
buffer.log(TAG, DEBUG, {}, { "(empty list)" })
@@ -281,26 +303,32 @@ class ShadeListBuilderLogger @Inject constructor(
val entry = entries[i]
buffer.log(TAG, DEBUG, {
int1 = i
- str1 = entry.key
+ str1 = entry.logKey
+ bool1 = logRankInFinalList
+ int2 = entry.representativeEntry!!.ranking.rank
}, {
- "[$int1] $str1"
+ "[$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
})
if (entry is GroupEntry) {
entry.summary?.let {
buffer.log(TAG, DEBUG, {
- str1 = it.key
+ str1 = it.logKey
+ bool1 = logRankInFinalList
+ int2 = it.ranking.rank
}, {
- " [*] $str1 (summary)"
+ " [*] $str1 (summary)".let { if (bool1) "$it rank=$int2" else it }
})
}
for (j in entry.children.indices) {
val child = entry.children[j]
buffer.log(TAG, DEBUG, {
int1 = j
- str1 = child.key
+ str1 = child.logKey
+ bool1 = logRankInFinalList
+ int2 = child.ranking.rank
}, {
- " [$int1] $str1"
+ " [$int1] $str1".let { if (bool1) "$it rank=$int2" else it }
})
}
}
@@ -308,7 +336,7 @@ class ShadeListBuilderLogger @Inject constructor(
}
fun logPipelineRunSuppressed() =
- buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
+ buffer.log(TAG, INFO, {}) { "Suppressing pipeline run during animation." }
}
-private const val TAG = "ShadeListBuilder" \ No newline at end of file
+private const val TAG = "ShadeListBuilder"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
index b981a9621526..966ab4c61b50 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/listbuilder/pluggable/Pluggable.java
@@ -49,10 +49,10 @@ public abstract class Pluggable<This> {
* Call this method when something has caused this pluggable's behavior to change. The pipeline
* will be re-run.
*/
- public final void invalidateList() {
+ public final void invalidateList(@Nullable String reason) {
if (mListener != null) {
Trace.beginSection("Pluggable<" + mName + ">.invalidateList");
- mListener.onPluggableInvalidated((This) this);
+ mListener.onPluggableInvalidated((This) this, reason);
Trace.endSection();
}
}
@@ -74,7 +74,7 @@ public abstract class Pluggable<This> {
* @param <T> The type of pluggable that is being listened to.
*/
public interface PluggableListener<T> {
- /** Called whenever {@link #invalidateList()} is called on this pluggable. */
- void onPluggableInvalidated(T pluggable);
+ /** Called whenever {@link #invalidateList(String)} is called on this pluggable. */
+ void onPluggableInvalidated(T pluggable, @Nullable String reason);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
index 4023474bf6a7..941b2ae4f771 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CollectionReadyForBuildListener.java
@@ -29,5 +29,5 @@ public interface CollectionReadyForBuildListener {
* Called by the NotifCollection to indicate that something in the collection has changed and
* that the list builder should regenerate the list.
*/
- void onBuildList(Collection<NotificationEntry> entries);
+ void onBuildList(Collection<NotificationEntry> entries, String reason);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 7e7936717b84..ebcac6b6afb3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.notifcollection
import android.os.RemoteException
import android.service.notification.NotificationListenerService
import android.service.notification.NotificationListenerService.RankingMap
+import android.service.notification.StatusBarNotification
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.ERROR
@@ -65,9 +66,9 @@ fun cancellationReasonDebugString(@CancellationReason reason: Int) =
class NotifCollectionLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logNotifPosted(key: String) {
+ fun logNotifPosted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"POSTED $str1"
})
@@ -75,49 +76,49 @@ class NotifCollectionLogger @Inject constructor(
fun logNotifGroupPosted(groupKey: String, batchSize: Int) {
buffer.log(TAG, INFO, {
- str1 = groupKey
+ str1 = logKey(groupKey)
int1 = batchSize
}, {
"POSTED GROUP $str1 ($int1 events)"
})
}
- fun logNotifUpdated(key: String) {
+ fun logNotifUpdated(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"UPDATED $str1"
})
}
- fun logNotifRemoved(key: String, @CancellationReason reason: Int) {
+ fun logNotifRemoved(sbn: StatusBarNotification, @CancellationReason reason: Int) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = sbn.logKey
int1 = reason
}, {
"REMOVED $str1 reason=${cancellationReasonDebugString(int1)}"
})
}
- fun logNotifReleased(key: String) {
+ fun logNotifReleased(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"RELEASED $str1"
})
}
- fun logNotifDismissed(key: String) {
+ fun logNotifDismissed(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"DISMISSED $str1"
})
}
- fun logNonExistentNotifDismissed(key: String) {
+ fun logNonExistentNotifDismissed(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"DISMISSED Non Existent $str1"
})
@@ -125,7 +126,7 @@ class NotifCollectionLogger @Inject constructor(
fun logChildDismissed(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = entry.key
+ str1 = entry.logKey
}, {
"CHILD DISMISSED (inferred): $str1"
})
@@ -141,31 +142,31 @@ class NotifCollectionLogger @Inject constructor(
fun logDismissOnAlreadyCanceledEntry(entry: NotificationEntry) {
buffer.log(TAG, DEBUG, {
- str1 = entry.key
+ str1 = entry.logKey
}, {
"Dismiss on $str1, which was already canceled. Trying to remove..."
})
}
- fun logNotifDismissedIntercepted(key: String) {
+ fun logNotifDismissedIntercepted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"DISMISS INTERCEPTED $str1"
})
}
- fun logNotifClearAllDismissalIntercepted(key: String) {
+ fun logNotifClearAllDismissalIntercepted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"CLEAR ALL DISMISSAL INTERCEPTED $str1"
})
}
- fun logNotifInternalUpdate(key: String, name: String, reason: String) {
+ fun logNotifInternalUpdate(entry: NotificationEntry, name: String, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = name
str3 = reason
}, {
@@ -173,9 +174,9 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logNotifInternalUpdateFailed(key: String, name: String, reason: String) {
+ fun logNotifInternalUpdateFailed(sbn: StatusBarNotification, name: String, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = sbn.logKey
str2 = name
str3 = reason
}, {
@@ -183,26 +184,49 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logNoNotificationToRemoveWithKey(key: String, @CancellationReason reason: Int) {
+ fun logNoNotificationToRemoveWithKey(
+ sbn: StatusBarNotification,
+ @CancellationReason reason: Int
+ ) {
buffer.log(TAG, ERROR, {
- str1 = key
+ str1 = sbn.logKey
int1 = reason
}, {
"No notification to remove with key $str1 reason=${cancellationReasonDebugString(int1)}"
})
}
- fun logRankingMissing(key: String, rankingMap: RankingMap) {
- buffer.log(TAG, WARNING, { str1 = key }, { "Ranking update is missing ranking for $str1" })
- buffer.log(TAG, DEBUG, {}, { "Ranking map contents:" })
- for (entry in rankingMap.orderedKeys) {
- buffer.log(TAG, DEBUG, { str1 = entry }, { " $str1" })
- }
+ fun logMissingRankings(
+ newlyInconsistentEntries: List<NotificationEntry>,
+ totalInconsistent: Int,
+ rankingMap: RankingMap
+ ) {
+ buffer.log(TAG, WARNING, {
+ int1 = totalInconsistent
+ int2 = newlyInconsistentEntries.size
+ str1 = newlyInconsistentEntries.joinToString { it.logKey ?: "null" }
+ }, {
+ "Ranking update is missing ranking for $int1 entries ($int2 new): $str1"
+ })
+ buffer.log(TAG, DEBUG, {
+ str1 = rankingMap.orderedKeys.map { logKey(it) ?: "null" }.toString()
+ }, {
+ "Ranking map contents: $str1"
+ })
+ }
+
+ fun logRecoveredRankings(newlyConsistentKeys: List<String>) {
+ buffer.log(TAG, INFO, {
+ int1 = newlyConsistentKeys.size
+ str1 = newlyConsistentKeys.joinToString { logKey(it) ?: "null" }
+ }, {
+ "Ranking update now contains rankings for $int1 previously inconsistent entries: $str1"
+ })
}
- fun logRemoteExceptionOnNotificationClear(key: String, e: RemoteException) {
+ fun logRemoteExceptionOnNotificationClear(entry: NotificationEntry, e: RemoteException) {
buffer.log(TAG, WTF, {
- str1 = key
+ str1 = entry.logKey
str2 = e.toString()
}, {
"RemoteException while attempting to clear $str1:\n$str2"
@@ -217,9 +241,9 @@ class NotifCollectionLogger @Inject constructor(
})
}
- fun logLifetimeExtended(key: String, extender: NotifLifetimeExtender) {
+ fun logLifetimeExtended(entry: NotificationEntry, extender: NotifLifetimeExtender) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = extender.name
}, {
"LIFETIME EXTENDED: $str1 by $str2"
@@ -227,12 +251,12 @@ class NotifCollectionLogger @Inject constructor(
}
fun logLifetimeExtensionEnded(
- key: String,
+ entry: NotificationEntry,
extender: NotifLifetimeExtender,
totalExtenders: Int
) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = extender.name
int1 = totalExtenders
}, {
@@ -248,6 +272,20 @@ class NotifCollectionLogger @Inject constructor(
})
}
+ fun logEntryBeingExtendedNotInCollection(
+ entry: NotificationEntry,
+ extender: NotifLifetimeExtender,
+ collectionEntryIs: String
+ ) {
+ buffer.log(TAG, WARNING, {
+ str1 = entry.logKey
+ str2 = extender.name
+ str3 = collectionEntryIs
+ }, {
+ "While ending lifetime extension by $str2 of $str1, entry in collection is $str3"
+ })
+ }
+
fun logFutureDismissalReused(dismissal: FutureDismissal) {
buffer.log(TAG, INFO, {
str1 = dismissal.label
@@ -324,4 +362,29 @@ class NotifCollectionLogger @Inject constructor(
}
}
+fun maybeLogInconsistentRankings(
+ logger: NotifCollectionLogger,
+ oldKeysWithoutRankings: Set<String>,
+ newEntriesWithoutRankings: Map<String, NotificationEntry>?,
+ rankingMap: RankingMap
+) {
+ if (oldKeysWithoutRankings.isEmpty() && newEntriesWithoutRankings == null) return
+ val newlyConsistent: List<String> = oldKeysWithoutRankings
+ .mapNotNull { key ->
+ key.takeIf { key !in (newEntriesWithoutRankings ?: emptyMap()) }
+ .takeIf { key in rankingMap.orderedKeys }
+ }.sorted()
+ if (newlyConsistent.isNotEmpty()) {
+ logger.logRecoveredRankings(newlyConsistent)
+ }
+ val newlyInconsistent: List<NotificationEntry> = newEntriesWithoutRankings
+ ?.mapNotNull { (key, entry) ->
+ entry.takeIf { key !in oldKeysWithoutRankings }
+ }?.sortedBy { it.key } ?: emptyList()
+ if (newlyInconsistent.isNotEmpty()) {
+ val totalInconsistent: Int = newEntriesWithoutRankings?.size ?: 0
+ logger.logMissingRankings(newlyInconsistent, totalInconsistent, rankingMap)
+ }
+}
+
private const val TAG = "NotifCollection"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
index 6a1e36f4f469..ec10aaf3cfe3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/NotificationVisibilityProviderImpl.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.notification.collection.provider
import com.android.internal.statusbar.NotificationVisibility
+import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
@@ -25,6 +26,7 @@ import com.android.systemui.statusbar.notification.logging.NotificationLogger
import javax.inject.Inject
/** pipeline-agnostic implementation for getting [NotificationVisibility]. */
+@SysUISingleton
class NotificationVisibilityProviderImpl @Inject constructor(
private val notifDataStore: NotifLiveDataStore,
private val notifCollection: CommonNotifCollection
@@ -46,4 +48,4 @@ class NotificationVisibilityProviderImpl @Inject constructor(
NotificationLogger.getNotificationLocation(notifCollection.getEntry(key))
private fun getCount() = notifDataStore.activeNotifCount.value
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
index 68bdd18c9881..82c7aae08f6a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionHeaderVisibilityProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionHeaderVisibilityProvider.kt
@@ -14,11 +14,11 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification
+package com.android.systemui.statusbar.notification.collection.provider
import android.content.Context
-import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.R
+import com.android.systemui.dagger.SysUISingleton
import javax.inject.Inject
/**
@@ -34,7 +34,7 @@ import javax.inject.Inject
class SectionHeaderVisibilityProvider @Inject constructor(
context: Context
) {
- var neverShowSectionHeaders = context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
- private set
+ val neverShowSectionHeaders =
+ context.resources.getBoolean(R.bool.config_notification_never_show_section_headers)
var sectionHeadersVisible = true
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
index 1f2d0fe6c46e..7b9483022fd8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/SectionClassifier.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProvider.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.notification
+package com.android.systemui.statusbar.notification.collection.provider
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
@@ -26,7 +26,7 @@ import javax.inject.Inject
* NOTE: This class exists to avoid putting metadata like "isMinimized" on the NotifSection
*/
@SysUISingleton
-class SectionClassifier @Inject constructor() {
+class SectionStyleProvider @Inject constructor() {
private lateinit var lowPrioritySections: Set<NotifSectioner>
/**
@@ -43,4 +43,4 @@ class SectionClassifier @Inject constructor() {
fun isMinimizedSection(section: NotifSection): Boolean {
return lowPrioritySections.contains(section.sectioner)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
index a2cb9509a621..1b3f83d1892f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/GroupExpansionManagerImpl.java
@@ -16,6 +16,7 @@
package com.android.systemui.statusbar.notification.collection.render;
+import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -28,10 +29,13 @@ import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
+import javax.inject.Inject;
+
/**
* Provides grouping information for notification entries including information about a group's
* expanded state.
*/
+@SysUISingleton
public class GroupExpansionManagerImpl implements GroupExpansionManager, Coordinator {
private final GroupMembershipManager mGroupMembershipManager;
private final Set<OnGroupExpansionChangeListener> mOnGroupChangeListeners = new HashSet<>();
@@ -39,6 +43,7 @@ public class GroupExpansionManagerImpl implements GroupExpansionManager, Coordin
// Set of summary keys whose groups are expanded
private final Set<NotificationEntry> mExpandedGroups = new HashSet<>();
+ @Inject
public GroupExpansionManagerImpl(GroupMembershipManager groupMembershipManager) {
mGroupMembershipManager = groupMembershipManager;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
index 8be710c8842c..d234e54e6725 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilder.kt
@@ -16,12 +16,12 @@
package com.android.systemui.statusbar.notification.collection.render
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.util.Compile
import com.android.systemui.util.traceSection
@@ -107,4 +107,4 @@ class NodeSpecBuilder(
.apply { entry.children.forEach { children.add(buildNotifNode(this, it)) } }
else -> throw RuntimeException("Unexpected entry: $entry")
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
index 3501b44a2c2e..38e3d496a60c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderLogger.kt
@@ -19,20 +19,24 @@ package com.android.systemui.statusbar.notification.collection.render
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.util.Compile
import javax.inject.Inject
class NodeSpecBuilderLogger @Inject constructor(
+ notifPipelineFlags: NotifPipelineFlags,
@NotificationLog private val buffer: LogBuffer
) {
+ private val devLoggingEnabled by lazy { notifPipelineFlags.isDevLoggingEnabled() }
+
fun logBuildNodeSpec(
oldSections: Set<NotifSection?>,
newHeaders: Map<NotifSection?, NodeController?>,
newCounts: Map<NotifSection?, Int>,
newSectionOrder: List<NotifSection?>
) {
- if (!Compile.IS_DEBUG)
+ if (!(Compile.IS_DEBUG && devLoggingEnabled))
return
buffer.log(TAG, LogLevel.DEBUG, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
index f460644ce71c..9a9941e7a19c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDiffer.kt
@@ -123,7 +123,7 @@ class ShadeViewDiffer(
when (childNode.parent) {
null -> {
// A new child (either newly created or coming from some other parent)
- logger.logAttachingChild(childNode.label, parentNode.label)
+ logger.logAttachingChild(childNode.label, parentNode.label, index)
parentNode.addChildAt(childNode, index)
childNode.parent = parentNode
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
index 4c0357243d48..6d1071c283e3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferLogger.kt
@@ -43,12 +43,13 @@ class ShadeViewDifferLogger @Inject constructor(
})
}
- fun logAttachingChild(key: String, parent: String) {
+ fun logAttachingChild(key: String, parent: String, atIndex: Int) {
buffer.log(TAG, LogLevel.DEBUG, {
str1 = key
str2 = parent
+ int1 = atIndex
}, {
- "Attaching view $str1 to $str2"
+ "Attaching view $str1 to $str2 at index $int1"
})
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
index 6ed81078c3a4..51dc72848d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewManager.kt
@@ -19,10 +19,10 @@ package com.android.systemui.statusbar.notification.collection.render
import android.content.Context
import android.view.View
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.ListEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.util.traceSection
import dagger.assisted.Assisted
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index c9c7fe9e0ab6..bf08fc7f53e2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -35,29 +35,26 @@ import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.settings.UserContextProvider;
+import com.android.systemui.shade.NotifPanelEventsModule;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.notification.AssistantFeedbackController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationEntryManagerLogger;
-import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStoreImpl;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotifPipelineChoreographerModule;
import com.android.systemui.statusbar.notification.collection.coordinator.ShadeEventCoordinator;
-import com.android.systemui.statusbar.notification.collection.coordinator.VisualStabilityCoordinator;
import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorsModule;
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManager;
import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl;
import com.android.systemui.statusbar.notification.collection.inflation.NotifInflater;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
import com.android.systemui.statusbar.notification.collection.inflation.OnUserInteractionCallbackImpl;
-import com.android.systemui.statusbar.notification.collection.legacy.LegacyNotificationPresenterExtensions;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.legacy.OnUserInteractionCallbackImplLegacy;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
@@ -88,9 +85,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationSectionsMan
import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotifPanelEventsModule;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.leak.LeakDetector;
import com.android.systemui.wmshell.BubblesManager;
@@ -116,12 +111,10 @@ import dagger.Provides;
})
public interface NotificationsModule {
@Binds
- StackScrollAlgorithm.SectionProvider bindSectionProvider(
- NotificationSectionsManager impl);
+ StackScrollAlgorithm.SectionProvider bindSectionProvider(NotificationSectionsManager impl);
@Binds
- StackScrollAlgorithm.BypassController bindBypassController(
- KeyguardBypassController impl);
+ StackScrollAlgorithm.BypassController bindBypassController(KeyguardBypassController impl);
/** Provides an instance of {@link NotificationEntryManager} */
@SysUISingleton
@@ -135,7 +128,8 @@ public interface NotificationsModule {
LeakDetector leakDetector,
IStatusBarService statusBarService,
NotifLiveDataStoreImpl notifLiveDataStore,
- DumpManager dumpManager) {
+ DumpManager dumpManager,
+ @Background Executor bgExecutor) {
return new NotificationEntryManager(
logger,
groupManager,
@@ -145,7 +139,8 @@ public interface NotificationsModule {
leakDetector,
statusBarService,
notifLiveDataStore,
- dumpManager);
+ dumpManager,
+ bgExecutor);
}
/** Provides an instance of {@link NotificationGutsManager} */
@@ -194,12 +189,8 @@ public interface NotificationsModule {
}
/** Provides an instance of {@link NotifGutsViewManager} */
- @SysUISingleton
- @Provides
- static NotifGutsViewManager provideNotifGutsViewManager(
- NotificationGutsManager notificationGutsManager) {
- return notificationGutsManager;
- }
+ @Binds
+ NotifGutsViewManager bindNotifGutsViewManager(NotificationGutsManager notificationGutsManager);
/** Provides an instance of {@link VisualStabilityManager} */
@SysUISingleton
@@ -226,10 +217,8 @@ public interface NotificationsModule {
static NotificationLogger provideNotificationLogger(
NotificationListener notificationListener,
@UiBackground Executor uiBgExecutor,
- NotifPipelineFlags notifPipelineFlags,
NotifLiveDataStore notifLiveDataStore,
NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager entryManager,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
NotificationLogger.ExpansionStateLogger expansionStateLogger,
@@ -237,10 +226,8 @@ public interface NotificationsModule {
return new NotificationLogger(
notificationListener,
uiBgExecutor,
- notifPipelineFlags,
notifLiveDataStore,
visibilityProvider,
- entryManager,
notifPipeline,
statusBarStateController,
expansionStateLogger,
@@ -257,25 +244,13 @@ public interface NotificationsModule {
/** Provides an instance of {@link GroupMembershipManager} */
@SysUISingleton
@Provides
- static GroupMembershipManager provideGroupMembershipManager(
- NotifPipelineFlags notifPipelineFlags,
- Lazy<NotificationGroupManagerLegacy> groupManagerLegacy) {
- return notifPipelineFlags.isNewPipelineEnabled()
- ? new GroupMembershipManagerImpl()
- : groupManagerLegacy.get();
+ static GroupMembershipManager provideGroupMembershipManager() {
+ return new GroupMembershipManagerImpl();
}
/** Provides an instance of {@link GroupExpansionManager} */
- @SysUISingleton
- @Provides
- static GroupExpansionManager provideGroupExpansionManager(
- NotifPipelineFlags notifPipelineFlags,
- Lazy<GroupMembershipManager> groupMembershipManager,
- Lazy<NotificationGroupManagerLegacy> groupManagerLegacy) {
- return notifPipelineFlags.isNewPipelineEnabled()
- ? new GroupExpansionManagerImpl(groupMembershipManager.get())
- : groupManagerLegacy.get();
- }
+ @Binds
+ GroupExpansionManager provideGroupExpansionManager(GroupExpansionManagerImpl impl);
/** Initializes the notification data pipeline (can be disabled via config). */
@SysUISingleton
@@ -294,69 +269,28 @@ public interface NotificationsModule {
/**
* Provide the active notification collection managing the notifications to render.
*/
- @Provides
- @SysUISingleton
- static CommonNotifCollection provideCommonNotifCollection(
- NotifPipelineFlags notifPipelineFlags,
- Lazy<NotifPipeline> pipeline,
- NotificationEntryManager entryManager) {
- return notifPipelineFlags.isNewPipelineEnabled()
- ? pipeline.get() : entryManager;
- }
+ @Binds
+ CommonNotifCollection provideCommonNotifCollection(NotifPipeline pipeline);
/**
* Provide the object which can be used to obtain NotificationVisibility objects.
*/
@Binds
- @SysUISingleton
NotificationVisibilityProvider provideNotificationVisibilityProvider(
- NotificationVisibilityProviderImpl newProvider);
+ NotificationVisibilityProviderImpl impl);
/**
* Provide the active implementation for presenting notifications.
*/
- @Provides
- @SysUISingleton
- static NotifShadeEventSource provideNotifShadeEventSource(
- NotifPipelineFlags notifPipelineFlags,
- Lazy<ShadeEventCoordinator> shadeEventCoordinatorLazy,
- Lazy<LegacyNotificationPresenterExtensions> legacyNotificationPresenterExtensionsLazy) {
- return notifPipelineFlags.isNewPipelineEnabled()
- ? shadeEventCoordinatorLazy.get()
- : legacyNotificationPresenterExtensionsLazy.get();
- }
+ @Binds
+ NotifShadeEventSource provideNotifShadeEventSource(ShadeEventCoordinator shadeEventCoordinator);
/**
* Provide a dismissal callback that's triggered when a user manually dismissed a notification
* from the notification shade or it gets auto-cancelled by click.
*/
- @Provides
- @SysUISingleton
- static OnUserInteractionCallback provideOnUserInteractionCallback(
- NotifPipelineFlags notifPipelineFlags,
- HeadsUpManager headsUpManager,
- StatusBarStateController statusBarStateController,
- Lazy<NotifCollection> notifCollection,
- Lazy<NotificationVisibilityProvider> visibilityProvider,
- Lazy<VisualStabilityCoordinator> visualStabilityCoordinator,
- NotificationEntryManager entryManager,
- VisualStabilityManager visualStabilityManager,
- Lazy<GroupMembershipManager> groupMembershipManagerLazy) {
- return notifPipelineFlags.isNewPipelineEnabled()
- ? new OnUserInteractionCallbackImpl(
- visibilityProvider.get(),
- notifCollection.get(),
- headsUpManager,
- statusBarStateController,
- visualStabilityCoordinator.get())
- : new OnUserInteractionCallbackImplLegacy(
- entryManager,
- visibilityProvider.get(),
- headsUpManager,
- statusBarStateController,
- visualStabilityManager,
- groupMembershipManagerLazy.get());
- }
+ @Binds
+ OnUserInteractionCallback provideOnUserInteractionCallback(OnUserInteractionCallbackImpl impl);
/** */
@Binds
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 11ffde625798..0e9f1cda73fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -23,31 +23,22 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
-import com.android.systemui.statusbar.notification.NotifPipelineFlags
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.NotificationClicker
import com.android.systemui.statusbar.notification.NotificationEntryManager
import com.android.systemui.statusbar.notification.NotificationListController
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore
import com.android.systemui.statusbar.notification.collection.NotifPipeline
-import com.android.systemui.statusbar.notification.collection.NotificationRankingManager
import com.android.systemui.statusbar.notification.collection.TargetSdkResolver
-import com.android.systemui.statusbar.notification.collection.inflation.BindEventManagerImpl
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection
-import com.android.systemui.statusbar.notification.collection.provider.DebugModeFilterProvider
import com.android.systemui.statusbar.notification.collection.render.NotifStackController
-import com.android.systemui.statusbar.notification.interruption.HeadsUpController
import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.CentralSurfaces
-import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
import com.android.systemui.statusbar.policy.DeviceProvisionedController
-import com.android.systemui.statusbar.policy.HeadsUpManager
-import com.android.systemui.statusbar.policy.RemoteInputUriController
import com.android.wm.shell.bubbles.Bubbles
import dagger.Lazy
import java.io.PrintWriter
@@ -64,30 +55,21 @@ import javax.inject.Inject
@SysUISingleton
class NotificationsControllerImpl @Inject constructor(
private val centralSurfaces: Lazy<CentralSurfaces>,
- private val notifPipelineFlags: NotifPipelineFlags,
private val notificationListener: NotificationListener,
private val entryManager: NotificationEntryManager,
- private val debugModeFilterProvider: DebugModeFilterProvider,
- private val legacyRanker: NotificationRankingManager,
private val commonNotifCollection: Lazy<CommonNotifCollection>,
private val notifPipeline: Lazy<NotifPipeline>,
private val notifLiveDataStore: NotifLiveDataStore,
private val targetSdkResolver: TargetSdkResolver,
- private val newNotifPipelineInitializer: Lazy<NotifPipelineInitializer>,
+ private val notifPipelineInitializer: Lazy<NotifPipelineInitializer>,
private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
private val deviceProvisionedController: DeviceProvisionedController,
private val notificationRowBinder: NotificationRowBinderImpl,
- private val bindEventManagerImpl: BindEventManagerImpl,
- private val remoteInputUriController: RemoteInputUriController,
- private val groupManagerLegacy: Lazy<NotificationGroupManagerLegacy>,
- private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
- private val headsUpManager: HeadsUpManager,
- private val headsUpController: HeadsUpController,
private val headsUpViewBinder: HeadsUpViewBinder,
private val clickerBuilder: NotificationClicker.Builder,
private val animatedImageNotificationManager: AnimatedImageNotificationManager,
private val peopleSpaceWidgetManager: PeopleSpaceWidgetManager,
- private val bubblesOptional: Optional<Bubbles>
+ private val bubblesOptional: Optional<Bubbles>,
) : NotificationsController {
override fun initialize(
@@ -118,33 +100,13 @@ class NotificationsControllerImpl @Inject constructor(
notifBindPipelineInitializer.initialize()
animatedImageNotificationManager.bind()
- if (INITIALIZE_NEW_PIPELINE) {
- newNotifPipelineInitializer.get().initialize(
- notificationListener,
- notificationRowBinder,
- listContainer,
- stackController)
- }
+ notifPipelineInitializer.get().initialize(
+ notificationListener,
+ notificationRowBinder,
+ listContainer,
+ stackController)
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- targetSdkResolver.initialize(notifPipeline.get())
- // TODO
- } else {
- targetSdkResolver.initialize(entryManager)
- remoteInputUriController.attach(entryManager)
- groupAlertTransferHelper.bind(entryManager, groupManagerLegacy.get())
- bindEventManagerImpl.attachToLegacyPipeline(entryManager)
- headsUpManager.addListener(groupManagerLegacy.get())
- headsUpManager.addListener(groupAlertTransferHelper)
- headsUpController.attach(entryManager, headsUpManager)
- groupManagerLegacy.get().setHeadsUpManager(headsUpManager)
- groupAlertTransferHelper.setHeadsUpManager(headsUpManager)
- debugModeFilterProvider.registerInvalidationListener {
- entryManager.updateNotifications("debug mode filter changed")
- }
-
- entryManager.initialize(notificationListener, legacyRanker)
- }
+ targetSdkResolver.initialize(notifPipeline.get())
peopleSpaceWidgetManager.attach(notificationListener)
}
@@ -185,9 +147,4 @@ class NotificationsControllerImpl @Inject constructor(
override fun getActiveNotificationsCount(): Int =
notifLiveDataStore.activeNotifCount.value
-
- companion object {
- // NOTE: The new pipeline is always active, even if the old pipeline is *rendering*.
- private const val INITIALIZE_NEW_PIPELINE = true
- }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
index 19cf9dc135b1..5ef2b9e55d9e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinder.java
@@ -83,7 +83,7 @@ public class HeadsUpViewBinder {
params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
CancellationSignal signal = mStage.requestRebind(entry, en -> {
- mLogger.entryBoundSuccessfully(entry.getKey());
+ mLogger.entryBoundSuccessfully(entry);
en.getRow().setUsesIncreasedHeadsUpHeight(params.useIncreasedHeadsUpHeight());
// requestRebing promises that if we called cancel before this callback would be
// invoked, then we will not enter this callback, and because we always cancel before
@@ -94,7 +94,7 @@ public class HeadsUpViewBinder {
}
});
abortBindCallback(entry);
- mLogger.startBindingHun(entry.getKey());
+ mLogger.startBindingHun(entry);
mOngoingBindCallbacks.put(entry, signal);
}
@@ -105,7 +105,7 @@ public class HeadsUpViewBinder {
public void abortBindCallback(NotificationEntry entry) {
CancellationSignal ongoingBindCallback = mOngoingBindCallbacks.remove(entry);
if (ongoingBindCallback != null) {
- mLogger.currentOngoingBindingAborted(entry.getKey());
+ mLogger.currentOngoingBindingAborted(entry);
ongoingBindCallback.cancel();
}
}
@@ -116,7 +116,7 @@ public class HeadsUpViewBinder {
public void unbindHeadsUpView(NotificationEntry entry) {
abortBindCallback(entry);
mStage.getStageParams(entry).markContentViewsFreeable(FLAG_CONTENT_VIEW_HEADS_UP);
- mLogger.entryContentViewMarkedFreeable(entry.getKey());
- mStage.requestRebind(entry, e -> mLogger.entryUnbound(e.getKey()));
+ mLogger.entryContentViewMarkedFreeable(entry);
+ mStage.requestRebind(entry, e -> mLogger.entryUnbound(e));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
index 50a6207efe0b..d1feaa05c653 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderLogger.kt
@@ -3,44 +3,46 @@ package com.android.systemui.statusbar.notification.interruption
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class HeadsUpViewBinderLogger @Inject constructor(@NotificationHeadsUpLog val buffer: LogBuffer) {
- fun startBindingHun(key: String) {
+ fun startBindingHun(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"start binding heads up entry $str1 "
})
}
- fun currentOngoingBindingAborted(key: String) {
+ fun currentOngoingBindingAborted(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"aborted potential ongoing heads up entry binding $str1 "
})
}
- fun entryBoundSuccessfully(key: String) {
+ fun entryBoundSuccessfully(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"heads up entry bound successfully $str1 "
})
}
- fun entryUnbound(key: String) {
+ fun entryUnbound(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"heads up entry unbound successfully $str1 "
})
}
- fun entryContentViewMarkedFreeable(key: String) {
+ fun entryContentViewMarkedFreeable(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"start unbinding heads up entry $str1 "
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
index 6c99e3adb73e..99d320d1c7ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptLogger.kt
@@ -16,20 +16,20 @@
package com.android.systemui.statusbar.notification.interruption
-import android.service.notification.StatusBarNotification
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.DEBUG
import com.android.systemui.log.LogLevel.INFO
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.NotificationInterruptLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class NotificationInterruptLogger @Inject constructor(
- @NotificationLog val notifBuffer: LogBuffer,
- @NotificationHeadsUpLog val hunBuffer: LogBuffer
+ @NotificationInterruptLog val buffer: LogBuffer
) {
fun logHeadsUpFeatureChanged(useHeadsUp: Boolean) {
- hunBuffer.log(TAG, INFO, {
+ buffer.log(TAG, INFO, {
bool1 = useHeadsUp
}, {
"heads up is enabled=$bool1"
@@ -37,118 +37,118 @@ class NotificationInterruptLogger @Inject constructor(
}
fun logWillDismissAll() {
- hunBuffer.log(TAG, INFO, {
+ buffer.log(TAG, INFO, {
}, {
"dismissing any existing heads up notification on disable event"
})
}
- fun logNoBubbleNotAllowed(sbn: StatusBarNotification) {
- notifBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoBubbleNotAllowed(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No bubble up: not allowed to bubble: $str1"
})
}
- fun logNoBubbleNoMetadata(sbn: StatusBarNotification) {
- notifBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoBubbleNoMetadata(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No bubble up: notification: $str1 doesn't have valid metadata"
})
}
fun logNoHeadsUpFeatureDisabled() {
- hunBuffer.log(TAG, DEBUG, {
+ buffer.log(TAG, DEBUG, {
}, {
"No heads up: no huns"
})
}
- fun logNoHeadsUpPackageSnoozed(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoHeadsUpPackageSnoozed(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No alerting: snoozed package: $str1"
})
}
- fun logNoHeadsUpAlreadyBubbled(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoHeadsUpAlreadyBubbled(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No heads up: in unlocked shade where notification is shown as a bubble: $str1"
})
}
- fun logNoHeadsUpSuppressedByDnd(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoHeadsUpSuppressedByDnd(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No heads up: suppressed by DND: $str1"
})
}
- fun logNoHeadsUpNotImportant(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoHeadsUpNotImportant(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No heads up: unimportant notification: $str1"
})
}
- fun logNoHeadsUpNotInUse(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoHeadsUpNotInUse(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No heads up: not in use: $str1"
})
}
fun logNoHeadsUpSuppressedBy(
- sbn: StatusBarNotification,
+ entry: NotificationEntry,
suppressor: NotificationInterruptSuppressor
) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
str2 = suppressor.name
}, {
"No heads up: aborted by suppressor: $str2 sbnKey=$str1"
})
}
- fun logHeadsUp(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logHeadsUp(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"Heads up: $str1"
})
}
- fun logNoAlertingFilteredOut(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoAlertingFilteredOut(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No alerting: filtered notification: $str1"
})
}
- fun logNoAlertingGroupAlertBehavior(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoAlertingGroupAlertBehavior(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No alerting: suppressed due to group alert behavior: $str1"
})
}
fun logNoAlertingSuppressedBy(
- sbn: StatusBarNotification,
+ entry: NotificationEntry,
suppressor: NotificationInterruptSuppressor,
awake: Boolean
) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
str2 = suppressor.name
bool1 = awake
}, {
@@ -156,65 +156,92 @@ class NotificationInterruptLogger @Inject constructor(
})
}
- fun logNoAlertingRecentFullscreen(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoAlertingRecentFullscreen(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No alerting: recent fullscreen: $str1"
})
}
- fun logNoPulsingSettingDisabled(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoPulsingSettingDisabled(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No pulsing: disabled by setting: $str1"
})
}
- fun logNoPulsingBatteryDisabled(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoPulsingBatteryDisabled(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No pulsing: disabled by battery saver: $str1"
})
}
- fun logNoPulsingNoAlert(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoPulsingNoAlert(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No pulsing: notification shouldn't alert: $str1"
})
}
- fun logNoPulsingNoAmbientEffect(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoPulsingNoAmbientEffect(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No pulsing: ambient effect suppressed: $str1"
})
}
- fun logNoPulsingNotImportant(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logNoPulsingNotImportant(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"No pulsing: not important enough: $str1"
})
}
- fun logPulsing(sbn: StatusBarNotification) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = sbn.key
+ fun logPulsing(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"Pulsing: $str1"
})
}
- fun keyguardHideNotification(key: String) {
- hunBuffer.log(TAG, DEBUG, {
- str1 = key
+ fun logNoFullscreen(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "No FullScreenIntent: $str2: $str1"
+ })
+ }
+
+ fun logNoFullscreenWarning(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, WARNING, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "No FullScreenIntent: WARNING: $str2: $str1"
+ })
+ }
+
+ fun logFullscreen(entry: NotificationEntry, reason: String) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
+ str2 = reason
+ }, {
+ "FullScreenIntent: $str2: $str1"
+ })
+ }
+
+ fun keyguardHideNotification(entry: NotificationEntry) {
+ buffer.log(TAG, DEBUG, {
+ str1 = entry.logKey
}, {
"Keyguard Hide Notification: $str1"
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
index e210f193b0a1..a72b3814d5ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImpl.java
@@ -41,6 +41,7 @@ import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import java.util.ArrayList;
import java.util.List;
@@ -58,6 +59,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
private final List<NotificationInterruptSuppressor> mSuppressors = new ArrayList<>();
private final StatusBarStateController mStatusBarStateController;
+ private final KeyguardStateController mKeyguardStateController;
private final NotificationFilter mNotificationFilter;
private final ContentResolver mContentResolver;
private final PowerManager mPowerManager;
@@ -82,6 +84,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
NotificationFilter notificationFilter,
BatteryController batteryController,
StatusBarStateController statusBarStateController,
+ KeyguardStateController keyguardStateController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
@Main Handler mainHandler,
@@ -94,6 +97,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
mAmbientDisplayConfiguration = ambientDisplayConfiguration;
mNotificationFilter = notificationFilter;
mStatusBarStateController = statusBarStateController;
+ mKeyguardStateController = keyguardStateController;
mHeadsUpManager = headsUpManager;
mLogger = logger;
mFlags = flags;
@@ -147,14 +151,14 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
}
if (!entry.canBubble()) {
- mLogger.logNoBubbleNotAllowed(sbn);
+ mLogger.logNoBubbleNotAllowed(entry);
return false;
}
if (entry.getBubbleMetadata() == null
|| (entry.getBubbleMetadata().getShortcutId() == null
&& entry.getBubbleMetadata().getIntent() == null)) {
- mLogger.logNoBubbleNoMetadata(sbn);
+ mLogger.logNoBubbleNoMetadata(entry);
return false;
}
@@ -177,9 +181,91 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
*/
@Override
public boolean shouldLaunchFullScreenIntentWhenAdded(NotificationEntry entry) {
- return entry.getSbn().getNotification().fullScreenIntent != null
- && (!shouldHeadsUp(entry)
- || mStatusBarStateController.getState() == StatusBarState.KEYGUARD);
+ if (entry.getSbn().getNotification().fullScreenIntent == null) {
+ return false;
+ }
+
+ // Never show FSI when suppressed by DND
+ if (entry.shouldSuppressFullScreenIntent()) {
+ mLogger.logNoFullscreen(entry, "Suppressed by DND");
+ return false;
+ }
+
+ // Never show FSI if importance is not HIGH
+ if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
+ mLogger.logNoFullscreen(entry, "Not important enough");
+ return false;
+ }
+
+ // If the notification has suppressive GroupAlertBehavior, block FSI and warn.
+ StatusBarNotification sbn = entry.getSbn();
+ if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
+ // b/231322873: Detect and report an event when a notification has both an FSI and a
+ // suppressive groupAlertBehavior, and now correctly block the FSI from firing.
+ final int uid = entry.getSbn().getUid();
+ android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "groupAlertBehavior");
+ mLogger.logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+ return false;
+ }
+
+ // If the screen is off, then launch the FullScreenIntent
+ if (!mPowerManager.isInteractive()) {
+ mLogger.logFullscreen(entry, "Device is not interactive");
+ return true;
+ }
+
+ // If the device is currently dreaming, then launch the FullScreenIntent
+ if (isDreaming()) {
+ mLogger.logFullscreen(entry, "Device is dreaming");
+ return true;
+ }
+
+ // If the keyguard is showing, then launch the FullScreenIntent
+ if (mStatusBarStateController.getState() == StatusBarState.KEYGUARD) {
+ mLogger.logFullscreen(entry, "Keyguard is showing");
+ return true;
+ }
+
+ // If the notification should HUN, then we don't need FSI
+ if (shouldHeadsUp(entry)) {
+ mLogger.logNoFullscreen(entry, "Expected to HUN");
+ return false;
+ }
+
+ // Check whether FSI requires the keyguard to be showing.
+ if (mFlags.fullScreenIntentRequiresKeyguard()) {
+
+ // If notification won't HUN and keyguard is showing, launch the FSI.
+ if (mKeyguardStateController.isShowing()) {
+ if (mKeyguardStateController.isOccluded()) {
+ mLogger.logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+ } else {
+ // Likely LOCKED_SHADE, but launch FSI anyway
+ mLogger.logFullscreen(entry, "Keyguard is showing and not occluded");
+ }
+ return true;
+ }
+
+ // Detect the case determined by b/231322873 to launch FSI while device is in use,
+ // as blocked by the correct implementation, and report the event.
+ final int uid = entry.getSbn().getUid();
+ android.util.EventLog.writeEvent(0x534e4554, "231322873", uid, "no hun or keyguard");
+ mLogger.logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ return false;
+ }
+
+ // If the notification won't HUN for some other reason (DND/snooze/etc), launch FSI.
+ mLogger.logFullscreen(entry, "Expected not to HUN");
+ return true;
+ }
+
+ private boolean isDreaming() {
+ try {
+ return mDreamManager.isDreaming();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Failed to query dream manager.", e);
+ return false;
+ }
}
private boolean shouldHeadsUpWhenAwake(NotificationEntry entry) {
@@ -194,51 +280,49 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
return false;
}
+ if (!canAlertHeadsUpCommon(entry)) {
+ return false;
+ }
+
if (!canAlertAwakeCommon(entry)) {
return false;
}
if (isSnoozedPackage(sbn)) {
- mLogger.logNoHeadsUpPackageSnoozed(sbn);
+ mLogger.logNoHeadsUpPackageSnoozed(entry);
return false;
}
boolean inShade = mStatusBarStateController.getState() == SHADE;
if (entry.isBubble() && inShade) {
- mLogger.logNoHeadsUpAlreadyBubbled(sbn);
+ mLogger.logNoHeadsUpAlreadyBubbled(entry);
return false;
}
if (entry.shouldSuppressPeek()) {
- mLogger.logNoHeadsUpSuppressedByDnd(sbn);
+ mLogger.logNoHeadsUpSuppressedByDnd(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
- mLogger.logNoHeadsUpNotImportant(sbn);
+ mLogger.logNoHeadsUpNotImportant(entry);
return false;
}
- boolean isDreaming = false;
- try {
- isDreaming = mDreamManager.isDreaming();
- } catch (RemoteException e) {
- Log.e(TAG, "Failed to query dream manager.", e);
- }
- boolean inUse = mPowerManager.isScreenOn() && !isDreaming;
+ boolean inUse = mPowerManager.isScreenOn() && !isDreaming();
if (!inUse) {
- mLogger.logNoHeadsUpNotInUse(sbn);
+ mLogger.logNoHeadsUpNotInUse(entry);
return false;
}
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeHeadsUp(entry)) {
- mLogger.logNoHeadsUpSuppressedBy(sbn, mSuppressors.get(i));
+ mLogger.logNoHeadsUpSuppressedBy(entry, mSuppressors.get(i));
return false;
}
}
- mLogger.logHeadsUp(sbn);
+ mLogger.logHeadsUp(entry);
return true;
}
@@ -250,33 +334,36 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
* @return true if the entry should ambient pulse, false otherwise
*/
private boolean shouldHeadsUpWhenDozing(NotificationEntry entry) {
- StatusBarNotification sbn = entry.getSbn();
-
if (!mAmbientDisplayConfiguration.pulseOnNotificationEnabled(UserHandle.USER_CURRENT)) {
- mLogger.logNoPulsingSettingDisabled(sbn);
+ mLogger.logNoPulsingSettingDisabled(entry);
return false;
}
if (mBatteryController.isAodPowerSave()) {
- mLogger.logNoPulsingBatteryDisabled(sbn);
+ mLogger.logNoPulsingBatteryDisabled(entry);
return false;
}
if (!canAlertCommon(entry)) {
- mLogger.logNoPulsingNoAlert(sbn);
+ mLogger.logNoPulsingNoAlert(entry);
+ return false;
+ }
+
+ if (!canAlertHeadsUpCommon(entry)) {
+ mLogger.logNoPulsingNoAlert(entry);
return false;
}
if (entry.shouldSuppressAmbient()) {
- mLogger.logNoPulsingNoAmbientEffect(sbn);
+ mLogger.logNoPulsingNoAmbientEffect(entry);
return false;
}
if (entry.getImportance() < NotificationManager.IMPORTANCE_DEFAULT) {
- mLogger.logNoPulsingNotImportant(sbn);
+ mLogger.logNoPulsingNotImportant(entry);
return false;
}
- mLogger.logPulsing(sbn);
+ mLogger.logPulsing(entry);
return true;
}
@@ -287,33 +374,38 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
* @return true if these checks pass, false if the notification should not alert
*/
private boolean canAlertCommon(NotificationEntry entry) {
- StatusBarNotification sbn = entry.getSbn();
+ for (int i = 0; i < mSuppressors.size(); i++) {
+ if (mSuppressors.get(i).suppressInterruptions(entry)) {
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ false);
+ return false;
+ }
+ }
- if (!mFlags.isNewPipelineEnabled() && mNotificationFilter.shouldFilterOut(entry)) {
- mLogger.logNoAlertingFilteredOut(sbn);
+ if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
+ mLogger.keyguardHideNotification(entry);
return false;
}
+ return true;
+ }
+
+ /**
+ * Common checks for heads up notifications on regular and AOD displays.
+ *
+ * @param entry the entry to check
+ * @return true if these checks pass, false if the notification should not alert
+ */
+ private boolean canAlertHeadsUpCommon(NotificationEntry entry) {
+ StatusBarNotification sbn = entry.getSbn();
+
// Don't alert notifications that are suppressed due to group alert behavior
if (sbn.isGroup() && sbn.getNotification().suppressAlertingDueToGrouping()) {
- mLogger.logNoAlertingGroupAlertBehavior(sbn);
+ mLogger.logNoAlertingGroupAlertBehavior(entry);
return false;
}
- for (int i = 0; i < mSuppressors.size(); i++) {
- if (mSuppressors.get(i).suppressInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ false);
- return false;
- }
- }
-
if (entry.hasJustLaunchedFullScreenIntent()) {
- mLogger.logNoAlertingRecentFullscreen(sbn);
- return false;
- }
-
- if (mKeyguardNotificationVisibilityProvider.shouldHideNotification(entry)) {
- mLogger.keyguardHideNotification(entry.getKey());
+ mLogger.logNoAlertingRecentFullscreen(entry);
return false;
}
@@ -331,7 +423,7 @@ public class NotificationInterruptStateProviderImpl implements NotificationInter
for (int i = 0; i < mSuppressors.size(); i++) {
if (mSuppressors.get(i).suppressAwakeInterruptions(entry)) {
- mLogger.logNoAlertingSuppressedBy(sbn, mSuppressors.get(i), /* awake */ true);
+ mLogger.logNoAlertingSuppressedBy(entry, mSuppressors.get(i), /* awake */ true);
return false;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
index 9fbd5c39dedd..639187790ae0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/logging/NotificationLogger.java
@@ -22,7 +22,6 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.Trace;
import android.service.notification.NotificationListenerService;
-import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Log;
@@ -39,9 +38,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -80,7 +76,6 @@ public class NotificationLogger implements StateListener {
private final Executor mUiBgExecutor;
private final NotifLiveDataStore mNotifLiveDataStore;
private final NotificationVisibilityProvider mVisibilityProvider;
- private final NotificationEntryManager mEntryManager;
private final NotifPipeline mNotifPipeline;
private final NotificationPanelLogger mNotificationPanelLogger;
private final ExpansionStateLogger mExpansionStateLogger;
@@ -220,10 +215,8 @@ public class NotificationLogger implements StateListener {
*/
public NotificationLogger(NotificationListener notificationListener,
@UiBackground Executor uiBgExecutor,
- NotifPipelineFlags notifPipelineFlags,
NotifLiveDataStore notifLiveDataStore,
NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager entryManager,
NotifPipeline notifPipeline,
StatusBarStateController statusBarStateController,
ExpansionStateLogger expansionStateLogger,
@@ -232,7 +225,6 @@ public class NotificationLogger implements StateListener {
mUiBgExecutor = uiBgExecutor;
mNotifLiveDataStore = notifLiveDataStore;
mVisibilityProvider = visibilityProvider;
- mEntryManager = entryManager;
mNotifPipeline = notifPipeline;
mBarService = IStatusBarService.Stub.asInterface(
ServiceManager.getService(Context.STATUS_BAR_SERVICE));
@@ -241,36 +233,7 @@ public class NotificationLogger implements StateListener {
// Not expected to be destroyed, don't need to unsubscribe
statusBarStateController.addCallback(this);
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- registerNewPipelineListener();
- } else {
- registerLegacyListener();
- }
- }
-
- private void registerLegacyListener() {
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onEntryRemoved(
- NotificationEntry entry,
- NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- mExpansionStateLogger.onEntryRemoved(entry.getKey());
- }
-
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- mExpansionStateLogger.onEntryUpdated(entry.getKey());
- }
-
- @Override
- public void onInflationError(
- StatusBarNotification notification,
- Exception exception) {
- logNotificationError(notification, exception);
- }
- });
+ registerNewPipelineListener();
}
private void registerNewPipelineListener() {
@@ -333,26 +296,6 @@ public class NotificationLogger implements StateListener {
}
}
- /**
- * Logs Notification inflation error
- */
- private void logNotificationError(
- StatusBarNotification notification,
- Exception exception) {
- try {
- mBarService.onNotificationError(
- notification.getPackageName(),
- notification.getTag(),
- notification.getId(),
- notification.getUid(),
- notification.getInitialPid(),
- exception.getMessage(),
- notification.getUserId());
- } catch (RemoteException ex) {
- // The end is nigh.
- }
- }
-
private void logNotificationVisibilityChanges(
Collection<NotificationVisibility> newlyVisible,
Collection<NotificationVisibility> noLongerVisible) {
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 26614ff4ae58..3c018022085d 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
@@ -143,7 +143,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
// We don't correctly track dark mode until the content views are inflated, so always update
// the background on first content update just in case it happens to be during a theme change.
- private boolean mUpdateBackgroundOnUpdate = true;
+ private boolean mUpdateSelfBackgroundOnUpdate = true;
private boolean mNotificationTranslationFinished = false;
private boolean mIsSnoozed;
private boolean mIsFaded;
@@ -555,9 +555,28 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
updateLimits();
updateShelfIconColor();
updateRippleAllowed();
- if (mUpdateBackgroundOnUpdate) {
- mUpdateBackgroundOnUpdate = false;
- updateBackgroundColors();
+ if (mUpdateSelfBackgroundOnUpdate) {
+ // Because this is triggered by UiMode change which we already propagated to children,
+ // we know that child rows will receive the same event, and will update their own
+ // backgrounds when they finish inflating, so propagating again would be redundant.
+ mUpdateSelfBackgroundOnUpdate = false;
+ updateBackgroundColorsOfSelf();
+ }
+ }
+
+ private void updateBackgroundColorsOfSelf() {
+ super.updateBackgroundColors();
+ }
+
+ @Override
+ public void updateBackgroundColors() {
+ // Because this call is made by the NSSL only on attached rows at the moment of the
+ // UiMode or Theme change, we have to propagate to our child views.
+ updateBackgroundColorsOfSelf();
+ if (mIsSummaryWithChildren) {
+ for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) {
+ child.updateBackgroundColors();
+ }
}
}
@@ -1244,7 +1263,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
public void onUiModeChanged() {
- mUpdateBackgroundOnUpdate = true;
+ mUpdateSelfBackgroundOnUpdate = true;
reInflateViews();
if (mChildrenContainer != null) {
for (ExpandableNotificationRow child : mChildrenContainer.getAttachedChildren()) {
@@ -1890,6 +1909,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
* @param isEnabled whether the content views should be enabled for accessibility
*/
private void updateContentAccessibilityImportanceForGuts(boolean isEnabled) {
+ updateAccessibilityImportance(isEnabled);
+
if (mChildrenContainer != null) {
updateChildAccessibilityImportance(mChildrenContainer, isEnabled);
}
@@ -1905,6 +1926,15 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
/**
+ * Updates whether this view is important for accessibility based on {@code isEnabled}.
+ */
+ private void updateAccessibilityImportance(boolean isEnabled) {
+ setImportantForAccessibility(isEnabled
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
+ : View.IMPORTANT_FOR_ACCESSIBILITY_NO);
+ }
+
+ /**
* Updates whether the given childView is important for accessibility based on
* {@code isEnabled}.
*/
@@ -2075,6 +2105,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
public void applyLaunchAnimationParams(LaunchAnimationParameters params) {
if (params == null) {
+ // `null` params indicates the animation is over, which means we can't access
+ // params.getParentStartClipTopAmount() which has the value we want to restore.
+ // Fortunately, only NotificationShelf actually uses these values for anything other
+ // than this launch animation, so we can restore the value to 0 and it's right for now.
+ if (mNotificationParent != null) {
+ mNotificationParent.setClipTopAmount(0);
+ }
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
index 599039d46556..a493a676e3d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row;
import static com.android.systemui.Dependency.ALLOW_NOTIFICATION_LONG_PRESS_NAME;
import static com.android.systemui.statusbar.NotificationRemoteInputManager.ENABLE_REMOTE_INPUT;
import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
+import static com.android.systemui.statusbar.notification.NotificationUtils.logKey;
import android.util.Log;
import android.view.View;
@@ -247,7 +248,7 @@ public class ExpandableNotificationRowController implements NotifViewController
@Override
@NonNull
public String getNodeLabel() {
- return mView.getEntry().getKey();
+ return logKey(mView.getEntry());
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index d71e76801b3b..8f73b802271d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -58,7 +58,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
private ArrayList<View> mMatchParentViews = new ArrayList<View>();
private static Rect mClipRect = new Rect();
private boolean mWillBeGone;
- private int mMinClipTopAmount = 0;
private boolean mClipToActualHeight = true;
private boolean mChangingPosition = false;
private ViewGroup mTransientContainer;
@@ -187,10 +186,12 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
* @param notifyListeners Whether the listener should be informed about the change.
*/
public void setActualHeight(int actualHeight, boolean notifyListeners) {
- mActualHeight = actualHeight;
- updateClipping();
- if (notifyListeners) {
- notifyHeightChanged(false /* needsAnimation */);
+ if (mActualHeight != actualHeight) {
+ mActualHeight = actualHeight;
+ updateClipping();
+ if (notifyListeners) {
+ notifyHeightChanged(false /* needsAnimation */);
+ }
}
}
@@ -477,14 +478,6 @@ public abstract class ExpandableView extends FrameLayout implements Dumpable {
mWillBeGone = willBeGone;
}
- public int getMinClipTopAmount() {
- return mMinClipTopAmount;
- }
-
- public void setMinClipTopAmount(int minClipTopAmount) {
- mMinClipTopAmount = minClipTopAmount;
- }
-
@Override
public void setLayerType(int layerType, Paint paint) {
// Allow resetting the layerType to NONE regardless of overlappingRendering
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
index c66140822d92..99a24cb3e9ca 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridConversationNotificationView.java
@@ -67,6 +67,7 @@ public class HybridConversationNotificationView extends HybridNotificationView {
mConversationIconView = requireViewById(com.android.internal.R.id.conversation_icon);
mConversationFacePile = requireViewById(com.android.internal.R.id.conversation_face_pile);
mConversationSenderName = requireViewById(R.id.conversation_notification_sender);
+ applyTextColor(mConversationSenderName, mSecondaryTextColor);
mFacePileSize = getResources()
.getDimensionPixelSize(R.dimen.conversation_single_line_face_pile_size);
mFacePileAvatarSize = getResources()
@@ -75,6 +76,9 @@ public class HybridConversationNotificationView extends HybridNotificationView {
.getDimensionPixelSize(R.dimen.conversation_single_line_avatar_size);
mFacePileProtectionWidth = getResources().getDimensionPixelSize(
R.dimen.conversation_single_line_face_pile_protection_width);
+ mTransformationHelper.setCustomTransformation(
+ new FadeOutAndDownWithTitleTransformation(mConversationSenderName),
+ mConversationSenderName.getId());
mTransformationHelper.addViewTransformingToSimilar(mConversationIconView);
mTransformationHelper.addTransformedView(mConversationSenderName);
}
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 56f8e087d64d..77fd05186090 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
@@ -16,13 +16,14 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+
import android.annotation.Nullable;
import android.app.Notification;
import android.content.Context;
import android.content.res.Resources;
import android.service.notification.StatusBarNotification;
import android.util.TypedValue;
-import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -55,10 +56,8 @@ public class HybridGroupManager {
mOverflowNumberPadding = res.getDimensionPixelSize(R.dimen.group_overflow_number_padding);
}
- private HybridNotificationView inflateHybridViewWithStyle(int style,
- View contentView, ViewGroup parent) {
- LayoutInflater inflater = new ContextThemeWrapper(mContext, style)
- .getSystemService(LayoutInflater.class);
+ private HybridNotificationView inflateHybridView(View contentView, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(mContext);
int layout = contentView instanceof ConversationLayout
? R.layout.hybrid_conversation_notification
: R.layout.hybrid_notification;
@@ -91,16 +90,8 @@ public class HybridGroupManager {
public HybridNotificationView bindFromNotification(HybridNotificationView reusableView,
View contentView, StatusBarNotification notification,
ViewGroup parent) {
- return bindFromNotificationWithStyle(reusableView, contentView, notification,
- R.style.HybridNotification, parent);
- }
-
- private HybridNotificationView bindFromNotificationWithStyle(
- HybridNotificationView reusableView, View contentView,
- StatusBarNotification notification,
- int style, ViewGroup parent) {
if (reusableView == null) {
- reusableView = inflateHybridViewWithStyle(style, contentView, parent);
+ reusableView = inflateHybridView(contentView, parent);
}
CharSequence titleText = resolveTitle(notification.getNotification());
CharSequence contentText = resolveText(notification.getNotification());
@@ -136,8 +127,8 @@ public class HybridGroupManager {
if (!text.equals(reusableView.getText())) {
reusableView.setText(text);
}
- String contentDescription = String.format(mContext.getResources().getQuantityString(
- R.plurals.notification_group_overflow_description, number), number);
+ String contentDescription = icuMessageFormat(mContext.getResources(),
+ R.string.notification_group_overflow_description, number);
reusableView.setContentDescription(contentDescription);
reusableView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mOverflowNumberSize);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
index c0d85a6a16ef..fc9d9e8b736c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/HybridNotificationView.java
@@ -16,13 +16,18 @@
package com.android.systemui.statusbar.notification.row;
+import static android.app.Notification.COLOR_INVALID;
+
import android.annotation.Nullable;
import android.content.Context;
+import android.content.res.TypedArray;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.View;
import android.widget.TextView;
+import androidx.annotation.ColorInt;
+
import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
@@ -40,6 +45,8 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper();
protected TextView mTitleView;
protected TextView mTextView;
+ protected int mPrimaryTextColor = COLOR_INVALID;
+ protected int mSecondaryTextColor = COLOR_INVALID;
public HybridNotificationView(Context context) {
this(context, null);
@@ -69,42 +76,37 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
@Override
protected void onFinishInflate() {
super.onFinishInflate();
+ resolveThemeTextColors();
mTitleView = findViewById(R.id.notification_title);
mTextView = findViewById(R.id.notification_text);
+ applyTextColor(mTitleView, mPrimaryTextColor);
+ applyTextColor(mTextView, mSecondaryTextColor);
mTransformationHelper.setCustomTransformation(
- new ViewTransformationHelper.CustomTransformation() {
- @Override
- public boolean transformTo(TransformState ownState, TransformableView notification,
- float transformationAmount) {
- // We want to transform to the same y location as the title
- TransformState otherState = notification.getCurrentState(
- TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeOut(mTextView, transformationAmount);
- if (otherState != null) {
- ownState.transformViewVerticalTo(otherState, transformationAmount);
- otherState.recycle();
- }
- return true;
- }
-
- @Override
- public boolean transformFrom(TransformState ownState,
- TransformableView notification, float transformationAmount) {
- // We want to transform from the same y location as the title
- TransformState otherState = notification.getCurrentState(
- TRANSFORMING_VIEW_TITLE);
- CrossFadeHelper.fadeIn(mTextView, transformationAmount, true /* remap */);
- if (otherState != null) {
- ownState.transformViewVerticalFrom(otherState, transformationAmount);
- otherState.recycle();
- }
- return true;
- }
- }, TRANSFORMING_VIEW_TEXT);
+ new FadeOutAndDownWithTitleTransformation(mTextView),
+ TRANSFORMING_VIEW_TEXT);
mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TITLE, mTitleView);
mTransformationHelper.addTransformedView(TRANSFORMING_VIEW_TEXT, mTextView);
}
+ protected void applyTextColor(TextView textView, @ColorInt int textColor) {
+ if (textColor != COLOR_INVALID) {
+ textView.setTextColor(textColor);
+ }
+ }
+
+ private void resolveThemeTextColors() {
+ try (TypedArray ta = mContext.getTheme().obtainStyledAttributes(
+ android.R.style.Theme_DeviceDefault_DayNight, new int[]{
+ android.R.attr.textColorPrimary,
+ android.R.attr.textColorSecondary
+ })) {
+ if (ta != null) {
+ mPrimaryTextColor = ta.getColor(0, mPrimaryTextColor);
+ mSecondaryTextColor = ta.getColor(1, mSecondaryTextColor);
+ }
+ }
+ }
+
public void bind(@Nullable CharSequence title, @Nullable CharSequence text,
@Nullable View contentView) {
mTitleView.setText(title);
@@ -152,4 +154,40 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
@Override
public void setNotificationFaded(boolean faded) {}
+
+ protected static class FadeOutAndDownWithTitleTransformation extends
+ ViewTransformationHelper.CustomTransformation {
+
+ private final View mView;
+
+ public FadeOutAndDownWithTitleTransformation(View view) {
+ mView = view;
+ }
+
+ @Override
+ public boolean transformTo(TransformState ownState, TransformableView notification,
+ float transformationAmount) {
+ // We want to transform to the same y location as the title
+ TransformState otherState = notification.getCurrentState(TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeOut(mView, transformationAmount);
+ if (otherState != null) {
+ ownState.transformViewVerticalTo(otherState, transformationAmount);
+ otherState.recycle();
+ }
+ return true;
+ }
+
+ @Override
+ public boolean transformFrom(TransformState ownState,
+ TransformableView notification, float transformationAmount) {
+ // We want to transform from the same y location as the title
+ TransformState otherState = notification.getCurrentState(TRANSFORMING_VIEW_TITLE);
+ CrossFadeHelper.fadeIn(mView, transformationAmount, true /* remap */);
+ if (otherState != null) {
+ ownState.transformViewVerticalFrom(otherState, transformationAmount);
+ otherState.recycle();
+ }
+ return true;
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
index f693ebbb7830..ea564ddb9193 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -112,7 +112,8 @@ public final class NotifBindPipeline {
public void manageRow(
@NonNull NotificationEntry entry,
@NonNull ExpandableNotificationRow row) {
- mLogger.logManagedRow(entry.getKey());
+ mLogger.logManagedRow(entry);
+ mLogger.logManagedRow(entry);
final BindEntry bindEntry = getBindEntry(entry);
if (bindEntry == null) {
@@ -154,12 +155,12 @@ public final class NotifBindPipeline {
* the real work once rather than repeatedly start and cancel it.
*/
private void requestPipelineRun(NotificationEntry entry) {
- mLogger.logRequestPipelineRun(entry.getKey());
+ mLogger.logRequestPipelineRun(entry);
final BindEntry bindEntry = getBindEntry(entry);
if (bindEntry.row == null) {
// Row is not managed yet but may be soon. Stop for now.
- mLogger.logRequestPipelineRowNotSet(entry.getKey());
+ mLogger.logRequestPipelineRowNotSet(entry);
return;
}
@@ -177,7 +178,7 @@ public final class NotifBindPipeline {
* callbacks when the run finishes. If a run is already in progress, it is restarted.
*/
private void startPipeline(NotificationEntry entry) {
- mLogger.logStartPipeline(entry.getKey());
+ mLogger.logStartPipeline(entry);
if (mStage == null) {
throw new IllegalStateException("No stage was ever set on the pipeline");
@@ -193,7 +194,7 @@ public final class NotifBindPipeline {
final BindEntry bindEntry = getBindEntry(entry);
final Set<BindCallback> callbacks = bindEntry.callbacks;
- mLogger.logFinishedPipeline(entry.getKey(), callbacks.size());
+ mLogger.logFinishedPipeline(entry, callbacks.size());
bindEntry.invalidated = false;
// Move all callbacks to separate list as callbacks may themselves add/remove callbacks.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
index ec406f0524ff..ab91926d466a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineLogger.kt
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class NotifBindPipelineLogger @Inject constructor(
@@ -32,41 +34,41 @@ class NotifBindPipelineLogger @Inject constructor(
})
}
- fun logManagedRow(notifKey: String) {
+ fun logManagedRow(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Row set for notif: $str1"
})
}
- fun logRequestPipelineRun(notifKey: String) {
+ fun logRequestPipelineRun(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Request pipeline run for notif: $str1"
})
}
- fun logRequestPipelineRowNotSet(notifKey: String) {
+ fun logRequestPipelineRowNotSet(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Row is not set so pipeline will not run. notif = $str1"
})
}
- fun logStartPipeline(notifKey: String) {
+ fun logStartPipeline(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
}, {
"Start pipeline for notif: $str1"
})
}
- fun logFinishedPipeline(notifKey: String, numCallbacks: Int) {
+ fun logFinishedPipeline(entry: NotificationEntry, numCallbacks: Int) {
buffer.log(TAG, INFO, {
- str1 = notifKey
+ str1 = entry.logKey
int1 = numCallbacks
}, {
"Finished pipeline for notif $str1 with $int1 callbacks"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
index 134f24e7e646..27aa4b38e0ba 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationConversationInfo.java
@@ -266,9 +266,14 @@ public class NotificationConversationInfo extends LinearLayout implements
snooze.setOnClickListener(mOnSnoozeClick);
*/
- if (mAppBubble == BUBBLE_PREFERENCE_ALL) {
- ((TextView) findViewById(R.id.default_summary)).setText(getResources().getString(
+ TextView defaultSummaryTextView = findViewById(R.id.default_summary);
+ if (mAppBubble == BUBBLE_PREFERENCE_ALL
+ && BubblesManager.areBubblesEnabled(mContext, mSbn.getUser())) {
+ defaultSummaryTextView.setText(getResources().getString(
R.string.notification_channel_summary_default_with_bubbles, mAppName));
+ } else {
+ defaultSummaryTextView.setText(getResources().getString(
+ R.string.notification_channel_summary_default));
}
findViewById(R.id.priority).setOnClickListener(mOnFavoriteClick);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
index 7269f5545163..512b04968166 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.notification.row;
+import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
+
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
@@ -243,11 +245,11 @@ public class NotificationSnooze extends LinearLayout
private SnoozeOption createOption(int minutes, int accessibilityActionId) {
Resources res = getResources();
boolean showInHours = minutes >= 60;
- int pluralResId = showInHours
- ? R.plurals.snoozeHourOptions
- : R.plurals.snoozeMinuteOptions;
+ int stringResId = showInHours
+ ? R.string.snoozeHourOptions
+ : R.string.snoozeMinuteOptions;
int count = showInHours ? (minutes / 60) : minutes;
- String description = res.getQuantityString(pluralResId, count, count);
+ String description = icuMessageFormat(res, stringResId, count);
String resultText = String.format(res.getString(R.string.snoozed_for_time), description);
AccessibilityAction action = new AccessibilityAction(accessibilityActionId, description);
final int index = resultText.indexOf(description);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
index 3616f8faee1e..81cf14646465 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -57,7 +57,7 @@ public class RowContentBindStage extends BindStage<RowContentBindParams> {
@NonNull StageCallback callback) {
RowContentBindParams params = getStageParams(entry);
- mLogger.logStageParams(entry.getKey(), params.toString());
+ mLogger.logStageParams(entry, params);
// Resolve content to bind/unbind.
@InflationFlag int inflationFlags = params.getContentViews();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
index 29cce3375c8a..f9923b2254d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStageLogger.kt
@@ -19,17 +19,19 @@ package com.android.systemui.statusbar.notification.row
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class RowContentBindStageLogger @Inject constructor(
@NotificationLog private val buffer: LogBuffer
) {
- fun logStageParams(notifKey: String, stageParams: String) {
+ fun logStageParams(entry: NotificationEntry, stageParams: RowContentBindParams) {
buffer.log(TAG, INFO, {
- str1 = notifKey
- str2 = stageParams
+ str1 = entry.logKey
+ str2 = stageParams.toString()
}, {
- "Invalidated notif $str1 with params: \n$str2"
+ "Invalidated notif $str1 with params: $str2"
})
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 7414bdc13672..5aaf63f8d32d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -74,11 +74,6 @@ public abstract class StackScrollerDecorView extends ExpandableView {
mSecondaryView = findSecondaryView();
setVisible(false /* nowVisible */, false /* animate */);
setSecondaryVisible(false /* nowVisible */, false /* animate */);
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
setOutlineProvider(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 a552f999aeb4..a76f0827fc18 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
@@ -1312,6 +1312,7 @@ public class NotificationChildrenContainer extends ViewGroup
}
float bottomRoundness = last ? currentBottomRoundness : 0.0f;
child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+ child.setTopRoundness(0.0f, false /* animate */);
last = false;
}
}
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 e2ed1d83530b..defae5b6941f 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
@@ -17,8 +17,11 @@
package com.android.systemui.statusbar.notification.stack;
import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING;
+import static com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_CLEAR_ALL;
import static com.android.systemui.statusbar.notification.stack.NotificationPriorityBucketKt.BUCKET_SILENT;
import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_SWIPE;
+import static com.android.systemui.util.DumpUtilsKt.println;
+import static com.android.systemui.util.DumpUtilsKt.visibilityString;
import static java.lang.annotation.RetentionPolicy.SOURCE;
@@ -746,19 +749,20 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
}
}
- private void logHunSkippedForUnexpectedState(String key, boolean expected, boolean actual) {
+ private void logHunSkippedForUnexpectedState(ExpandableNotificationRow enr,
+ boolean expected, boolean actual) {
if (mLogger == null) return;
- mLogger.hunSkippedForUnexpectedState(key, expected, actual);
+ mLogger.hunSkippedForUnexpectedState(enr.getEntry(), expected, actual);
}
- private void logHunAnimationSkipped(String key, String reason) {
+ private void logHunAnimationSkipped(ExpandableNotificationRow enr, String reason) {
if (mLogger == null) return;
- mLogger.hunAnimationSkipped(key, reason);
+ mLogger.hunAnimationSkipped(enr.getEntry(), reason);
}
- private void logHunAnimationEventAdded(String key, int type) {
+ private void logHunAnimationEventAdded(ExpandableNotificationRow enr, int type) {
if (mLogger == null) return;
- mLogger.hunAnimationEventAdded(key, type);
+ mLogger.hunAnimationEventAdded(enr.getEntry(), type);
}
private void onDrawDebug(Canvas canvas) {
@@ -1386,12 +1390,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
*/
@ShadeViewRefactor(RefactorComponent.COORDINATOR)
public void setExpandedHeight(float height) {
- final float shadeBottom = getHeight() - getEmptyBottomMargin();
final boolean skipHeightUpdate = shouldSkipHeightUpdate();
- if (!skipHeightUpdate) {
- final float expansionFraction = MathUtils.saturate(height / shadeBottom);
- mAmbientState.setExpansionFraction(expansionFraction);
- }
updateStackPosition();
if (!skipHeightUpdate) {
@@ -3177,7 +3176,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (isHeadsUp != row.isHeadsUp()) {
// For cases where we have a heads up showing and appearing again we shouldn't
// do the animations at all.
- logHunSkippedForUnexpectedState(key, isHeadsUp, row.isHeadsUp());
+ logHunSkippedForUnexpectedState(row, isHeadsUp, row.isHeadsUp());
continue;
}
int type = AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER;
@@ -3195,7 +3194,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (row.isChildInGroup()) {
// We can otherwise get stuck in there if it was just isolated
row.setHeadsUpAnimatingAway(false);
- logHunAnimationSkipped(key, "row is child in group");
+ logHunAnimationSkipped(row, "row is child in group");
continue;
}
} else {
@@ -3203,7 +3202,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (viewState == null) {
// A view state was never generated for this view, so we don't need to animate
// this. This may happen with notification children.
- logHunAnimationSkipped(key, "row has no viewState");
+ logHunAnimationSkipped(row, "row has no viewState");
continue;
}
if (isHeadsUp && (mAddedHeadsUpChildren.contains(row) || pinnedAndClosed)) {
@@ -3227,7 +3226,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
+ " onBottom=" + onBottom
+ " row=" + row.getEntry().getKey());
}
- logHunAnimationEventAdded(key, type);
+ logHunAnimationEventAdded(row, type);
}
mHeadsUpChangeAnimations.clear();
mAddedHeadsUpChildren.clear();
@@ -4363,8 +4362,6 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
/**
* Update colors of "dismiss" and "empty shade" views.
- *
- * @param lightTheme True if light theme should be used.
*/
@ShadeViewRefactor(RefactorComponent.DECORATOR)
void updateDecorViews() {
@@ -4558,31 +4555,12 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
mClearAllInProgress = clearAllInProgress;
mAmbientState.setClearAllInProgress(clearAllInProgress);
mController.getNoticationRoundessManager().setClearAllInProgress(clearAllInProgress);
- handleClearAllClipping();
}
boolean getClearAllInProgress() {
return mClearAllInProgress;
}
- @ShadeViewRefactor(RefactorComponent.ADAPTER)
- private void handleClearAllClipping() {
- final int count = getChildCount();
- boolean previousChildWillBeDismissed = false;
- for (int i = 0; i < count; i++) {
- ExpandableView child = (ExpandableView) getChildAt(i);
- if (child.getVisibility() == GONE) {
- continue;
- }
- if (mClearAllInProgress && previousChildWillBeDismissed) {
- child.setMinClipTopAmount(child.getClipTopAmount());
- } else {
- child.setMinClipTopAmount(0);
- }
- previousChildWillBeDismissed = canChildBeCleared(child);
- }
- }
-
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public boolean isFooterViewNotGone() {
return mFooterView != null
@@ -4799,8 +4777,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
if (SPEW) {
Log.v(TAG, "generateHeadsUpAnimation: previous hun appear animation cancelled");
}
- logHunAnimationSkipped(row.getEntry().getKey(),
- "previous hun appear animation cancelled");
+ logHunAnimationSkipped(row, "previous hun appear animation cancelled");
return;
}
mHeadsUpChangeAnimations.add(new Pair<>(row, isHeadsUp));
@@ -5079,30 +5056,31 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
@ShadeViewRefactor(RefactorComponent.SHADE_VIEW)
public void dump(PrintWriter pwOriginal, String[] args) {
IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
- StringBuilder sb = new StringBuilder("[")
- .append(this.getClass().getSimpleName()).append(":")
- .append(" pulsing=").append(mPulsing ? "T" : "f")
- .append(" expanded=").append(mIsExpanded ? "T" : "f")
- .append(" headsUpPinned=").append(mInHeadsUpPinnedMode ? "T" : "f")
- .append(" qsClipping=").append(mShouldUseRoundedRectClipping ? "T" : "f")
- .append(" qsClipDismiss=").append(mDismissUsingRowTranslationX ? "T" : "f")
- .append(" visibility=").append(DumpUtilsKt.visibilityString(getVisibility()))
- .append(" alpha=").append(getAlpha())
- .append(" scrollY=").append(mAmbientState.getScrollY())
- .append(" maxTopPadding=").append(mMaxTopPadding)
- .append(" showShelfOnly=").append(mShouldShowShelfOnly ? "T" : "f")
- .append(" qsExpandFraction=").append(mQsExpansionFraction)
- .append(" isCurrentUserSetup=").append(mIsCurrentUserSetup)
- .append(" hideAmount=").append(mAmbientState.getHideAmount())
- .append(" ambientStateSwipingUp=").append(mAmbientState.isSwipingUp())
- .append(" maxDisplayedNotifications=").append(mMaxDisplayedNotifications)
- .append(" intrinsicContentHeight=").append(mIntrinsicContentHeight)
- .append(" contentHeight=").append(mContentHeight)
- .append(" intrinsicPadding=").append(mIntrinsicPadding)
- .append(" topPadding=").append(mTopPadding)
- .append(" bottomPadding=").append(mBottomPadding)
- .append("]");
- pw.println(sb.toString());
+ pw.println("Internal state:");
+ DumpUtilsKt.withIncreasedIndent(pw, () -> {
+ println(pw, "pulsing", mPulsing);
+ println(pw, "expanded", mIsExpanded);
+ println(pw, "headsUpPinned", mInHeadsUpPinnedMode);
+ println(pw, "qsClipping", mShouldUseRoundedRectClipping);
+ println(pw, "qsClipDismiss", mDismissUsingRowTranslationX);
+ println(pw, "visibility", visibilityString(getVisibility()));
+ println(pw, "alpha", getAlpha());
+ println(pw, "scrollY", mAmbientState.getScrollY());
+ println(pw, "maxTopPadding", mMaxTopPadding);
+ println(pw, "showShelfOnly", mShouldShowShelfOnly);
+ println(pw, "qsExpandFraction", mQsExpansionFraction);
+ println(pw, "isCurrentUserSetup", mIsCurrentUserSetup);
+ println(pw, "hideAmount", mAmbientState.getHideAmount());
+ println(pw, "ambientStateSwipingUp", mAmbientState.isSwipingUp());
+ println(pw, "maxDisplayedNotifications", mMaxDisplayedNotifications);
+ println(pw, "intrinsicContentHeight", mIntrinsicContentHeight);
+ println(pw, "contentHeight", mContentHeight);
+ println(pw, "intrinsicPadding", mIntrinsicPadding);
+ println(pw, "topPadding", mTopPadding);
+ println(pw, "bottomPadding", mBottomPadding);
+ });
+ pw.println();
+ pw.println("Contents:");
DumpUtilsKt.withIncreasedIndent(pw, () -> {
int childCount = getChildCount();
pw.println("Number of children: " + childCount);
@@ -5269,6 +5247,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
setClearAllInProgress(true);
mShadeNeedsToClose = closeShade;
+ InteractionJankMonitor.getInstance().begin(this, CUJ_SHADE_CLEAR_ALL);
// Decrease the delay for every row we animate to give the sense of
// accelerating the swipes
final int rowDelayDecrement = 5;
@@ -6181,6 +6160,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable
private void onClearAllAnimationsEnd(
List<ExpandableNotificationRow> viewsToRemove,
@SelectedRows int selectedRows) {
+ InteractionJankMonitor.getInstance().end(CUJ_SHADE_CLEAR_ALL);
if (mClearAllAnimationListener != null) {
mClearAllAnimationListener.onAnimationEnd(viewsToRemove, selectedRows);
}
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 010e6cf90817..85f1689714a3 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
@@ -28,7 +28,6 @@ import static com.android.systemui.statusbar.notification.stack.NotificationStac
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_GENTLE;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_HIGH_PRIORITY;
import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.SelectedRows;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.canChildBeCleared;
import static com.android.systemui.statusbar.phone.NotificationIconAreaController.HIGH_PRIORITY;
import android.content.res.Configuration;
@@ -38,12 +37,10 @@ import android.graphics.PointF;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
-import android.service.notification.NotificationListenerService;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Pair;
import android.view.Display;
-import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -53,27 +50,26 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.colorextraction.ColorExtractor;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.UiEvent;
import com.android.internal.logging.UiEventLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.ExpandHelper;
import com.android.systemui.Gefingerpoken;
import com.android.systemui.R;
import com.android.systemui.SwipeHelper;
import com.android.systemui.classifier.Classifier;
import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -84,9 +80,7 @@ import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.LaunchAnimationParameters;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -117,7 +111,6 @@ import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -156,18 +149,16 @@ public class NotificationStackScrollLayoutController {
private final ConfigurationController mConfigurationController;
private final ZenModeController mZenModeController;
private final MetricsLogger mMetricsLogger;
+ private final DumpManager mDumpManager;
private final FalsingCollector mFalsingCollector;
private final FalsingManager mFalsingManager;
private final Resources mResources;
private final NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
private final ScrimController mScrimController;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final NotifPipeline mNotifPipeline;
private final NotifCollection mNotifCollection;
private final NotificationEntryManager mNotificationEntryManager;
- private final IStatusBarService mIStatusBarService;
private final UiEventLogger mUiEventLogger;
- private final LayoutInflater mLayoutInflater;
private final NotificationRemoteInputManager mRemoteInputManager;
private final VisualStabilityManager mVisualStabilityManager;
private final ShadeController mShadeController;
@@ -204,8 +195,6 @@ public class NotificationStackScrollLayoutController {
@Nullable
private NotificationActivityStarter mNotificationActivityStarter;
- private ColorExtractor.OnColorsChangedListener mOnColorsChangedListener;
-
@VisibleForTesting
final View.OnAttachStateChangeListener mOnAttachStateChangeListener =
new View.OnAttachStateChangeListener() {
@@ -254,10 +243,7 @@ public class NotificationStackScrollLayoutController {
mView.setAnimateBottomOnLayout(true);
}
// Let's update the footer once the notifications have been updated (in the next frame)
- mView.post(() -> {
- updateFooter();
- updateSectionBoundaries("dynamic privacy changed");
- });
+ mView.post(this::updateFooter);
};
@VisibleForTesting
@@ -339,6 +325,13 @@ public class NotificationStackScrollLayoutController {
};
/**
+ * Recalculate sensitiveness without animation; called when waking up while keyguard occluded.
+ */
+ public void updateSensitivenessForOccludedWakeup() {
+ mView.updateSensitiveness(false, mLockscreenUserManager.isAnyProfilePublicMode());
+ }
+
+ /**
* Set the overexpansion of the panel to be applied to the view.
*/
public void setOverExpansion(float overExpansion) {
@@ -632,9 +625,9 @@ public class NotificationStackScrollLayoutController {
KeyguardMediaController keyguardMediaController,
KeyguardBypassController keyguardBypassController,
ZenModeController zenModeController,
- SysuiColorExtractor colorExtractor,
NotificationLockscreenUserManager lockscreenUserManager,
MetricsLogger metricsLogger,
+ DumpManager dumpManager,
FalsingCollector falsingCollector,
FalsingManager falsingManager,
@Main Resources resources,
@@ -644,15 +637,12 @@ public class NotificationStackScrollLayoutController {
NotificationGroupManagerLegacy legacyGroupManager,
GroupExpansionManager groupManager,
@SilentHeader SectionHeaderController silentHeaderController,
- NotifPipelineFlags notifPipelineFlags,
NotifPipeline notifPipeline,
NotifCollection notifCollection,
NotificationEntryManager notificationEntryManager,
LockscreenShadeTransitionController lockscreenShadeTransitionController,
ShadeTransitionController shadeTransitionController,
- IStatusBarService iStatusBarService,
UiEventLogger uiEventLogger,
- LayoutInflater layoutInflater,
NotificationRemoteInputManager remoteInputManager,
VisualStabilityManager visualStabilityManager,
ShadeController shadeController,
@@ -677,6 +667,7 @@ public class NotificationStackScrollLayoutController {
mZenModeController = zenModeController;
mLockscreenUserManager = lockscreenUserManager;
mMetricsLogger = metricsLogger;
+ mDumpManager = dumpManager;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mShadeTransitionController = shadeTransitionController;
mFalsingCollector = falsingCollector;
@@ -694,14 +685,11 @@ public class NotificationStackScrollLayoutController {
mCentralSurfaces.requestNotificationUpdate("onGroupsChanged");
}
});
- mNotifPipelineFlags = notifPipelineFlags;
mSilentHeaderController = silentHeaderController;
mNotifPipeline = notifPipeline;
mNotifCollection = notifCollection;
mNotificationEntryManager = notificationEntryManager;
- mIStatusBarService = iStatusBarService;
mUiEventLogger = uiEventLogger;
- mLayoutInflater = layoutInflater;
mRemoteInputManager = remoteInputManager;
mVisualStabilityManager = visualStabilityManager;
mShadeController = shadeController;
@@ -728,6 +716,7 @@ public class NotificationStackScrollLayoutController {
}
});
mView.setShadeController(mShadeController);
+ mDumpManager.registerDumpable(mView);
mKeyguardBypassController.registerOnBypassStateChangedListener(
isEnabled -> mNotificationRoundnessManager.setShouldRoundPulsingViews(!isEnabled));
@@ -740,21 +729,12 @@ public class NotificationStackScrollLayoutController {
.setOnMenuEventListener(mMenuEventListener)
.build();
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryUpdated(NotificationEntry entry) {
- mView.onEntryUpdated(entry);
- }
- });
- } else {
- mNotificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- mView.onEntryUpdated(entry);
- }
- });
- }
+ mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryUpdated(NotificationEntry entry) {
+ mView.onEntryUpdated(entry);
+ }
+ });
mView.initView(mView.getContext(), mSwipeHelper, mNotificationStackSizeCalculator);
mView.setKeyguardBypassEnabled(mKeyguardBypassController.getBypassEnabled());
@@ -1226,10 +1206,6 @@ public class NotificationStackScrollLayoutController {
Trace.endSection();
}
- public boolean areNotificationsHiddenInShade() {
- return mZenModeController.areNotificationsHiddenInShade();
- }
-
public boolean isShowingEmptyShadeView() {
return mShowEmptyShadeView;
}
@@ -1334,15 +1310,6 @@ public class NotificationStackScrollLayoutController {
};
}
- public void updateSectionBoundaries(String reason) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- return;
- }
- Trace.beginSection("NSSLC.updateSectionBoundaries");
- mView.updateSectionBoundaries(reason);
- Trace.endSection();
- }
-
public void updateFooter() {
Trace.beginSection("NSSLC.updateFooter");
mView.updateFooter();
@@ -1458,39 +1425,18 @@ public class NotificationStackScrollLayoutController {
private void onAnimationEnd(List<ExpandableNotificationRow> viewsToRemove,
@SelectedRows int selectedRows) {
- if (mNotifPipelineFlags.isNewPipelineEnabled()) {
- if (selectedRows == ROWS_ALL) {
- mNotifCollection.dismissAllNotifications(
- mLockscreenUserManager.getCurrentUserId());
- } else {
- final List<Pair<NotificationEntry, DismissedByUserStats>>
- entriesWithRowsDismissedFromShade = new ArrayList<>();
- for (ExpandableNotificationRow row : viewsToRemove) {
- final NotificationEntry entry = row.getEntry();
- entriesWithRowsDismissedFromShade.add(
- new Pair<>(entry, getDismissedByUserStats(entry)));
- }
- mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
- }
+ if (selectedRows == ROWS_ALL) {
+ mNotifCollection.dismissAllNotifications(
+ mLockscreenUserManager.getCurrentUserId());
} else {
- for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
- if (canChildBeCleared(rowToRemove)) {
- mNotificationEntryManager.performRemoveNotification(
- rowToRemove.getEntry().getSbn(),
- getDismissedByUserStats(rowToRemove.getEntry()),
- NotificationListenerService.REASON_CANCEL_ALL);
- } else {
- rowToRemove.resetTranslation();
- }
- }
- if (selectedRows == ROWS_ALL) {
- try {
- // TODO(b/169585328): Do not clear media player notifications
- mIStatusBarService.onClearAllNotifications(
- mLockscreenUserManager.getCurrentUserId());
- } catch (Exception ignored) {
- }
+ final List<Pair<NotificationEntry, DismissedByUserStats>>
+ entriesWithRowsDismissedFromShade = new ArrayList<>();
+ for (ExpandableNotificationRow row : viewsToRemove) {
+ final NotificationEntry entry = row.getEntry();
+ entriesWithRowsDismissedFromShade.add(
+ new Pair<>(entry, getDismissedByUserStats(entry)));
}
+ mNotifCollection.dismissNotifications(entriesWithRowsDismissedFromShade);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 04bf62104f66..5f79c0e3913a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -3,21 +3,27 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.dagger.NotificationHeadsUpLog
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.*
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_ADD
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_APPEAR
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
import javax.inject.Inject
class NotificationStackScrollLogger @Inject constructor(
@NotificationHeadsUpLog private val buffer: LogBuffer
) {
- fun hunAnimationSkipped(key: String, reason: String) {
+ fun hunAnimationSkipped(entry: NotificationEntry, reason: String) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"heads up animation skipped: key: $str1 reason: $str2"
})
}
- fun hunAnimationEventAdded(key: String, type: Int) {
+ fun hunAnimationEventAdded(entry: NotificationEntry, type: Int) {
val reason: String
reason = if (type == ANIMATION_TYPE_HEADS_UP_DISAPPEAR) {
"HEADS_UP_DISAPPEAR"
@@ -33,16 +39,16 @@ class NotificationStackScrollLogger @Inject constructor(
type.toString()
}
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
str2 = reason
}, {
"heads up animation added: $str1 with type $str2"
})
}
- fun hunSkippedForUnexpectedState(key: String, expected: Boolean, actual: Boolean) {
+ fun hunSkippedForUnexpectedState(entry: NotificationEntry, expected: Boolean, actual: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
bool1 = expected
bool2 = actual
}, {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index 4013254c6592..6d513d0da5c1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -24,8 +24,7 @@ import android.util.MathUtils;
import android.view.View;
import android.view.ViewGroup;
-import androidx.annotation.VisibleForTesting;
-
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.policy.SystemBarUtils;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
@@ -65,6 +64,9 @@ public class StackScrollAlgorithm {
private int mPinnedZTranslationExtra;
private float mNotificationScrimPadding;
private int mMarginBottom;
+ private float mQuickQsOffsetHeight;
+ private float mSmallCornerRadius;
+ private float mLargeCornerRadius;
public StackScrollAlgorithm(
Context context,
@@ -74,10 +76,10 @@ public class StackScrollAlgorithm {
}
public void initView(Context context) {
- initConstants(context);
+ updateResources(context);
}
- private void initConstants(Context context) {
+ private void updateResources(Context context) {
Resources res = context.getResources();
mPaddingBetweenElements = res.getDimensionPixelSize(
R.dimen.notification_divider_height);
@@ -93,6 +95,9 @@ public class StackScrollAlgorithm {
R.dimen.notification_section_divider_height_lockscreen);
mNotificationScrimPadding = res.getDimensionPixelSize(R.dimen.notification_side_paddings);
mMarginBottom = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+ mQuickQsOffsetHeight = SystemBarUtils.getQuickQsOffsetHeight(context);
+ mSmallCornerRadius = res.getDimension(R.dimen.notification_corner_radius_small);
+ mLargeCornerRadius = res.getDimension(R.dimen.notification_corner_radius);
}
/**
@@ -441,6 +446,15 @@ public class StackScrollAlgorithm {
return false;
}
+ @VisibleForTesting
+ void maybeUpdateHeadsUpIsVisible(ExpandableViewState viewState, boolean isShadeExpanded,
+ boolean mustStayOnScreen, boolean topVisible, float viewEnd, float hunMax) {
+
+ if (isShadeExpanded && mustStayOnScreen && topVisible) {
+ viewState.headsUpIsVisible = viewEnd < hunMax;
+ }
+ }
+
// TODO(b/172289889) polish shade open from HUN
/**
* Populates the {@link ExpandableViewState} for a single child.
@@ -474,14 +488,6 @@ public class StackScrollAlgorithm {
: ShadeInterpolation.getContentAlpha(expansion);
}
- if (ambientState.isShadeExpanded() && view.mustStayOnScreen()
- && viewState.yTranslation >= 0) {
- // Even if we're not scrolled away we're in view and we're also not in the
- // shelf. We can relax the constraints and let us scroll off the top!
- float end = viewState.yTranslation + viewState.height + ambientState.getStackY();
- viewState.headsUpIsVisible = end < ambientState.getMaxHeadsUpTranslation();
- }
-
final float expansionFraction = getExpansionFractionWithoutShelf(
algorithmState, ambientState);
@@ -497,8 +503,15 @@ public class StackScrollAlgorithm {
algorithmState.mCurrentExpandedYPosition += gap;
}
+ // Must set viewState.yTranslation _before_ use.
+ // Incoming views have yTranslation=0 by default.
viewState.yTranslation = algorithmState.mCurrentYPosition;
+ maybeUpdateHeadsUpIsVisible(viewState, ambientState.isShadeExpanded(),
+ view.mustStayOnScreen(), /* topVisible */ viewState.yTranslation >= 0,
+ /* viewEnd */ viewState.yTranslation + viewState.height + ambientState.getStackY(),
+ /* hunMax */ ambientState.getMaxHeadsUpTranslation()
+ );
if (view instanceof FooterView) {
final boolean shadeClosed = !ambientState.isShadeExpanded();
final boolean isShelfShowing = algorithmState.firstViewInShelf != null;
@@ -682,7 +695,8 @@ public class StackScrollAlgorithm {
if (row.mustStayOnScreen() && !childState.headsUpIsVisible
&& !row.showingPulsing()) {
// Ensure that the heads up is always visible even when scrolled off
- clampHunToTop(ambientState, row, childState);
+ clampHunToTop(mQuickQsOffsetHeight, ambientState.getStackTranslation(),
+ row.getCollapsedHeight(), childState);
if (isTopEntry && row.isAboveShelf()) {
// the first hun can't get off screen.
clampHunToMaxTranslation(ambientState, row, childState);
@@ -719,27 +733,62 @@ public class StackScrollAlgorithm {
}
}
- private void clampHunToTop(AmbientState ambientState, ExpandableNotificationRow row,
- ExpandableViewState childState) {
- float newTranslation = Math.max(ambientState.getTopPadding()
- + ambientState.getStackTranslation(), childState.yTranslation);
- childState.height = (int) Math.max(childState.height - (newTranslation
- - childState.yTranslation), row.getCollapsedHeight());
- childState.yTranslation = newTranslation;
+ /**
+ * When shade is open and we are scrolled to the bottom of notifications,
+ * clamp incoming HUN in its collapsed form, right below qs offset.
+ * Transition pinned collapsed HUN to full height when scrolling back up.
+ */
+ @VisibleForTesting
+ void clampHunToTop(float quickQsOffsetHeight, float stackTranslation, float collapsedHeight,
+ ExpandableViewState viewState) {
+
+ final float newTranslation = Math.max(quickQsOffsetHeight + stackTranslation,
+ viewState.yTranslation);
+
+ // Transition from collapsed pinned state to fully expanded state
+ // when the pinned HUN approaches its actual location (when scrolling back to top).
+ final float distToRealY = newTranslation - viewState.yTranslation;
+ viewState.height = (int) Math.max(viewState.height - distToRealY, collapsedHeight);
+ viewState.yTranslation = newTranslation;
}
+ // Pin HUN to bottom of expanded QS
+ // while the rest of notifications are scrolled offscreen.
private void clampHunToMaxTranslation(AmbientState ambientState, ExpandableNotificationRow row,
ExpandableViewState childState) {
- float newTranslation;
float maxHeadsUpTranslation = ambientState.getMaxHeadsUpTranslation();
- float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ final float maxShelfPosition = ambientState.getInnerHeight() + ambientState.getTopPadding()
+ ambientState.getStackTranslation();
maxHeadsUpTranslation = Math.min(maxHeadsUpTranslation, maxShelfPosition);
- float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
- newTranslation = Math.min(childState.yTranslation, bottomPosition);
+
+ final float bottomPosition = maxHeadsUpTranslation - row.getCollapsedHeight();
+ final float newTranslation = Math.min(childState.yTranslation, bottomPosition);
childState.height = (int) Math.min(childState.height, maxHeadsUpTranslation
- newTranslation);
childState.yTranslation = newTranslation;
+
+ // Animate pinned HUN bottom corners to and from original roundness.
+ final float originalCornerRadius =
+ row.isLastInSection() ? 1f : (mSmallCornerRadius / mLargeCornerRadius);
+ final float roundness = computeCornerRoundnessForPinnedHun(mHostView.getHeight(),
+ ambientState.getStackY(), getMaxAllowedChildHeight(row), originalCornerRadius);
+ row.setBottomRoundness(roundness, /* animate= */ false);
+ }
+
+ @VisibleForTesting
+ float computeCornerRoundnessForPinnedHun(float hostViewHeight, float stackY,
+ float viewMaxHeight, float originalCornerRadius) {
+
+ // Compute y where corner roundness should be in its original unpinned state.
+ // We use view max height because the pinned collapsed HUN expands to max height
+ // when it becomes unpinned.
+ final float originalRoundnessY = hostViewHeight - viewMaxHeight;
+
+ final float distToOriginalRoundness = Math.max(0f, stackY - originalRoundnessY);
+ final float progressToPinnedRoundness = Math.min(1f,
+ distToOriginalRoundness / viewMaxHeight);
+
+ return MathUtils.lerp(originalCornerRadius, 1f, progressToPinnedRoundness);
}
protected int getMaxAllowedChildHeight(View child) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
index 7c9df426aba6..174bf4c27de8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateAnimator.java
@@ -25,6 +25,7 @@ import android.view.View;
import com.android.keyguard.KeyguardSliceView;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -52,7 +53,8 @@ public class StackStateAnimator {
public static final int ANIMATION_DURATION_CLOSE_REMOTE_INPUT = 150;
public static final int ANIMATION_DURATION_HEADS_UP_APPEAR = 400;
public static final int ANIMATION_DURATION_HEADS_UP_DISAPPEAR = 400;
- public static final int ANIMATION_DURATION_FOLD_TO_AOD = 600;
+ public static final int ANIMATION_DURATION_FOLD_TO_AOD =
+ AnimatableClockView.ANIMATION_DURATION_FOLD_TO_AOD;
public static final int ANIMATION_DURATION_PULSE_APPEAR =
KeyguardSliceView.DEFAULT_ANIM_DURATION;
public static final int ANIMATION_DURATION_BLOCKING_HELPER_FADE = 240;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
index 77377af9ddfb..cb4a0884fea4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackStateLogger.kt
@@ -3,6 +3,7 @@ package com.android.systemui.statusbar.notification.stack
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
class StackStateLogger @Inject constructor(
@@ -10,7 +11,7 @@ class StackStateLogger @Inject constructor(
) {
fun logHUNViewDisappearing(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 "
})
@@ -18,7 +19,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewAppearing(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification view appearing $str1 "
})
@@ -26,7 +27,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewDisappearingWithRemoveEvent(key: String) {
buffer.log(TAG, LogLevel.ERROR, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 for ANIMATION_TYPE_REMOVE"
})
@@ -34,7 +35,7 @@ class StackStateLogger @Inject constructor(
fun logHUNViewAppearingWithAddEvent(key: String) {
buffer.log(TAG, LogLevel.ERROR, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up view disappearing $str1 for ANIMATION_TYPE_ADD"
})
@@ -42,7 +43,7 @@ class StackStateLogger @Inject constructor(
fun disappearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification disappear animation ended $str1 "
})
@@ -50,7 +51,7 @@ class StackStateLogger @Inject constructor(
fun appearAnimationEnded(key: String) {
buffer.log(TAG, LogLevel.INFO, {
- str1 = key
+ str1 = logKey(key)
}, {
"Heads up notification appear animation ended $str1 "
})
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
index 8782be50794f..9070eadd9944 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
@@ -428,7 +428,7 @@ public class AutoTileManager implements UserAwareController {
if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
initSafetyTile();
} else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
- mHost.removeTile(CustomTile.getComponentFromSpec(mSafetySpec));
+ mHost.removeTile(mSafetySpec);
mHost.unmarkTileAsAutoAdded(mSafetySpec);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 39620ac23117..a0f386ff0b5e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -448,7 +448,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
}
// During wake and unlock, we need to draw black before waking up to avoid abrupt
// brightness changes due to display state transitions.
- boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
Runnable wakeUp = ()-> {
if (!wasDeviceInteractive) {
if (DEBUG_BIO_WAKELOCK) {
@@ -659,7 +658,10 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp
mLatencyTracker.onActionCancel(action);
}
- if (biometricSourceType == BiometricSourceType.FINGERPRINT
+ if (!mVibratorHelper.hasVibrator()
+ && (!mUpdateMonitor.isDeviceInteractive() || mUpdateMonitor.isDreaming())) {
+ startWakeAndUnlock(MODE_SHOW_BOUNCER);
+ } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
&& mUpdateMonitor.isUdfpsSupported()) {
long currUptimeMillis = SystemClock.uptimeMillis();
if (currUptimeMillis - mLastFpFailureUptimeMillis < mConsecutiveFpFailureThreshold) {
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 fc043b114479..5a8050814c8d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -47,6 +47,9 @@ import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.GestureRecorder;
import com.android.systemui.statusbar.LightRevealScrim;
import com.android.systemui.statusbar.NotificationPresenter;
@@ -265,9 +268,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
boolean isPulsing();
- @Nullable
- View getAmbientIndicationContainer();
-
boolean isOccluded();
//TODO: These can / should probably be moved to NotificationPresenter or ShadeController
@@ -427,12 +427,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void onHintFinished();
- void onCameraHintStarted();
-
- void onVoiceAssistHintStarted();
-
- void onPhoneHintStarted();
-
void onTrackingStopped(boolean expand);
// TODO: Figure out way to remove these.
@@ -448,6 +442,8 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void setBouncerShowing(boolean bouncerShowing);
+ void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
+
void collapseShade();
int getWakefulnessState();
@@ -564,8 +560,6 @@ public interface CentralSurfaces extends Dumpable, ActivityStarter, LifecycleOwn
void setLaunchEmergencyActionOnFinishedWaking(boolean launch);
- void setTopHidesStatusBar(boolean hides);
-
QSPanelController getQSPanelController();
boolean areNotificationAlertsDisabled();
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 9060d5f67913..f4b7772a1453 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -45,6 +45,7 @@ import android.view.WindowInsetsController.Behavior;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.R;
@@ -54,9 +55,10 @@ import com.android.systemui.dagger.qualifiers.DisplayId;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.shade.NotificationPanelView;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
@@ -87,19 +89,16 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final AssistManager mAssistManager;
private final DozeServiceHost mDozeServiceHost;
- private final SysuiStatusBarStateController mStatusBarStateController;
- private final NotificationShadeWindowView mNotificationShadeWindowView;
private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final PowerManager mPowerManager;
private final VibratorHelper mVibratorHelper;
private final Optional<Vibrator> mVibratorOptional;
- private final LightBarController mLightBarController;
private final DisableFlagsLogger mDisableFlagsLogger;
private final int mDisplayId;
private final boolean mVibrateOnOpening;
private final VibrationEffect mCameraLaunchGestureVibrationEffect;
-
+ private final SystemBarAttributesListener mSystemBarAttributesListener;
private static final VibrationAttributes HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES =
VibrationAttributes.createForUsage(VibrationAttributes.USAGE_HARDWARE_FEEDBACK);
@@ -122,16 +121,14 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
AssistManager assistManager,
DozeServiceHost dozeServiceHost,
- SysuiStatusBarStateController statusBarStateController,
- NotificationShadeWindowView notificationShadeWindowView,
NotificationStackScrollLayoutController notificationStackScrollLayoutController,
StatusBarHideIconsForBouncerManager statusBarHideIconsForBouncerManager,
PowerManager powerManager,
VibratorHelper vibratorHelper,
Optional<Vibrator> vibratorOptional,
- LightBarController lightBarController,
DisableFlagsLogger disableFlagsLogger,
- @DisplayId int displayId) {
+ @DisplayId int displayId,
+ SystemBarAttributesListener systemBarAttributesListener) {
mCentralSurfaces = centralSurfaces;
mContext = context;
@@ -148,20 +145,18 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mAssistManager = assistManager;
mDozeServiceHost = dozeServiceHost;
- mStatusBarStateController = statusBarStateController;
- mNotificationShadeWindowView = notificationShadeWindowView;
mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mPowerManager = powerManager;
mVibratorHelper = vibratorHelper;
mVibratorOptional = vibratorOptional;
- mLightBarController = lightBarController;
mDisableFlagsLogger = disableFlagsLogger;
mDisplayId = displayId;
mVibrateOnOpening = resources.getBoolean(R.bool.config_vibrateOnIconAnimation);
mCameraLaunchGestureVibrationEffect = getCameraGestureVibrationEffect(
mVibratorOptional, resources);
+ mSystemBarAttributesListener = systemBarAttributesListener;
}
@Override
@@ -187,7 +182,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
public void remQsTile(ComponentName tile) {
QSPanelController qsPanelController = mCentralSurfaces.getQSPanelController();
if (qsPanelController != null && qsPanelController.getHost() != null) {
- qsPanelController.getHost().removeTile(tile);
+ qsPanelController.getHost().removeTileByUser(tile);
}
}
@@ -388,8 +383,7 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
mStatusBarKeyguardViewManager.reset(true /* hide */);
}
- mNotificationPanelViewController.launchCamera(
- mCentralSurfaces.isDeviceInteractive() /* animate */, source);
+ mNotificationPanelViewController.launchCamera(source);
mCentralSurfaces.updateScrimController();
} else {
// We need to defer the camera launch until the screen comes on, since otherwise
@@ -464,18 +458,23 @@ public class CentralSurfacesCommandQueueCallbacks implements CommandQueue.Callba
@Override
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
}
- boolean barModeChanged = mCentralSurfaces.setAppearance(appearance);
-
- mLightBarController.onStatusBarAppearanceChanged(appearanceRegions, barModeChanged,
- mCentralSurfaces.getBarMode(), navbarColorManagedByIme);
-
- mCentralSurfaces.updateBubblesVisibility();
- mStatusBarStateController.setSystemBarAttributes(
- appearance, behavior, requestedVisibilities, packageName);
+ // SystemBarAttributesListener should __always__ be the top-level listener for system bar
+ // attributes changed.
+ mSystemBarAttributesListener.onSystemBarAttributesChanged(
+ displayId,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails
+ );
}
@Override
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 aa38d8a69b80..6cd028ecd2c0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -139,6 +139,7 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.biometrics.AuthRippleController;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.camera.CameraIntents;
+import com.android.systemui.charging.WiredChargingRippleController;
import com.android.systemui.charging.WirelessChargingAnimation;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.colorextraction.SysuiColorExtractor;
@@ -147,7 +148,6 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dagger.qualifiers.UiBackground;
import com.android.systemui.demomode.DemoMode;
import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.emergency.EmergencyGesture;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
@@ -174,6 +174,9 @@ import com.android.systemui.qs.QSPanelController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.scrim.ScrimView;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.AutoHideUiElement;
import com.android.systemui.statusbar.BackDropView;
@@ -197,8 +200,6 @@ import com.android.systemui.statusbar.PowerButtonReveal;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
-import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -207,7 +208,6 @@ import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
@@ -238,7 +238,6 @@ import com.android.systemui.util.WallpaperController;
import com.android.systemui.util.concurrency.DelayableExecutor;
import com.android.systemui.util.concurrency.MessageRouter;
import com.android.systemui.volume.VolumeComponent;
-import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.SplashscreenContentDrawer;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -307,7 +306,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
- private final DreamOverlayStateController mDreamOverlayStateController;
private CentralSurfacesCommandQueueCallbacks mCommandQueueCallbacks;
private float mTransitionToFullShadeProgress = 0f;
private NotificationListContainer mNotifListContainer;
@@ -405,11 +403,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
@Override
- public void setTopHidesStatusBar(boolean hides) {
- mTopHidesStatusBar = hides;
- }
-
- @Override
public QSPanelController getQSPanelController() {
return mQSPanelController;
}
@@ -442,6 +435,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
*/
protected int mState; // TODO: remove this. Just use StatusBarStateController
protected boolean mBouncerShowing;
+ private boolean mBouncerShowingOverDream;
private final PhoneStatusBarPolicy mIconPolicy;
@@ -451,7 +445,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
private BiometricUnlockController mBiometricUnlockController;
private final LightBarController mLightBarController;
private final Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
- private final LockscreenGestureLogger mLockscreenGestureLogger;
@Nullable
protected LockscreenWallpaper mLockscreenWallpaper;
private final AutoHideController mAutoHideController;
@@ -518,9 +511,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
private boolean mExpandedVisible;
- private final int[] mAbsPos = new int[2];
-
- private final NotifShadeEventSource mNotifShadeEventSource;
protected final NotificationEntryManager mEntryManager;
private final NotificationGutsManager mGutsManager;
private final NotificationLogger mNotificationLogger;
@@ -602,7 +592,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
}
- private Handler mMainHandler;
private final DelayableExecutor mMainExecutor;
private int mInteractingWindows;
@@ -636,12 +625,9 @@ public class CentralSurfacesImpl extends CoreStartable implements
// Fingerprint (as computed by getLoggingFingerprint() of the last logged state.
private int mLastLoggedStateFingerprint;
- private boolean mTopHidesStatusBar;
- private boolean mStatusBarWindowHidden;
private boolean mIsLaunchingActivityOverLockscreen;
private final UserSwitcherController mUserSwitcherController;
- private final NetworkController mNetworkController;
private final LifecycleRegistry mLifecycle = new LifecycleRegistry(this);
protected final BatteryController mBatteryController;
protected boolean mPanelExpanded;
@@ -661,7 +647,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
protected NotificationPresenter mPresenter;
private NotificationActivityStarter mNotificationActivityStarter;
private final Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
- private final Optional<BubblesManager> mBubblesManagerOptional;
private final Optional<Bubbles> mBubblesOptional;
private final Bubbles.BubbleExpandListener mBubbleExpandListener;
private final Optional<StartingSurface> mStartingSurfaceOptional;
@@ -703,7 +688,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
FalsingManager falsingManager,
FalsingCollector falsingCollector,
BroadcastDispatcher broadcastDispatcher,
- NotifShadeEventSource notifShadeEventSource,
NotificationEntryManager notificationEntryManager,
NotificationGutsManager notificationGutsManager,
NotificationLogger notificationLogger,
@@ -718,13 +702,11 @@ public class CentralSurfacesImpl extends CoreStartable implements
NotificationLockscreenUserManager lockScreenUserManager,
NotificationRemoteInputManager remoteInputManager,
UserSwitcherController userSwitcherController,
- NetworkController networkController,
BatteryController batteryController,
SysuiColorExtractor colorExtractor,
ScreenLifecycle screenLifecycle,
WakefulnessLifecycle wakefulnessLifecycle,
SysuiStatusBarStateController statusBarStateController,
- Optional<BubblesManager> bubblesManagerOptional,
Optional<Bubbles> bubblesOptional,
VisualStabilityManager visualStabilityManager,
DeviceProvisionedController deviceProvisionedController,
@@ -736,7 +718,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
DozeParameters dozeParameters,
ScrimController scrimController,
Lazy<LockscreenWallpaper> lockscreenWallpaperLazy,
- LockscreenGestureLogger lockscreenGestureLogger,
Lazy<BiometricUnlockController> biometricUnlockControllerLazy,
DozeServiceHost dozeServiceHost,
PowerManager powerManager,
@@ -769,7 +750,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
LockscreenShadeTransitionController lockscreenShadeTransitionController,
FeatureFlags featureFlags,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
- @Main Handler mainHandler,
@Main DelayableExecutor delayableExecutor,
@Main MessageRouter messageRouter,
WallpaperManager wallpaperManager,
@@ -778,7 +758,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
NotifPipelineFlags notifPipelineFlags,
InteractionJankMonitor jankMonitor,
DeviceStateManager deviceStateManager,
- DreamOverlayStateController dreamOverlayStateController,
WiredChargingRippleController wiredChargingRippleController,
IDreamManager dreamManager) {
super(context);
@@ -799,7 +778,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
mFalsingCollector = falsingCollector;
mFalsingManager = falsingManager;
mBroadcastDispatcher = broadcastDispatcher;
- mNotifShadeEventSource = notifShadeEventSource;
mEntryManager = notificationEntryManager;
mGutsManager = notificationGutsManager;
mNotificationLogger = notificationLogger;
@@ -814,13 +792,11 @@ public class CentralSurfacesImpl extends CoreStartable implements
mLockscreenUserManager = lockScreenUserManager;
mRemoteInputManager = remoteInputManager;
mUserSwitcherController = userSwitcherController;
- mNetworkController = networkController;
mBatteryController = batteryController;
mColorExtractor = colorExtractor;
mScreenLifecycle = screenLifecycle;
mWakefulnessLifecycle = wakefulnessLifecycle;
mStatusBarStateController = statusBarStateController;
- mBubblesManagerOptional = bubblesManagerOptional;
mBubblesOptional = bubblesOptional;
mVisualStabilityManager = visualStabilityManager;
mDeviceProvisionedController = deviceProvisionedController;
@@ -834,7 +810,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
mDozeParameters = dozeParameters;
mScrimController = scrimController;
mLockscreenWallpaperLazy = lockscreenWallpaperLazy;
- mLockscreenGestureLogger = lockscreenGestureLogger;
mScreenPinningRequest = screenPinningRequest;
mDozeScrimController = dozeScrimController;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
@@ -861,12 +836,10 @@ public class CentralSurfacesImpl extends CoreStartable implements
mStatusBarHideIconsForBouncerManager = statusBarHideIconsForBouncerManager;
mFeatureFlags = featureFlags;
mKeyguardUnlockAnimationController = keyguardUnlockAnimationController;
- mMainHandler = mainHandler;
mMainExecutor = delayableExecutor;
mMessageRouter = messageRouter;
mWallpaperManager = wallpaperManager;
mJankMonitor = jankMonitor;
- mDreamOverlayStateController = dreamOverlayStateController;
mLockscreenShadeTransitionController = lockscreenShadeTransitionController;
mStartingSurfaceOptional = startingSurfaceOptional;
@@ -973,7 +946,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
- result.mRequestedVisibilities, result.mPackageName);
+ result.mRequestedVisibilities, result.mPackageName, result.mLetterboxDetails);
// StatusBarManagerService has a back up of IME token and it's restored here.
mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
@@ -1197,9 +1170,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mHeadsUpManager.addListener(mVisualStabilityManager);
- }
mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
createNavigationBar(result);
@@ -1284,8 +1254,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
backdrop.setScaleY(scale);
});
- mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
-
// Set up the quick settings tile panel
final View container = mNotificationShadeWindowView.findViewById(R.id.qs_frame);
if (container != null) {
@@ -1485,12 +1453,16 @@ public class CentralSurfacesImpl extends CoreStartable implements
mPowerManager.wakeUp(
time, PowerManager.WAKE_REASON_GESTURE, "com.android.systemui:" + why);
mWakeUpComingFromTouch = true;
- where.getLocationInWindow(mTmpInt2);
// NOTE, the incoming view can sometimes be the entire container... unsure if
// this location is valuable enough
- mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
- mTmpInt2[1] + where.getHeight() / 2);
+ if (where != null) {
+ where.getLocationInWindow(mTmpInt2);
+ mWakeUpTouchLocation = new PointF(mTmpInt2[0] + where.getWidth() / 2,
+ mTmpInt2[1] + where.getHeight() / 2);
+ } else {
+ mWakeUpTouchLocation = new PointF(-1, -1);
+ }
mFalsingCollector.onScreenOnFromTouch();
}
}
@@ -1737,7 +1709,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
// Wrap the animation controller to dismiss the shade and set
// mIsLaunchingActivityOverLockscreen during the animation.
ActivityLaunchAnimator.Controller delegate = wrapAnimationController(
- animationController, dismissShade);
+ animationController, dismissShade, /* isLaunchForActivity= */ true);
controller = new DelegateLaunchAnimatorController(delegate) {
@Override
public void onIntentStarted(boolean willAnimate) {
@@ -1889,12 +1861,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
return mDozeServiceHost.isPulsing();
}
- @androidx.annotation.Nullable
- @Override
- public View getAmbientIndicationContainer() {
- return mAmbientIndicationContainer;
- }
-
/**
* When the keyguard is showing and covered by a "showWhenLocked" activity it
* is occluded. This is controlled by {@link com.android.server.policy.PhoneWindowManager}
@@ -2274,8 +2240,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
public void updateBubblesVisibility() {
mBubblesOptional.ifPresent(bubbles -> bubbles.onStatusBarVisibilityChanged(
mStatusBarMode != MODE_LIGHTS_OUT
- && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT
- && !mStatusBarWindowHidden));
+ && mStatusBarMode != MODE_LIGHTS_OUT_TRANSPARENT));
}
void checkBarMode(@TransitionMode int mode, @WindowVisibleState int windowState,
@@ -2354,15 +2319,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
pw.print (" ");
mNotificationPanelViewController.dump(pw, args);
}
- pw.println(" mStackScroller: ");
- if (mStackScroller != null) {
- // Double indent until we rewrite the rest of this dump()
- pw.increaseIndent();
- pw.increaseIndent();
- mStackScroller.dump(pw, args);
- pw.decreaseIndent();
- pw.decreaseIndent();
- }
+ pw.println(" mStackScroller: " + mStackScroller + " (dump moved)");
pw.println(" Theme:");
String nightMode = mUiModeManager == null ? "null" : mUiModeManager.getNightMode() + "";
pw.println(" dark theme: " + nightMode +
@@ -2500,7 +2457,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
true /* isActivityIntent */);
ActivityLaunchAnimator.Controller animController =
animationController != null ? wrapAnimationController(animationController,
- dismissShade) : null;
+ dismissShade, /* isLaunchForActivity= */ true) : null;
// If we animate, we will dismiss the shade only once the animation is done. This is taken
// care of by the StatusBarLaunchAnimationController.
@@ -2517,6 +2474,12 @@ public class CentralSurfacesImpl extends CoreStartable implements
animate, intent.getPackage(), (adapter) -> {
ActivityOptions options = new ActivityOptions(
CentralSurfaces.getActivityOptions(mDisplayId, adapter));
+
+ // We know that the intent of the caller is to dismiss the keyguard and
+ // this runnable is called right after the keyguard is solved, so we tell
+ // WM that we should dismiss it to avoid flickers when opening an activity
+ // that can also be shown over the keyguard.
+ options.setDismissKeyguard();
options.setDisallowEnterPictureInPictureWhileLaunching(
disallowEnterPictureInPictureWhileLaunching);
if (CameraIntents.isInsecureCameraIntent(intent)) {
@@ -2563,13 +2526,34 @@ public class CentralSurfacesImpl extends CoreStartable implements
callback.onActivityStarted(ActivityManager.START_CANCELED);
}
};
+ // Do not deferKeyguard when occluded because, when keyguard is occluded,
+ // we do not launch the activity until keyguard is done.
+ boolean occluded = mStatusBarKeyguardViewManager.isShowing()
+ && mStatusBarKeyguardViewManager.isOccluded();
+ boolean deferred = !occluded;
executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShadeDirectly,
- willLaunchResolverActivity, true /* deferred */, animate);
+ willLaunchResolverActivity, deferred /* deferred */, animate);
}
+ /**
+ * Return a {@link ActivityLaunchAnimator.Controller} wrapping {@code animationController} so
+ * that:
+ * - if it launches in the notification shade window and {@code dismissShade} is true, then
+ * the shade will be instantly dismissed at the end of the animation.
+ * - if it launches in status bar window, it will make the status bar window match the device
+ * size during the animation (that way, the animation won't be clipped by the status bar
+ * size).
+ *
+ * @param animationController the controller that is wrapped and will drive the main animation.
+ * @param dismissShade whether the notification shade will be dismissed at the end of the
+ * animation. This is ignored if {@code animationController} is not
+ * animating in the shade window.
+ * @param isLaunchForActivity whether the launch is for an activity.
+ */
@Nullable
private ActivityLaunchAnimator.Controller wrapAnimationController(
- ActivityLaunchAnimator.Controller animationController, boolean dismissShade) {
+ ActivityLaunchAnimator.Controller animationController, boolean dismissShade,
+ boolean isLaunchForActivity) {
View rootView = animationController.getLaunchContainer().getRootView();
Optional<ActivityLaunchAnimator.Controller> controllerFromStatusBar =
@@ -2583,7 +2567,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
// If the view is not in the status bar, then we are animating a view in the shade.
// We have to make sure that we collapse it when the animation ends or is cancelled.
return new StatusBarLaunchAnimatorController(animationController, this,
- true /* isLaunchForActivity */);
+ isLaunchForActivity);
}
return animationController;
@@ -2724,7 +2708,8 @@ public class CentralSurfacesImpl extends CoreStartable implements
@Override
public void dismissKeyguardThenExecute(OnDismissAction action, Runnable cancelAction,
boolean afterKeyguardGone) {
- if (mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
+ if (!action.willRunAnimationOnKeyguard()
+ && mWakefulnessLifecycle.getWakefulness() == WAKEFULNESS_ASLEEP
&& mKeyguardStateController.canDismissLockScreen()
&& !mStatusBarStateController.leaveOpenOnKeyguardHide()
&& mDozeServiceHost.isPulsing()) {
@@ -3026,8 +3011,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
@Override
public boolean isInLaunchTransition() {
- return mNotificationPanelViewController.isLaunchTransitionRunning()
- || mNotificationPanelViewController.isLaunchTransitionFinished();
+ return mNotificationPanelViewController.isLaunchTransitionFinished();
}
/**
@@ -3059,11 +3043,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
mCommandQueue.appTransitionStarting(mDisplayId, SystemClock.uptimeMillis(),
LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
};
- if (mNotificationPanelViewController.isLaunchTransitionRunning()) {
- mNotificationPanelViewController.setLaunchTransitionEndRunnable(hideRunnable);
- } else {
- hideRunnable.run();
- }
+ hideRunnable.run();
}
private void cancelAfterLaunchTransitionRunnables() {
@@ -3072,7 +3052,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
mLaunchTransitionEndRunnable = null;
mLaunchTransitionCancelRunnable = null;
- mNotificationPanelViewController.setLaunchTransitionEndRunnable(null);
}
/**
@@ -3328,9 +3307,13 @@ public class CentralSurfacesImpl extends CoreStartable implements
@Override
public boolean onBackPressed() {
- boolean isScrimmedBouncer = mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
- if (mStatusBarKeyguardViewManager.onBackPressed(isScrimmedBouncer /* hideImmediately */)) {
- if (isScrimmedBouncer) {
+ final boolean isScrimmedBouncer =
+ mScrimController.getState() == ScrimState.BOUNCER_SCRIMMED;
+ final boolean isBouncerOverDream = isBouncerShowingOverDream();
+
+ if (mStatusBarKeyguardViewManager.onBackPressed(
+ isScrimmedBouncer || isBouncerOverDream /* hideImmediately */)) {
+ if (isScrimmedBouncer || isBouncerOverDream) {
mStatusBarStateController.setLeaveOpenOnKeyguardHide(false);
} else {
mNotificationPanelViewController.expandWithoutQs();
@@ -3342,17 +3325,14 @@ public class CentralSurfacesImpl extends CoreStartable implements
return true;
}
if (mNotificationPanelViewController.isQsExpanded()) {
- if (mNotificationPanelViewController.isQsDetailShowing()) {
- mNotificationPanelViewController.closeQsDetail();
- } else {
mNotificationPanelViewController.animateCloseQs(false /* animateAway */);
- }
return true;
}
if (mNotificationPanelViewController.closeUserSwitcherIfOpen()) {
return true;
}
- if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED) {
+ if (mState != StatusBarState.KEYGUARD && mState != StatusBarState.SHADE_LOCKED
+ && !isBouncerOverDream) {
if (mNotificationPanelViewController.canPanelBeCollapsed()) {
mShadeController.animateCollapsePanels();
}
@@ -3496,24 +3476,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
@Override
- public void onCameraHintStarted() {
- mFalsingCollector.onCameraHintStarted();
- mKeyguardIndicationController.showTransientIndication(R.string.camera_hint);
- }
-
- @Override
- public void onVoiceAssistHintStarted() {
- mFalsingCollector.onLeftAffordanceHintStarted();
- mKeyguardIndicationController.showTransientIndication(R.string.voice_hint);
- }
-
- @Override
- public void onPhoneHintStarted() {
- mFalsingCollector.onLeftAffordanceHintStarted();
- mKeyguardIndicationController.showTransientIndication(R.string.phone_hint);
- }
-
- @Override
public void onTrackingStopped(boolean expand) {
}
@@ -3558,6 +3520,9 @@ public class CentralSurfacesImpl extends CoreStartable implements
setBouncerShowingForStatusBarComponents(bouncerShowing);
mStatusBarHideIconsForBouncerManager.setBouncerShowingAndTriggerUpdate(bouncerShowing);
mCommandQueue.recomputeDisableFlags(mDisplayId, true /* animate */);
+ if (mBouncerShowing) {
+ wakeUpIfDozing(SystemClock.uptimeMillis(), null, "BOUNCER_VISIBLE");
+ }
updateScrimController();
if (!mBouncerShowing) {
updatePanelExpansionForKeyguard();
@@ -3565,6 +3530,17 @@ public class CentralSurfacesImpl extends CoreStartable implements
}
/**
+ * Sets whether the bouncer over dream is showing. Note that the bouncer over dream is handled
+ * independently of the rest of the notification panel. As a result, setting this state via
+ * {@link #setBouncerShowing(boolean)} leads to unintended side effects from states modified
+ * behind the dream.
+ */
+ @Override
+ public void setBouncerShowingOverDream(boolean bouncerShowingOverDream) {
+ mBouncerShowingOverDream = bouncerShowingOverDream;
+ }
+
+ /**
* Propagate the bouncer state to status bar components.
*
* Separate from {@link #setBouncerShowing} because we sometimes re-create the status bar and
@@ -3685,9 +3661,20 @@ public class CentralSurfacesImpl extends CoreStartable implements
public void onFinishedWakingUp() {
mWakeUpCoordinator.setFullyAwake(true);
mWakeUpCoordinator.setWakingUp(false);
+ if (mKeyguardStateController.isOccluded()
+ && !mDozeParameters.canControlUnlockedScreenOff()) {
+ // When the keyguard is occluded we don't use the KEYGUARD state which would
+ // normally cause these redaction updates. If AOD is on, the KEYGUARD state is used
+ // to show the doze, AND UnlockedScreenOffAnimationController.onFinishedWakingUp()
+ // would force a KEYGUARD state that would take care of recalculating redaction.
+ // So if AOD is off or unsupported we need to trigger these updates at screen on
+ // when the keyguard is occluded.
+ mLockscreenUserManager.updatePublicMode();
+ mNotificationPanelViewController.getNotificationStackScrollLayoutController()
+ .updateSensitivenessForOccludedWakeup();
+ }
if (mLaunchCameraWhenFinishedWaking) {
- mNotificationPanelViewController.launchCamera(
- false /* animate */, mLastCameraLaunchSource);
+ mNotificationPanelViewController.launchCamera(mLastCameraLaunchSource);
mLaunchCameraWhenFinishedWaking = false;
}
if (mLaunchEmergencyActionWhenFinishedWaking) {
@@ -4120,8 +4107,9 @@ public class CentralSurfacesImpl extends CoreStartable implements
// We wrap animationCallback with a StatusBarLaunchAnimatorController so that the
// shade is collapsed after the animation (or when it is cancelled, aborted, etc).
ActivityLaunchAnimator.Controller controller =
- animationController != null ? new StatusBarLaunchAnimatorController(
- animationController, this, intent.isActivity()) : null;
+ animationController != null ? wrapAnimationController(
+ animationController, /* dismissShade= */ true, intent.isActivity())
+ : null;
mActivityLaunchAnimator.startPendingIntentWithAnimation(
controller, animate, intent.getCreatorPackage(),
@@ -4208,7 +4196,7 @@ public class CentralSurfacesImpl extends CoreStartable implements
@Override
public boolean isBouncerShowingOverDream() {
- return isBouncerShowing() && mDreamOverlayStateController.isOverlayActive();
+ return mBouncerShowingOverDream;
}
/**
@@ -4329,9 +4317,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
if (!mUserSetup) {
animateCollapseQuickSettings();
}
- if (mNotificationPanelViewController != null) {
- mNotificationPanelViewController.setUserSetupComplete(mUserSetup);
- }
updateQsExpansionEnabled();
}
}
@@ -4370,9 +4355,6 @@ public class CentralSurfacesImpl extends CoreStartable implements
Log.v(TAG, "configuration changed: " + mContext.getResources().getConfiguration());
}
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mViewHierarchyManager.updateRowStates();
- }
mScreenPinningRequest.onConfigurationChanged();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index 55b310ff986d..80c3e6ce989d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -40,6 +40,8 @@ import com.android.systemui.doze.DozeLog;
import com.android.systemui.doze.DozeReceiver;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
index 9863a0ed1ce0..484441a1e76b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceController.java
@@ -26,6 +26,7 @@ import com.android.internal.widget.ViewClippingUtil;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
@@ -40,8 +41,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
import com.android.systemui.util.ViewController;
-import java.util.Optional;
import java.util.ArrayList;
+import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 415bd90ed23a..80432dbd277c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -217,8 +217,9 @@ public class HeadsUpManagerPhone extends HeadsUpManager implements Dumpable,
* Notify that the status bar panel gets expanded or collapsed.
*
* @param isExpanded True to notify expanded, false to notify collapsed.
+ * TODO(b/237811427) replace with a listener
*/
- void setIsPanelExpanded(boolean isExpanded) {
+ public void setIsPanelExpanded(boolean isExpanded) {
if (isExpanded != mIsExpanded) {
mIsExpanded = isExpanded;
if (isExpanded) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
index 42f301d2f222..6bfb0dad28f7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpTouchHelper.java
@@ -21,6 +21,7 @@ import android.view.MotionEvent;
import android.view.ViewConfiguration;
import com.android.systemui.Gefingerpoken;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
deleted file mode 100644
index 2922b4c98d34..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardAffordanceHelper.java
+++ /dev/null
@@ -1,587 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
-import android.content.Context;
-import android.view.MotionEvent;
-import android.view.VelocityTracker;
-import android.view.View;
-import android.view.ViewConfiguration;
-
-import com.android.systemui.R;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.classifier.Classifier;
-import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.wm.shell.animation.FlingAnimationUtils;
-
-/**
- * A touch handler of the keyguard which is responsible for launching phone and camera affordances.
- */
-public class KeyguardAffordanceHelper {
-
- public static final long HINT_PHASE1_DURATION = 200;
- private static final long HINT_PHASE2_DURATION = 350;
- private static final float BACKGROUND_RADIUS_SCALE_FACTOR = 0.25f;
- private static final int HINT_CIRCLE_OPEN_DURATION = 500;
-
- private final Context mContext;
- private final Callback mCallback;
-
- private FlingAnimationUtils mFlingAnimationUtils;
- private VelocityTracker mVelocityTracker;
- private boolean mSwipingInProgress;
- private float mInitialTouchX;
- private float mInitialTouchY;
- private float mTranslation;
- private float mTranslationOnDown;
- private int mTouchSlop;
- private int mMinTranslationAmount;
- private int mMinFlingVelocity;
- private int mHintGrowAmount;
- private KeyguardAffordanceView mLeftIcon;
- private KeyguardAffordanceView mRightIcon;
- private Animator mSwipeAnimator;
- private final FalsingManager mFalsingManager;
- private int mMinBackgroundRadius;
- private boolean mMotionCancelled;
- private int mTouchTargetSize;
- private View mTargetedView;
- private boolean mTouchSlopExeeded;
- private AnimatorListenerAdapter mFlingEndListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mSwipeAnimator = null;
- mSwipingInProgress = false;
- mTargetedView = null;
- }
- };
- private Runnable mAnimationEndRunnable = new Runnable() {
- @Override
- public void run() {
- mCallback.onAnimationToSideEnded();
- }
- };
-
- KeyguardAffordanceHelper(Callback callback, Context context, FalsingManager falsingManager) {
- mContext = context;
- mCallback = callback;
- initIcons();
- updateIcon(mLeftIcon, 0.0f, mLeftIcon.getRestingAlpha(), false, false, true, false);
- updateIcon(mRightIcon, 0.0f, mRightIcon.getRestingAlpha(), false, false, true, false);
- mFalsingManager = falsingManager;
- initDimens();
- }
-
- private void initDimens() {
- final ViewConfiguration configuration = ViewConfiguration.get(mContext);
- mTouchSlop = configuration.getScaledPagingTouchSlop();
- mMinFlingVelocity = configuration.getScaledMinimumFlingVelocity();
- mMinTranslationAmount = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_min_swipe_amount);
- mMinBackgroundRadius = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_affordance_min_background_radius);
- mTouchTargetSize = mContext.getResources().getDimensionPixelSize(
- R.dimen.keyguard_affordance_touch_target_size);
- mHintGrowAmount =
- mContext.getResources().getDimensionPixelSize(R.dimen.hint_grow_amount_sideways);
- mFlingAnimationUtils = new FlingAnimationUtils(mContext.getResources().getDisplayMetrics(),
- 0.4f);
- }
-
- private void initIcons() {
- mLeftIcon = mCallback.getLeftIcon();
- mRightIcon = mCallback.getRightIcon();
- updatePreviews();
- }
-
- public void updatePreviews() {
- mLeftIcon.setPreviewView(mCallback.getLeftPreview());
- mRightIcon.setPreviewView(mCallback.getRightPreview());
- }
-
- public boolean onTouchEvent(MotionEvent event) {
- int action = event.getActionMasked();
- if (mMotionCancelled && action != MotionEvent.ACTION_DOWN) {
- return false;
- }
- final float y = event.getY();
- final float x = event.getX();
-
- boolean isUp = false;
- switch (action) {
- case MotionEvent.ACTION_DOWN:
- View targetView = getIconAtPosition(x, y);
- if (targetView == null || (mTargetedView != null && mTargetedView != targetView)) {
- mMotionCancelled = true;
- return false;
- }
- if (mTargetedView != null) {
- cancelAnimation();
- } else {
- mTouchSlopExeeded = false;
- }
- startSwiping(targetView);
- mInitialTouchX = x;
- mInitialTouchY = y;
- mTranslationOnDown = mTranslation;
- initVelocityTracker();
- trackMovement(event);
- mMotionCancelled = false;
- break;
- case MotionEvent.ACTION_POINTER_DOWN:
- mMotionCancelled = true;
- endMotion(true /* forceSnapBack */, x, y);
- break;
- case MotionEvent.ACTION_MOVE:
- trackMovement(event);
- float xDist = x - mInitialTouchX;
- float yDist = y - mInitialTouchY;
- float distance = (float) Math.hypot(xDist, yDist);
- if (!mTouchSlopExeeded && distance > mTouchSlop) {
- mTouchSlopExeeded = true;
- }
- if (mSwipingInProgress) {
- if (mTargetedView == mRightIcon) {
- distance = mTranslationOnDown - distance;
- distance = Math.min(0, distance);
- } else {
- distance = mTranslationOnDown + distance;
- distance = Math.max(0, distance);
- }
- setTranslation(distance, false /* isReset */, false /* animateReset */);
- }
- break;
-
- case MotionEvent.ACTION_UP:
- isUp = true;
- case MotionEvent.ACTION_CANCEL:
- boolean hintOnTheRight = mTargetedView == mRightIcon;
- trackMovement(event);
- endMotion(!isUp, x, y);
- if (!mTouchSlopExeeded && isUp) {
- mCallback.onIconClicked(hintOnTheRight);
- }
- break;
- }
- return true;
- }
-
- private void startSwiping(View targetView) {
- mCallback.onSwipingStarted(targetView == mRightIcon);
- mSwipingInProgress = true;
- mTargetedView = targetView;
- }
-
- private View getIconAtPosition(float x, float y) {
- if (leftSwipePossible() && isOnIcon(mLeftIcon, x, y)) {
- return mLeftIcon;
- }
- if (rightSwipePossible() && isOnIcon(mRightIcon, x, y)) {
- return mRightIcon;
- }
- return null;
- }
-
- public boolean isOnAffordanceIcon(float x, float y) {
- return isOnIcon(mLeftIcon, x, y) || isOnIcon(mRightIcon, x, y);
- }
-
- private boolean isOnIcon(View icon, float x, float y) {
- float iconX = icon.getX() + icon.getWidth() / 2.0f;
- float iconY = icon.getY() + icon.getHeight() / 2.0f;
- double distance = Math.hypot(x - iconX, y - iconY);
- return distance <= mTouchTargetSize / 2;
- }
-
- private void endMotion(boolean forceSnapBack, float lastX, float lastY) {
- if (mSwipingInProgress) {
- flingWithCurrentVelocity(forceSnapBack, lastX, lastY);
- } else {
- mTargetedView = null;
- }
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- mVelocityTracker = null;
- }
- }
-
- private boolean rightSwipePossible() {
- return mRightIcon.getVisibility() == View.VISIBLE;
- }
-
- private boolean leftSwipePossible() {
- return mLeftIcon.getVisibility() == View.VISIBLE;
- }
-
- public boolean onInterceptTouchEvent(MotionEvent ev) {
- return false;
- }
-
- public void startHintAnimation(boolean right,
- Runnable onFinishedListener) {
- cancelAnimation();
- startHintAnimationPhase1(right, onFinishedListener);
- }
-
- private void startHintAnimationPhase1(final boolean right, final Runnable onFinishedListener) {
- final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- ValueAnimator animator = getAnimatorToRadius(right, mHintGrowAmount);
- animator.addListener(new AnimatorListenerAdapter() {
- private boolean mCancelled;
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCancelled = true;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- if (mCancelled) {
- mSwipeAnimator = null;
- mTargetedView = null;
- onFinishedListener.run();
- } else {
- startUnlockHintAnimationPhase2(right, onFinishedListener);
- }
- }
- });
- animator.setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN);
- animator.setDuration(HINT_PHASE1_DURATION);
- animator.start();
- mSwipeAnimator = animator;
- mTargetedView = targetView;
- }
-
- /**
- * Phase 2: Move back.
- */
- private void startUnlockHintAnimationPhase2(boolean right, final Runnable onFinishedListener) {
- ValueAnimator animator = getAnimatorToRadius(right, 0);
- animator.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mSwipeAnimator = null;
- mTargetedView = null;
- onFinishedListener.run();
- }
- });
- animator.setInterpolator(Interpolators.FAST_OUT_LINEAR_IN);
- animator.setDuration(HINT_PHASE2_DURATION);
- animator.setStartDelay(HINT_CIRCLE_OPEN_DURATION);
- animator.start();
- mSwipeAnimator = animator;
- }
-
- private ValueAnimator getAnimatorToRadius(final boolean right, int radius) {
- final KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- ValueAnimator animator = ValueAnimator.ofFloat(targetView.getCircleRadius(), radius);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- float newRadius = (float) animation.getAnimatedValue();
- targetView.setCircleRadiusWithoutAnimation(newRadius);
- float translation = getTranslationFromRadius(newRadius);
- mTranslation = right ? -translation : translation;
- updateIconsFromTranslation(targetView);
- }
- });
- return animator;
- }
-
- private void cancelAnimation() {
- if (mSwipeAnimator != null) {
- mSwipeAnimator.cancel();
- }
- }
-
- private void flingWithCurrentVelocity(boolean forceSnapBack, float lastX, float lastY) {
- float vel = getCurrentVelocity(lastX, lastY);
-
- // We snap back if the current translation is not far enough
- boolean snapBack = false;
- if (mCallback.needsAntiFalsing()) {
- snapBack = snapBack || mFalsingManager.isFalseTouch(
- mTargetedView == mRightIcon
- ? Classifier.RIGHT_AFFORDANCE : Classifier.LEFT_AFFORDANCE);
- }
- snapBack = snapBack || isBelowFalsingThreshold();
-
- // or if the velocity is in the opposite direction.
- boolean velIsInWrongDirection = vel * mTranslation < 0;
- snapBack |= Math.abs(vel) > mMinFlingVelocity && velIsInWrongDirection;
- vel = snapBack ^ velIsInWrongDirection ? 0 : vel;
- fling(vel, snapBack || forceSnapBack, mTranslation < 0);
- }
-
- private boolean isBelowFalsingThreshold() {
- return Math.abs(mTranslation) < Math.abs(mTranslationOnDown) + getMinTranslationAmount();
- }
-
- private int getMinTranslationAmount() {
- float factor = mCallback.getAffordanceFalsingFactor();
- return (int) (mMinTranslationAmount * factor);
- }
-
- private void fling(float vel, final boolean snapBack, boolean right) {
- float target = right ? -mCallback.getMaxTranslationDistance()
- : mCallback.getMaxTranslationDistance();
- target = snapBack ? 0 : target;
-
- ValueAnimator animator = ValueAnimator.ofFloat(mTranslation, target);
- mFlingAnimationUtils.apply(animator, mTranslation, target, vel);
- animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(ValueAnimator animation) {
- mTranslation = (float) animation.getAnimatedValue();
- }
- });
- animator.addListener(mFlingEndListener);
- if (!snapBack) {
- startFinishingCircleAnimation(vel * 0.375f, mAnimationEndRunnable, right);
- mCallback.onAnimationToSideStarted(right, mTranslation, vel);
- } else {
- reset(true);
- }
- animator.start();
- mSwipeAnimator = animator;
- if (snapBack) {
- mCallback.onSwipingAborted();
- }
- }
-
- private void startFinishingCircleAnimation(float velocity, Runnable animationEndRunnable,
- boolean right) {
- KeyguardAffordanceView targetView = right ? mRightIcon : mLeftIcon;
- targetView.finishAnimation(velocity, animationEndRunnable);
- }
-
- private void setTranslation(float translation, boolean isReset, boolean animateReset) {
- translation = rightSwipePossible() ? translation : Math.max(0, translation);
- translation = leftSwipePossible() ? translation : Math.min(0, translation);
- float absTranslation = Math.abs(translation);
- if (translation != mTranslation || isReset) {
- KeyguardAffordanceView targetView = translation > 0 ? mLeftIcon : mRightIcon;
- KeyguardAffordanceView otherView = translation > 0 ? mRightIcon : mLeftIcon;
- float alpha = absTranslation / getMinTranslationAmount();
-
- // We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = 1.0f - alpha;
- fadeOutAlpha = Math.max(fadeOutAlpha, 0.0f);
-
- boolean animateIcons = isReset && animateReset;
- boolean forceNoCircleAnimation = isReset && !animateReset;
- float radius = getRadiusFromTranslation(absTranslation);
- boolean slowAnimation = isReset && isBelowFalsingThreshold();
- if (!isReset) {
- updateIcon(targetView, radius, alpha + fadeOutAlpha * targetView.getRestingAlpha(),
- false, false, false, false);
- } else {
- updateIcon(targetView, 0.0f, fadeOutAlpha * targetView.getRestingAlpha(),
- animateIcons, slowAnimation, true /* isReset */, forceNoCircleAnimation);
- }
- updateIcon(otherView, 0.0f, fadeOutAlpha * otherView.getRestingAlpha(),
- animateIcons, slowAnimation, isReset, forceNoCircleAnimation);
-
- mTranslation = translation;
- }
- }
-
- private void updateIconsFromTranslation(KeyguardAffordanceView targetView) {
- float absTranslation = Math.abs(mTranslation);
- float alpha = absTranslation / getMinTranslationAmount();
-
- // We interpolate the alpha of the other icons to 0
- float fadeOutAlpha = 1.0f - alpha;
- fadeOutAlpha = Math.max(0.0f, fadeOutAlpha);
-
- // We interpolate the alpha of the targetView to 1
- KeyguardAffordanceView otherView = targetView == mRightIcon ? mLeftIcon : mRightIcon;
- updateIconAlpha(targetView, alpha + fadeOutAlpha * targetView.getRestingAlpha(), false);
- updateIconAlpha(otherView, fadeOutAlpha * otherView.getRestingAlpha(), false);
- }
-
- private float getTranslationFromRadius(float circleSize) {
- float translation = (circleSize - mMinBackgroundRadius)
- / BACKGROUND_RADIUS_SCALE_FACTOR;
- return translation > 0.0f ? translation + mTouchSlop : 0.0f;
- }
-
- private float getRadiusFromTranslation(float translation) {
- if (translation <= mTouchSlop) {
- return 0.0f;
- }
- return (translation - mTouchSlop) * BACKGROUND_RADIUS_SCALE_FACTOR + mMinBackgroundRadius;
- }
-
- public void animateHideLeftRightIcon() {
- cancelAnimation();
- updateIcon(mRightIcon, 0f, 0f, true, false, false, false);
- updateIcon(mLeftIcon, 0f, 0f, true, false, false, false);
- }
-
- private void updateIcon(KeyguardAffordanceView view, float circleRadius, float alpha,
- boolean animate, boolean slowRadiusAnimation, boolean force,
- boolean forceNoCircleAnimation) {
- if (view.getVisibility() != View.VISIBLE && !force) {
- return;
- }
- if (forceNoCircleAnimation) {
- view.setCircleRadiusWithoutAnimation(circleRadius);
- } else {
- view.setCircleRadius(circleRadius, slowRadiusAnimation);
- }
- updateIconAlpha(view, alpha, animate);
- }
-
- private void updateIconAlpha(KeyguardAffordanceView view, float alpha, boolean animate) {
- float scale = getScale(alpha, view);
- alpha = Math.min(1.0f, alpha);
- view.setImageAlpha(alpha, animate);
- view.setImageScale(scale, animate);
- }
-
- private float getScale(float alpha, KeyguardAffordanceView icon) {
- float scale = alpha / icon.getRestingAlpha() * 0.2f +
- KeyguardAffordanceView.MIN_ICON_SCALE_AMOUNT;
- return Math.min(scale, KeyguardAffordanceView.MAX_ICON_SCALE_AMOUNT);
- }
-
- private void trackMovement(MotionEvent event) {
- if (mVelocityTracker != null) {
- mVelocityTracker.addMovement(event);
- }
- }
-
- private void initVelocityTracker() {
- if (mVelocityTracker != null) {
- mVelocityTracker.recycle();
- }
- mVelocityTracker = VelocityTracker.obtain();
- }
-
- private float getCurrentVelocity(float lastX, float lastY) {
- if (mVelocityTracker == null) {
- return 0;
- }
- mVelocityTracker.computeCurrentVelocity(1000);
- float aX = mVelocityTracker.getXVelocity();
- float aY = mVelocityTracker.getYVelocity();
- float bX = lastX - mInitialTouchX;
- float bY = lastY - mInitialTouchY;
- float bLen = (float) Math.hypot(bX, bY);
- // Project the velocity onto the distance vector: a * b / |b|
- float projectedVelocity = (aX * bX + aY * bY) / bLen;
- if (mTargetedView == mRightIcon) {
- projectedVelocity = -projectedVelocity;
- }
- return projectedVelocity;
- }
-
- public void onConfigurationChanged() {
- initDimens();
- initIcons();
- }
-
- public void onRtlPropertiesChanged() {
- initIcons();
- }
-
- public void reset(boolean animate) {
- cancelAnimation();
- setTranslation(0.0f, true /* isReset */, animate);
- mMotionCancelled = true;
- if (mSwipingInProgress) {
- mCallback.onSwipingAborted();
- mSwipingInProgress = false;
- }
- }
-
- public boolean isSwipingInProgress() {
- return mSwipingInProgress;
- }
-
- public void launchAffordance(boolean animate, boolean left) {
- if (mSwipingInProgress) {
- // We don't want to mess with the state if the user is actually swiping already.
- return;
- }
- KeyguardAffordanceView targetView = left ? mLeftIcon : mRightIcon;
- KeyguardAffordanceView otherView = left ? mRightIcon : mLeftIcon;
- startSwiping(targetView);
-
- // Do not animate the circle expanding if the affordance isn't visible,
- // otherwise the circle will be meaningless.
- if (targetView.getVisibility() != View.VISIBLE) {
- animate = false;
- }
-
- if (animate) {
- fling(0, false, !left);
- updateIcon(otherView, 0.0f, 0, true, false, true, false);
- } else {
- mCallback.onAnimationToSideStarted(!left, mTranslation, 0);
- mTranslation = left ? mCallback.getMaxTranslationDistance()
- : mCallback.getMaxTranslationDistance();
- updateIcon(otherView, 0.0f, 0.0f, false, false, true, false);
- targetView.instantFinishAnimation();
- mFlingEndListener.onAnimationEnd(null);
- mAnimationEndRunnable.run();
- }
- }
-
- public interface Callback {
-
- /**
- * Notifies the callback when an animation to a side page was started.
- *
- * @param rightPage Is the page animated to the right page?
- */
- void onAnimationToSideStarted(boolean rightPage, float translation, float vel);
-
- /**
- * Notifies the callback the animation to a side page has ended.
- */
- void onAnimationToSideEnded();
-
- float getMaxTranslationDistance();
-
- void onSwipingStarted(boolean rightIcon);
-
- void onSwipingAborted();
-
- void onIconClicked(boolean rightIcon);
-
- KeyguardAffordanceView getLeftIcon();
-
- KeyguardAffordanceView getRightIcon();
-
- View getLeftPreview();
-
- View getRightPreview();
-
- /**
- * @return The factor the minimum swipe amount should be multiplied with.
- */
- float getAffordanceFalsingFactor();
-
- boolean needsAntiFalsing();
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
index 414bc84fcd85..ff3d122855d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaView.java
@@ -16,56 +16,31 @@
package com.android.systemui.statusbar.phone;
-import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK;
-import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
-
import static com.android.systemui.controls.dagger.ControlsComponent.Visibility.AVAILABLE;
import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_BUTTON;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_LEFT_UNLOCK;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_BUTTON;
-import static com.android.systemui.tuner.LockscreenFragment.LOCKSCREEN_RIGHT_UNLOCK;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE;
import static com.android.systemui.wallet.controller.QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE;
-import android.app.ActivityManager;
-import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.admin.DevicePolicyManager;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
-import android.content.ServiceConnection;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Message;
-import android.os.Messenger;
import android.os.RemoteException;
import android.os.UserHandle;
-import android.provider.MediaStore;
-import android.service.media.CameraPrewarmService;
import android.service.quickaccesswallet.GetWalletCardsError;
import android.service.quickaccesswallet.GetWalletCardsResponse;
import android.service.quickaccesswallet.QuickAccessWalletClient;
-import android.telecom.TelecomManager;
-import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
import android.view.WindowInsets;
-import android.view.WindowManager;
-import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
@@ -73,81 +48,38 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.settingslib.Utils;
-import com.android.systemui.ActivityIntentHelper;
import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.assist.AssistManager;
-import com.android.systemui.camera.CameraIntents;
-import com.android.systemui.controls.ControlsServiceInfo;
import com.android.systemui.controls.dagger.ControlsComponent;
import com.android.systemui.controls.management.ControlsListingController;
import com.android.systemui.controls.ui.ControlsActivity;
import com.android.systemui.controls.ui.ControlsUiController;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.FalsingManager;
-import com.android.systemui.plugins.IntentButtonProvider;
-import com.android.systemui.plugins.IntentButtonProvider.IntentButton;
-import com.android.systemui.plugins.IntentButtonProvider.IntentButton.IconState;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
-import com.android.systemui.statusbar.policy.AccessibilityController;
-import com.android.systemui.statusbar.policy.ExtensionController;
-import com.android.systemui.statusbar.policy.ExtensionController.Extension;
-import com.android.systemui.statusbar.policy.FlashlightController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.PreviewInflater;
-import com.android.systemui.tuner.LockscreenFragment.LockButtonFactory;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.wallet.controller.QuickAccessWalletController;
+import java.util.ArrayList;
import java.util.List;
/**
* Implementation for the bottom area of the Keyguard, including camera/phone affordance and status
* text.
*/
-public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickListener,
- KeyguardStateController.Callback,
- AccessibilityController.AccessibilityStateChangedCallback {
-
- final static String TAG = "CentralSurfaces/KeyguardBottomAreaView";
-
- public static final String CAMERA_LAUNCH_SOURCE_AFFORDANCE = "lockscreen_affordance";
- public static final String CAMERA_LAUNCH_SOURCE_WIGGLE = "wiggle_gesture";
- public static final String CAMERA_LAUNCH_SOURCE_POWER_DOUBLE_TAP = "power_double_tap";
- public static final String CAMERA_LAUNCH_SOURCE_LIFT_TRIGGER = "lift_to_launch_ml";
-
- public static final String EXTRA_CAMERA_LAUNCH_SOURCE
- = "com.android.systemui.camera_launch_source";
-
- private static final String LEFT_BUTTON_PLUGIN
- = "com.android.systemui.action.PLUGIN_LOCKSCREEN_LEFT_BUTTON";
- private static final String RIGHT_BUTTON_PLUGIN
- = "com.android.systemui.action.PLUGIN_LOCKSCREEN_RIGHT_BUTTON";
+public class KeyguardBottomAreaView extends FrameLayout {
- private static final Intent PHONE_INTENT = new Intent(Intent.ACTION_DIAL);
- private static final int DOZE_ANIMATION_STAGGER_DELAY = 48;
+ private static final String TAG = "CentralSurfaces/KeyguardBottomAreaView";
private static final int DOZE_ANIMATION_ELEMENT_DURATION = 250;
- // TODO(b/179494051): May no longer be needed
- private final boolean mShowLeftAffordance;
- private final boolean mShowCameraAffordance;
-
- private KeyguardAffordanceView mRightAffordanceView;
- private KeyguardAffordanceView mLeftAffordanceView;
-
private ImageView mWalletButton;
private ImageView mQRCodeScannerButton;
private ImageView mControlsButton;
private boolean mHasCard = false;
- private WalletCardRetriever mCardRetriever = new WalletCardRetriever();
+ private final WalletCardRetriever mCardRetriever = new WalletCardRetriever();
private QuickAccessWalletController mQuickAccessWalletController;
private QRCodeScannerController mQRCodeScannerController;
private ControlsComponent mControlsComponent;
@@ -157,68 +89,41 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
private ViewGroup mIndicationArea;
private TextView mIndicationText;
private TextView mIndicationTextBottom;
- private ViewGroup mPreviewContainer;
private ViewGroup mOverlayContainer;
- private View mLeftPreview;
- private View mCameraPreview;
-
private ActivityStarter mActivityStarter;
private KeyguardStateController mKeyguardStateController;
- private FlashlightController mFlashlightController;
- private PreviewInflater mPreviewInflater;
- private AccessibilityController mAccessibilityController;
- private CentralSurfaces mCentralSurfaces;
- private KeyguardAffordanceHelper mAffordanceHelper;
private FalsingManager mFalsingManager;
- private boolean mUserSetupComplete;
- private boolean mPrewarmBound;
- private Messenger mPrewarmMessenger;
- private final ServiceConnection mPrewarmConnection = new ServiceConnection() {
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- mPrewarmMessenger = new Messenger(service);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- mPrewarmMessenger = null;
- }
- };
-
- private boolean mLeftIsVoiceAssist;
- private Drawable mLeftAssistIcon;
-
- private IntentButton mRightButton = new DefaultRightButton();
- private Extension<IntentButton> mRightExtension;
- private String mRightButtonStr;
- private IntentButton mLeftButton = new DefaultLeftButton();
- private Extension<IntentButton> mLeftExtension;
- private String mLeftButtonStr;
private boolean mDozing;
private int mIndicationBottomMargin;
private int mIndicationPadding;
private float mDarkAmount;
private int mBurnInXOffset;
private int mBurnInYOffset;
- private ActivityIntentHelper mActivityIntentHelper;
- private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-
- private ControlsListingController.ControlsListingCallback mListingCallback =
- new ControlsListingController.ControlsListingCallback() {
- public void onServicesUpdated(List<ControlsServiceInfo> serviceInfos) {
- post(() -> {
- boolean available = !serviceInfos.isEmpty();
-
- if (available != mControlServicesAvailable) {
- mControlServicesAvailable = available;
- updateControlsVisibility();
- updateAffordanceColors();
- }
- });
+
+ private final ControlsListingController.ControlsListingCallback mListingCallback =
+ serviceInfos -> post(() -> {
+ boolean available = !serviceInfos.isEmpty();
+
+ if (available != mControlServicesAvailable) {
+ mControlServicesAvailable = available;
+ updateControlsVisibility();
+ updateAffordanceColors();
+ }
+ });
+
+ private final KeyguardStateController.Callback mKeyguardStateCallback =
+ new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ if (mKeyguardStateController.isShowing()) {
+ if (mQuickAccessWalletController != null) {
+ mQuickAccessWalletController.queryWalletCards(mCardRetriever);
}
- };
+ }
+ }
+ };
public KeyguardBottomAreaView(Context context) {
this(context, null);
@@ -235,46 +140,40 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
public KeyguardBottomAreaView(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
- mShowLeftAffordance = getResources().getBoolean(R.bool.config_keyguardShowLeftAffordance);
- mShowCameraAffordance = getResources()
- .getBoolean(R.bool.config_keyguardShowCameraAffordance);
}
- private AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
- @Override
- public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(host, info);
- String label = null;
- if (host == mRightAffordanceView) {
- label = getResources().getString(R.string.camera_label);
- } else if (host == mLeftAffordanceView) {
- if (mLeftIsVoiceAssist) {
- label = getResources().getString(R.string.voice_assist_label);
- } else {
- label = getResources().getString(R.string.phone_label);
- }
- }
- info.addAction(new AccessibilityAction(ACTION_CLICK, label));
- }
+ /** Initializes the {@link KeyguardBottomAreaView} with the given dependencies */
+ public void init(
+ FalsingManager falsingManager,
+ QuickAccessWalletController controller,
+ ControlsComponent controlsComponent,
+ QRCodeScannerController qrCodeScannerController) {
+ mFalsingManager = falsingManager;
+ mQuickAccessWalletController = controller;
+ mQuickAccessWalletController.setupWalletChangeObservers(
+ mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
+ mQuickAccessWalletController.updateWalletPreference();
+ mQuickAccessWalletController.queryWalletCards(mCardRetriever);
+ updateWalletVisibility();
- @Override
- public boolean performAccessibilityAction(View host, int action, Bundle args) {
- if (action == ACTION_CLICK) {
- if (host == mRightAffordanceView) {
- launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
- return true;
- } else if (host == mLeftAffordanceView) {
- launchLeftAffordance();
- return true;
- }
- }
- return super.performAccessibilityAction(host, action, args);
- }
- };
+ mControlsComponent = controlsComponent;
+ mControlsComponent.getControlsListingController().ifPresent(
+ c -> c.addCallback(mListingCallback));
- public void initFrom(KeyguardBottomAreaView oldBottomArea) {
- setCentralSurfaces(oldBottomArea.mCentralSurfaces);
+ mQRCodeScannerController = qrCodeScannerController;
+ mQRCodeScannerController.registerQRCodeScannerChangeObservers(
+ QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+ QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE);
+ updateQRCodeButtonVisibility();
+
+ updateAffordanceColors();
+ }
+ /**
+ * Initializes this instance of {@link KeyguardBottomAreaView} based on the given instance of
+ * another {@link KeyguardBottomAreaView}
+ */
+ public void initFrom(KeyguardBottomAreaView oldBottomArea) {
// if it exists, continue to use the original ambient indication container
// instead of the newly inflated one
if (mAmbientIndicationArea != null) {
@@ -302,11 +201,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mPreviewInflater = new PreviewInflater(mContext, new LockPatternUtils(mContext),
- new ActivityIntentHelper(mContext));
mOverlayContainer = findViewById(R.id.overlay_container);
- mRightAffordanceView = findViewById(R.id.camera_button);
- mLeftAffordanceView = findViewById(R.id.left_button);
mWalletButton = findViewById(R.id.wallet_button);
mQRCodeScannerButton = findViewById(R.id.qr_code_scanner_button);
mControlsButton = findViewById(R.id.controls_button);
@@ -318,18 +213,11 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
R.dimen.keyguard_indication_margin_bottom);
mBurnInYOffset = getResources().getDimensionPixelSize(
R.dimen.default_burn_in_prevention_offset);
- updateCameraVisibility();
mKeyguardStateController = Dependency.get(KeyguardStateController.class);
- mKeyguardStateController.addCallback(this);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
setClipChildren(false);
setClipToPadding(false);
- mRightAffordanceView.setOnClickListener(this);
- mLeftAffordanceView.setOnClickListener(this);
- initAccessibility();
mActivityStarter = Dependency.get(ActivityStarter.class);
- mFlashlightController = Dependency.get(FlashlightController.class);
- mAccessibilityController = Dependency.get(AccessibilityController.class);
- mActivityIntentHelper = new ActivityIntentHelper(getContext());
mIndicationPadding = getResources().getDimensionPixelSize(
R.dimen.keyguard_indication_area_padding);
@@ -338,51 +226,18 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
updateControlsVisibility();
}
- /**
- * Set the container where the previews are rendered.
- */
- public void setPreviewContainer(ViewGroup previewContainer) {
- mPreviewContainer = previewContainer;
- inflateCameraPreview();
- updateLeftAffordance();
- }
-
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mAccessibilityController.addStateChangedCallback(this);
- mRightExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
- .withPlugin(IntentButtonProvider.class, RIGHT_BUTTON_PLUGIN,
- p -> p.getIntentButton())
- .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_RIGHT_BUTTON))
- .withDefault(() -> new DefaultRightButton())
- .withCallback(button -> setRightButton(button))
- .build();
- mLeftExtension = Dependency.get(ExtensionController.class).newExtension(IntentButton.class)
- .withPlugin(IntentButtonProvider.class, LEFT_BUTTON_PLUGIN,
- p -> p.getIntentButton())
- .withTunerFactory(new LockButtonFactory(mContext, LOCKSCREEN_LEFT_BUTTON))
- .withDefault(() -> new DefaultLeftButton())
- .withCallback(button -> setLeftButton(button))
- .build();
final IntentFilter filter = new IntentFilter();
filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
- getContext().registerReceiverAsUser(mDevicePolicyReceiver,
- UserHandle.ALL, filter, null, null);
- mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
- mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
- mKeyguardStateController.addCallback(this);
+ mKeyguardStateController.addCallback(mKeyguardStateCallback);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mKeyguardStateController.removeCallback(this);
- mAccessibilityController.removeStateChangedCallback(this);
- mRightExtension.destroy();
- mLeftExtension.destroy();
- getContext().unregisterReceiver(mDevicePolicyReceiver);
- mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
+ mKeyguardStateController.removeCallback(mKeyguardStateCallback);
if (mQuickAccessWalletController != null) {
mQuickAccessWalletController.unregisterWalletChangeObservers(
@@ -401,11 +256,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
- private void initAccessibility() {
- mLeftAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
- mRightAffordanceView.setAccessibilityDelegate(mAccessibilityDelegate);
- }
-
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@@ -427,19 +277,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
getResources().getDimensionPixelSize(
com.android.internal.R.dimen.text_size_small_material));
- ViewGroup.LayoutParams lp = mRightAffordanceView.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
- lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
- mRightAffordanceView.setLayoutParams(lp);
- updateRightAffordanceIcon();
-
- lp = mLeftAffordanceView.getLayoutParams();
- lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_width);
- lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_height);
- mLeftAffordanceView.setLayoutParams(lp);
- updateLeftAffordanceIcon();
-
- lp = mWalletButton.getLayoutParams();
+ ViewGroup.LayoutParams lp = mWalletButton.getLayoutParams();
lp.width = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width);
lp.height = getResources().getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height);
mWalletButton.setLayoutParams(lp);
@@ -462,76 +300,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
updateAffordanceColors();
}
- private void updateRightAffordanceIcon() {
- IconState state = mRightButton.getIcon();
- mRightAffordanceView.setVisibility(!mDozing && state.isVisible ? View.VISIBLE : View.GONE);
- if (state.drawable != mRightAffordanceView.getDrawable()
- || state.tint != mRightAffordanceView.shouldTint()) {
- mRightAffordanceView.setImageDrawable(state.drawable, state.tint);
- }
- mRightAffordanceView.setContentDescription(state.contentDescription);
- }
-
- public void setCentralSurfaces(CentralSurfaces centralSurfaces) {
- mCentralSurfaces = centralSurfaces;
- updateCameraVisibility(); // in case onFinishInflate() was called too early
- }
-
- public void setAffordanceHelper(KeyguardAffordanceHelper affordanceHelper) {
- mAffordanceHelper = affordanceHelper;
- }
-
- public void setUserSetupComplete(boolean userSetupComplete) {
- mUserSetupComplete = userSetupComplete;
- updateCameraVisibility();
- updateLeftAffordanceIcon();
- }
-
- private Intent getCameraIntent() {
- return mRightButton.getIntent();
- }
-
- /**
- * Resolves the intent to launch the camera application.
- */
- public ResolveInfo resolveCameraIntent() {
- return mContext.getPackageManager().resolveActivityAsUser(getCameraIntent(),
- PackageManager.MATCH_DEFAULT_ONLY,
- KeyguardUpdateMonitor.getCurrentUser());
- }
-
- private void updateCameraVisibility() {
- if (mRightAffordanceView == null) {
- // Things are not set up yet; reply hazy, ask again later
- return;
- }
- mRightAffordanceView.setVisibility(!mDozing && mShowCameraAffordance
- && mRightButton.getIcon().isVisible ? View.VISIBLE : View.GONE);
- }
-
- /**
- * Set an alternate icon for the left assist affordance (replace the mic icon)
- */
- public void setLeftAssistIcon(Drawable drawable) {
- mLeftAssistIcon = drawable;
- updateLeftAffordanceIcon();
- }
-
- private void updateLeftAffordanceIcon() {
- if (!mShowLeftAffordance || mDozing) {
- mLeftAffordanceView.setVisibility(GONE);
- return;
- }
-
- IconState state = mLeftButton.getIcon();
- mLeftAffordanceView.setVisibility(state.isVisible ? View.VISIBLE : View.GONE);
- if (state.drawable != mLeftAffordanceView.getDrawable()
- || state.tint != mLeftAffordanceView.shouldTint()) {
- mLeftAffordanceView.setImageDrawable(state.drawable, state.tint);
- }
- mLeftAffordanceView.setContentDescription(state.contentDescription);
- }
-
private void updateWalletVisibility() {
if (mDozing
|| mQuickAccessWalletController == null
@@ -575,128 +343,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
}
- public boolean isLeftVoiceAssist() {
- return mLeftIsVoiceAssist;
- }
-
- private boolean isPhoneVisible() {
- PackageManager pm = mContext.getPackageManager();
- return pm.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)
- && pm.resolveActivity(PHONE_INTENT, 0) != null;
- }
-
- @Override
- public void onStateChanged(boolean accessibilityEnabled, boolean touchExplorationEnabled) {
- mRightAffordanceView.setClickable(touchExplorationEnabled);
- mLeftAffordanceView.setClickable(touchExplorationEnabled);
- mRightAffordanceView.setFocusable(accessibilityEnabled);
- mLeftAffordanceView.setFocusable(accessibilityEnabled);
- }
-
- @Override
- public void onClick(View v) {
- if (v == mRightAffordanceView) {
- launchCamera(CAMERA_LAUNCH_SOURCE_AFFORDANCE);
- } else if (v == mLeftAffordanceView) {
- launchLeftAffordance();
- }
- }
-
- public void bindCameraPrewarmService() {
- Intent intent = getCameraIntent();
- ActivityInfo targetInfo = mActivityIntentHelper.getTargetActivityInfo(intent,
- KeyguardUpdateMonitor.getCurrentUser(), true /* onlyDirectBootAware */);
- if (targetInfo != null && targetInfo.metaData != null) {
- String clazz = targetInfo.metaData.getString(
- MediaStore.META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE);
- if (clazz != null) {
- Intent serviceIntent = new Intent();
- serviceIntent.setClassName(targetInfo.packageName, clazz);
- serviceIntent.setAction(CameraPrewarmService.ACTION_PREWARM);
- try {
- if (getContext().bindServiceAsUser(serviceIntent, mPrewarmConnection,
- Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
- new UserHandle(UserHandle.USER_CURRENT))) {
- mPrewarmBound = true;
- }
- } catch (SecurityException e) {
- Log.w(TAG, "Unable to bind to prewarm service package=" + targetInfo.packageName
- + " class=" + clazz, e);
- }
- }
- }
- }
-
- public void unbindCameraPrewarmService(boolean launched) {
- if (mPrewarmBound) {
- if (mPrewarmMessenger != null && launched) {
- try {
- mPrewarmMessenger.send(Message.obtain(null /* handler */,
- CameraPrewarmService.MSG_CAMERA_FIRED));
- } catch (RemoteException e) {
- Log.w(TAG, "Error sending camera fired message", e);
- }
- }
- mContext.unbindService(mPrewarmConnection);
- mPrewarmBound = false;
- }
- }
-
- public void launchCamera(String source) {
- final Intent intent = getCameraIntent();
- intent.putExtra(EXTRA_CAMERA_LAUNCH_SOURCE, source);
- boolean wouldLaunchResolverActivity = mActivityIntentHelper.wouldLaunchResolverActivity(
- intent, KeyguardUpdateMonitor.getCurrentUser());
- if (CameraIntents.isSecureCameraIntent(intent) && !wouldLaunchResolverActivity) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- int result = ActivityManager.START_CANCELED;
-
- // Normally an activity will set it's requested rotation
- // animation on its window. However when launching an activity
- // causes the orientation to change this is too late. In these cases
- // the default animation is used. This doesn't look good for
- // the camera (as it rotates the camera contents out of sync
- // with physical reality). So, we ask the WindowManager to
- // force the crossfade animation if an orientation change
- // happens to occur during the launch.
- ActivityOptions o = ActivityOptions.makeBasic();
- o.setDisallowEnterPictureInPictureWhileLaunching(true);
- o.setRotationAnimationHint(
- WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
- try {
- result = ActivityTaskManager.getService().startActivityAsUser(
- null, getContext().getBasePackageName(),
- getContext().getAttributionTag(), intent,
- intent.resolveTypeIfNeeded(getContext().getContentResolver()),
- null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, o.toBundle(),
- UserHandle.CURRENT.getIdentifier());
- } catch (RemoteException e) {
- Log.w(TAG, "Unable to start camera activity", e);
- }
- final boolean launched = isSuccessfulLaunch(result);
- post(new Runnable() {
- @Override
- public void run() {
- unbindCameraPrewarmService(launched);
- }
- });
- }
- });
- } else {
- // We need to delay starting the activity because ResolverActivity finishes itself if
- // launched behind lockscreen.
- mActivityStarter.startActivity(intent, false /* dismissShade */,
- new ActivityStarter.Callback() {
- @Override
- public void onActivityStarted(int resultCode) {
- unbindCameraPrewarmService(isSuccessfulLaunch(resultCode));
- }
- });
- }
- }
-
public void setDarkAmount(float darkAmount) {
if (darkAmount == mDarkAmount) {
return;
@@ -705,85 +351,17 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
dozeTimeTick();
}
- private static boolean isSuccessfulLaunch(int result) {
- return result == ActivityManager.START_SUCCESS
- || result == ActivityManager.START_DELIVERED_TO_TOP
- || result == ActivityManager.START_TASK_TO_FRONT;
- }
-
- public void launchLeftAffordance() {
- if (mLeftIsVoiceAssist) {
- launchVoiceAssist();
- } else {
- launchPhone();
- }
- }
-
- @VisibleForTesting
- void launchVoiceAssist() {
- Runnable runnable = new Runnable() {
- @Override
- public void run() {
- Dependency.get(AssistManager.class).launchVoiceAssistFromKeyguard();
- }
- };
- if (!mKeyguardStateController.canDismissLockScreen()) {
- Dependency.get(Dependency.BACKGROUND_EXECUTOR).execute(runnable);
- } else {
- boolean dismissShade = !TextUtils.isEmpty(mRightButtonStr)
- && Dependency.get(TunerService.class).getValue(LOCKSCREEN_RIGHT_UNLOCK, 1) != 0;
- mCentralSurfaces.executeRunnableDismissingKeyguard(runnable, null /* cancelAction */,
- dismissShade, false /* afterKeyguardGone */, true /* deferred */);
- }
- }
-
- private boolean canLaunchVoiceAssist() {
- return Dependency.get(AssistManager.class).canVoiceAssistBeLaunchedFromKeyguard();
- }
-
- private void launchPhone() {
- final TelecomManager tm = TelecomManager.from(mContext);
- if (tm.isInCall()) {
- AsyncTask.execute(new Runnable() {
- @Override
- public void run() {
- tm.showInCallScreen(false /* showDialpad */);
- }
- });
- } else {
- boolean dismissShade = !TextUtils.isEmpty(mLeftButtonStr)
- && Dependency.get(TunerService.class).getValue(LOCKSCREEN_LEFT_UNLOCK, 1) != 0;
- mActivityStarter.startActivity(mLeftButton.getIntent(), dismissShade);
- }
- }
-
-
- @Override
- protected void onVisibilityChanged(View changedView, int visibility) {
- super.onVisibilityChanged(changedView, visibility);
- if (changedView == this && visibility == VISIBLE) {
- updateCameraVisibility();
+ /**
+ * Returns a list of animators to use to animate the indication areas.
+ */
+ public List<ViewPropertyAnimator> getIndicationAreaAnimators() {
+ List<ViewPropertyAnimator> animators =
+ new ArrayList<>(mAmbientIndicationArea != null ? 2 : 1);
+ animators.add(mIndicationArea.animate());
+ if (mAmbientIndicationArea != null) {
+ animators.add(mAmbientIndicationArea.animate());
}
- }
-
- public KeyguardAffordanceView getLeftView() {
- return mLeftAffordanceView;
- }
-
- public KeyguardAffordanceView getRightView() {
- return mRightAffordanceView;
- }
-
- public View getLeftPreview() {
- return mLeftPreview;
- }
-
- public View getRightPreview() {
- return mCameraPreview;
- }
-
- public View getIndicationArea() {
- return mIndicationArea;
+ return animators;
}
@Override
@@ -791,67 +369,7 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
return false;
}
- @Override
- public void onUnlockedChanged() {
- updateCameraVisibility();
- }
-
- @Override
- public void onKeyguardShowingChanged() {
- if (mKeyguardStateController.isShowing()) {
- if (mQuickAccessWalletController != null) {
- mQuickAccessWalletController.queryWalletCards(mCardRetriever);
- }
- }
- }
-
- private void inflateCameraPreview() {
- if (mPreviewContainer == null) {
- return;
- }
- View previewBefore = mCameraPreview;
- boolean visibleBefore = false;
- if (previewBefore != null) {
- mPreviewContainer.removeView(previewBefore);
- visibleBefore = previewBefore.getVisibility() == View.VISIBLE;
- }
- mCameraPreview = mPreviewInflater.inflatePreview(getCameraIntent());
- if (mCameraPreview != null) {
- mPreviewContainer.addView(mCameraPreview);
- mCameraPreview.setVisibility(visibleBefore ? View.VISIBLE : View.INVISIBLE);
- }
- if (mAffordanceHelper != null) {
- mAffordanceHelper.updatePreviews();
- }
- }
-
- private void updateLeftPreview() {
- if (mPreviewContainer == null) {
- return;
- }
- View previewBefore = mLeftPreview;
- if (previewBefore != null) {
- mPreviewContainer.removeView(previewBefore);
- }
-
- if (mLeftIsVoiceAssist) {
- if (Dependency.get(AssistManager.class).getVoiceInteractorComponentName() != null) {
- mLeftPreview = mPreviewInflater.inflatePreviewFromService(
- Dependency.get(AssistManager.class).getVoiceInteractorComponentName());
- }
- } else {
- mLeftPreview = mPreviewInflater.inflatePreview(mLeftButton.getIntent());
- }
- if (mLeftPreview != null) {
- mPreviewContainer.addView(mLeftPreview);
- mLeftPreview.setVisibility(View.INVISIBLE);
- }
- if (mAffordanceHelper != null) {
- mAffordanceHelper.updatePreviews();
- }
- }
-
- public void startFinishDozeAnimation() {
+ private void startFinishDozeAnimation() {
long delay = 0;
if (mWalletButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mWalletButton, delay);
@@ -862,13 +380,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
if (mControlsButton.getVisibility() == View.VISIBLE) {
startFinishDozeAnimationElement(mControlsButton, delay);
}
- if (mLeftAffordanceView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mLeftAffordanceView, delay);
- delay += DOZE_ANIMATION_STAGGER_DELAY;
- }
- if (mRightAffordanceView.getVisibility() == View.VISIBLE) {
- startFinishDozeAnimationElement(mRightAffordanceView, delay);
- }
}
private void startFinishDozeAnimationElement(View element, long delay) {
@@ -882,58 +393,9 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
.setDuration(DOZE_ANIMATION_ELEMENT_DURATION);
}
- private final BroadcastReceiver mDevicePolicyReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- post(new Runnable() {
- @Override
- public void run() {
- updateCameraVisibility();
- }
- });
- }
- };
-
- private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
- new KeyguardUpdateMonitorCallback() {
- @Override
- public void onUserSwitchComplete(int userId) {
- updateCameraVisibility();
- }
-
- @Override
- public void onUserUnlocked() {
- inflateCameraPreview();
- updateCameraVisibility();
- updateLeftAffordance();
- }
- };
-
- public void updateLeftAffordance() {
- updateLeftAffordanceIcon();
- updateLeftPreview();
- }
-
- private void setRightButton(IntentButton button) {
- mRightButton = button;
- updateRightAffordanceIcon();
- updateCameraVisibility();
- inflateCameraPreview();
- }
-
- private void setLeftButton(IntentButton button) {
- mLeftButton = button;
- if (!(mLeftButton instanceof DefaultLeftButton)) {
- mLeftIsVoiceAssist = false;
- }
- updateLeftAffordance();
- }
-
public void setDozing(boolean dozing, boolean animate) {
mDozing = dozing;
- updateCameraVisibility();
- updateLeftAffordanceIcon();
updateWalletVisibility();
updateControlsVisibility();
updateQRCodeButtonVisibility();
@@ -969,80 +431,23 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
}
/**
- * Sets the alpha of the indication areas and affordances, excluding the lock icon.
+ * Sets the alpha of various sub-components, for example the indication areas and bottom quick
+ * action buttons. Does not set the alpha of the lock icon.
*/
- public void setAffordanceAlpha(float alpha) {
- mLeftAffordanceView.setAlpha(alpha);
- mRightAffordanceView.setAlpha(alpha);
+ public void setComponentAlphas(float alpha) {
+ setImportantForAccessibility(
+ alpha == 0f
+ ? View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
+ : View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
+ if (mAmbientIndicationArea != null) {
+ mAmbientIndicationArea.setAlpha(alpha);
+ }
mIndicationArea.setAlpha(alpha);
mWalletButton.setAlpha(alpha);
mQRCodeScannerButton.setAlpha(alpha);
mControlsButton.setAlpha(alpha);
}
- private class DefaultLeftButton implements IntentButton {
-
- private IconState mIconState = new IconState();
-
- @Override
- public IconState getIcon() {
- mLeftIsVoiceAssist = canLaunchVoiceAssist();
- if (mLeftIsVoiceAssist) {
- mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance;
- if (mLeftAssistIcon == null) {
- mIconState.drawable = mContext.getDrawable(R.drawable.ic_mic_26dp);
- } else {
- mIconState.drawable = mLeftAssistIcon;
- }
- mIconState.contentDescription = mContext.getString(
- R.string.accessibility_voice_assist_button);
- } else {
- mIconState.isVisible = mUserSetupComplete && mShowLeftAffordance
- && isPhoneVisible();
- mIconState.drawable = mContext.getDrawable(
- com.android.internal.R.drawable.ic_phone);
- mIconState.contentDescription = mContext.getString(
- R.string.accessibility_phone_button);
- }
- return mIconState;
- }
-
- @Override
- public Intent getIntent() {
- return PHONE_INTENT;
- }
- }
-
- private class DefaultRightButton implements IntentButton {
-
- private IconState mIconState = new IconState();
-
- @Override
- public IconState getIcon() {
- boolean isCameraDisabled = (mCentralSurfaces != null)
- && !mCentralSurfaces.isCameraAllowedByAdmin();
- mIconState.isVisible = !isCameraDisabled
- && mShowCameraAffordance
- && mUserSetupComplete
- && resolveCameraIntent() != null;
- mIconState.drawable = mContext.getDrawable(R.drawable.ic_camera_alt_24dp);
- mIconState.contentDescription =
- mContext.getString(R.string.accessibility_camera_button);
- return mIconState;
- }
-
- @Override
- public Intent getIntent() {
- boolean canDismissLs = mKeyguardStateController.canDismissLockScreen();
- boolean secure = mKeyguardStateController.isMethodSecure();
- if (secure && !canDismissLs) {
- return CameraIntents.getSecureCameraIntent(getContext());
- } else {
- return CameraIntents.getInsecureCameraIntent(getContext());
- }
- }
- }
-
@Override
public WindowInsets onApplyWindowInsets(WindowInsets insets) {
int bottom = insets.getDisplayCutout() != null
@@ -1055,38 +460,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
return insets;
}
- /** Set the falsing manager */
- public void setFalsingManager(FalsingManager falsingManager) {
- mFalsingManager = falsingManager;
- }
-
- /**
- * Initialize the wallet feature, only enabling if the feature is enabled within the platform.
- */
- public void initWallet(
- QuickAccessWalletController controller) {
- mQuickAccessWalletController = controller;
- mQuickAccessWalletController.setupWalletChangeObservers(
- mCardRetriever, WALLET_PREFERENCE_CHANGE, DEFAULT_PAYMENT_APP_CHANGE);
- mQuickAccessWalletController.updateWalletPreference();
- mQuickAccessWalletController.queryWalletCards(mCardRetriever);
-
- updateWalletVisibility();
- updateAffordanceColors();
- }
-
- /**
- * Initialize the qr code scanner feature, controlled by QRCodeScannerController.
- */
- public void initQRCodeScanner(QRCodeScannerController qrCodeScannerController) {
- mQRCodeScannerController = qrCodeScannerController;
- mQRCodeScannerController.registerQRCodeScannerChangeObservers(
- QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
- QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE);
- updateQRCodeButtonVisibility();
- updateAffordanceColors();
- }
-
private void updateQRCodeButtonVisibility() {
if (mQuickAccessWalletController != null
&& mQuickAccessWalletController.isWalletEnabled()) {
@@ -1142,17 +515,6 @@ public class KeyguardBottomAreaView extends FrameLayout implements View.OnClickL
mQRCodeScannerButton.setBackgroundTintList(bgColor);
}
- /**
- * Initialize controls via the ControlsComponent
- */
- public void initControls(ControlsComponent controlsComponent) {
- mControlsComponent = controlsComponent;
- mControlsComponent.getControlsListingController().ifPresent(
- c -> c.addCallback(mListingCallback));
-
- updateAffordanceColors();
- }
-
private void onWalletClick(View v) {
// More coming here; need to inform the user about how to proceed
if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
new file mode 100644
index 000000000000..3942dae7e0c3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaViewController.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.util.ViewController
+import javax.inject.Inject
+
+class KeyguardBottomAreaViewController @Inject constructor(view: KeyguardBottomAreaView) :
+ ViewController<KeyguardBottomAreaView> (view) {
+ override fun onViewAttached() {
+ }
+
+ override fun onViewDetached() {
+ }
+
+ fun getView(): KeyguardBottomAreaView {
+ // TODO: remove this method.
+ return mView
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index a9048002fb44..0001cd0a2798 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -266,6 +266,9 @@ public class KeyguardBouncer {
private void setVisibility(@View.Visibility int visibility) {
mContainer.setVisibility(visibility);
+ if (mKeyguardViewController != null) {
+ mKeyguardViewController.onBouncerVisibilityChanged(visibility);
+ }
dispatchVisibilityChanged();
}
@@ -640,6 +643,10 @@ public class KeyguardBouncer {
public interface BouncerExpansionCallback {
/**
* Invoked when the bouncer expansion reaches {@link KeyguardBouncer#EXPANSION_VISIBLE}.
+ * This is NOT called each time the bouncer is shown, but rather only when the fully
+ * shown amount has changed based on the panel expansion. The bouncer's visibility
+ * can still change when the expansion amount hasn't changed.
+ * See {@link KeyguardBouncer#isShowing()} for the checks for the bouncer showing state.
*/
default void onFullyShown() {
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
index 629aa0317d24..01af48616b65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java
@@ -27,6 +27,7 @@ import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.keyguard.KeyguardStatusView;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.policy.KeyguardUserSwitcherListView;
/**
@@ -309,9 +310,12 @@ public class KeyguardClockPositionAlgorithm {
*/
private float getClockAlpha(int y) {
float alphaKeyguard = Math.max(0, y / Math.max(1f, getClockY(1f, mDarkAmount)));
- float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f);
- qsAlphaFactor = 1f - qsAlphaFactor;
- alphaKeyguard *= qsAlphaFactor;
+ if (!mIsSplitShade) {
+ // in split shade QS are always expanded so this factor shouldn't apply
+ float qsAlphaFactor = MathUtils.saturate(mQsExpansion / 0.3f);
+ qsAlphaFactor = 1f - qsAlphaFactor;
+ alphaKeyguard *= qsAlphaFactor;
+ }
alphaKeyguard = Interpolators.ACCELERATE.getInterpolation(alphaKeyguard);
return MathUtils.lerp(alphaKeyguard, 1f, mDarkAmount);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
index def574c1890f..f06b346780da 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewController.java
@@ -42,6 +42,7 @@ import com.android.systemui.animation.Interpolators;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt
deleted file mode 100644
index 178c17dd5694..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderController.kt
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.app.StatusBarManager
-import android.view.View
-import android.widget.TextView
-import androidx.constraintlayout.motion.widget.MotionLayout
-import com.android.settingslib.Utils
-import com.android.systemui.Dumpable
-import com.android.systemui.FontSizeUtils
-import com.android.systemui.R
-import com.android.systemui.animation.ShadeInterpolation
-import com.android.systemui.battery.BatteryMeterView
-import com.android.systemui.battery.BatteryMeterViewController
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
-import com.android.systemui.qs.ChipVisibilityListener
-import com.android.systemui.qs.HeaderPrivacyIconsController
-import com.android.systemui.qs.carrier.QSCarrierGroup
-import com.android.systemui.qs.carrier.QSCarrierGroupController
-import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
-import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_BATTERY_CONTROLLER
-import com.android.systemui.statusbar.phone.dagger.StatusBarViewModule.LARGE_SCREEN_SHADE_HEADER
-import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
-import javax.inject.Inject
-import javax.inject.Named
-
-@CentralSurfacesScope
-class LargeScreenShadeHeaderController @Inject constructor(
- @Named(LARGE_SCREEN_SHADE_HEADER) private val header: View,
- private val statusBarIconController: StatusBarIconController,
- private val privacyIconsController: HeaderPrivacyIconsController,
- private val configurationController: ConfigurationController,
- qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder,
- featureFlags: FeatureFlags,
- @Named(LARGE_SCREEN_BATTERY_CONTROLLER) batteryMeterViewController: BatteryMeterViewController,
- dumpManager: DumpManager
-) : Dumpable {
-
- companion object {
- private val HEADER_TRANSITION_ID = R.id.header_transition
- private val LARGE_SCREEN_HEADER_TRANSITION_ID = R.id.large_screen_header_transition
- private val QQS_HEADER_CONSTRAINT = R.id.qqs_header_constraint
- private val QS_HEADER_CONSTRAINT = R.id.qs_header_constraint
- private val LARGE_SCREEN_HEADER_CONSTRAINT = R.id.large_screen_header_constraint
-
- private fun Int.stateToString() = when (this) {
- QQS_HEADER_CONSTRAINT -> "QQS Header"
- QS_HEADER_CONSTRAINT -> "QS Header"
- LARGE_SCREEN_HEADER_CONSTRAINT -> "Large Screen Header"
- else -> "Unknown state"
- }
- }
-
- private val combinedHeaders = featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)
- private val iconManager: StatusBarIconController.TintedIconManager
- private val iconContainer: StatusIconContainer
- private val carrierIconSlots: List<String>
- private val qsCarrierGroupController: QSCarrierGroupController
- private val clock: TextView = header.findViewById(R.id.clock)
- private val date: TextView = header.findViewById(R.id.date)
- private val qsCarrierGroup: QSCarrierGroup = header.findViewById(R.id.carrier_group)
-
- private var qsDisabled = false
-
- private var visible = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- updateListeners()
- }
-
- var shadeExpanded = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- onShadeExpandedChanged()
- }
-
- var active = false
- set(value) {
- if (field == value) {
- return
- }
- field = value
- onHeaderStateChanged()
- }
-
- var shadeExpandedFraction = -1f
- set(value) {
- if (visible && field != value) {
- header.alpha = ShadeInterpolation.getContentAlpha(value)
- field = value
- }
- }
-
- var qsExpandedFraction = -1f
- set(value) {
- if (visible && field != value) {
- field = value
- updateVisibility()
- updatePosition()
- }
- }
-
- var qsScrollY = 0
- set(value) {
- if (field != value) {
- field = value
- updateScrollY()
- }
- }
-
- private val chipVisibilityListener: ChipVisibilityListener = object : ChipVisibilityListener {
- override fun onChipVisibilityRefreshed(visible: Boolean) {
- if (header is MotionLayout) {
- val state = header.getConstraintSet(QQS_HEADER_CONSTRAINT).apply {
- setAlpha(R.id.statusIcons, if (visible) 0f else 1f)
- setAlpha(R.id.batteryRemainingIcon, if (visible) 0f else 1f)
- }
- header.updateState(QQS_HEADER_CONSTRAINT, state)
- }
- }
- }
-
- init {
- if (header is MotionLayout) {
- val context = header.context
- val resources = header.resources
- header.getConstraintSet(QQS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qqs_header))
- header.getConstraintSet(QS_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.qs_header))
- header.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT)
- .load(context, resources.getXml(R.xml.large_screen_shade_header))
- privacyIconsController.chipVisibilityListener = chipVisibilityListener
- }
-
- bindConfigurationListener()
-
- batteryMeterViewController.init()
- val batteryIcon: BatteryMeterView = header.findViewById(R.id.batteryRemainingIcon)
-
- // battery settings same as in QS icons
- batteryMeterViewController.ignoreTunerUpdates()
- batteryIcon.setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
-
- iconContainer = header.findViewById(R.id.statusIcons)
- iconManager = StatusBarIconController.TintedIconManager(iconContainer, featureFlags)
- iconManager.setTint(Utils.getColorAttrDefaultColor(header.context,
- android.R.attr.textColorPrimary))
-
- carrierIconSlots = if (featureFlags.isEnabled(Flags.COMBINED_STATUS_BAR_SIGNAL_ICONS)) {
- listOf(
- header.context.getString(com.android.internal.R.string.status_bar_no_calling),
- header.context.getString(com.android.internal.R.string.status_bar_call_strength)
- )
- } else {
- listOf(header.context.getString(com.android.internal.R.string.status_bar_mobile))
- }
- qsCarrierGroupController = qsCarrierGroupControllerBuilder
- .setQSCarrierGroup(header.findViewById(R.id.carrier_group))
- .build()
-
- dumpManager.registerDumpable(this)
-
- updateVisibility()
- updateConstraints()
- }
-
- fun disable(state1: Int, state2: Int, animate: Boolean) {
- val disabled = state2 and StatusBarManager.DISABLE2_QUICK_SETTINGS != 0
- if (disabled == qsDisabled) return
- qsDisabled = disabled
- updateVisibility()
- }
-
- private fun updateScrollY() {
- if (!active && combinedHeaders) {
- header.scrollY = qsScrollY
- }
- }
-
- private fun bindConfigurationListener() {
- val listener = object : ConfigurationController.ConfigurationListener {
- override fun onDensityOrFontScaleChanged() {
- val qsStatusStyle = R.style.TextAppearance_QS_Status
- FontSizeUtils.updateFontSizeFromStyle(clock, qsStatusStyle)
- FontSizeUtils.updateFontSizeFromStyle(date, qsStatusStyle)
- qsCarrierGroup.updateTextAppearance(qsStatusStyle)
- }
- }
- configurationController.addCallback(listener)
- }
-
- private fun onShadeExpandedChanged() {
- if (shadeExpanded) {
- privacyIconsController.startListening()
- } else {
- privacyIconsController.stopListening()
- }
- updateVisibility()
- updatePosition()
- }
-
- private fun onHeaderStateChanged() {
- if (active || combinedHeaders) {
- privacyIconsController.onParentVisible()
- } else {
- privacyIconsController.onParentInvisible()
- }
- updateVisibility()
- updateConstraints()
- }
-
- private fun updateVisibility() {
- val visibility = if (!active && !combinedHeaders || qsDisabled) {
- View.GONE
- } else if (shadeExpanded) {
- View.VISIBLE
- } else {
- View.INVISIBLE
- }
- if (header.visibility != visibility) {
- header.visibility = visibility
- visible = visibility == View.VISIBLE
- }
- }
-
- private fun updateConstraints() {
- if (!combinedHeaders) {
- return
- }
- header as MotionLayout
- if (active) {
- header.setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
- } else {
- header.setTransition(HEADER_TRANSITION_ID)
- header.transitionToStart()
- updatePosition()
- updateScrollY()
- }
- }
-
- private fun updatePosition() {
- if (header is MotionLayout && !active && visible) {
- header.setProgress(qsExpandedFraction)
- }
- }
-
- private fun updateListeners() {
- qsCarrierGroupController.setListening(visible)
- if (visible) {
- updateSingleCarrier(qsCarrierGroupController.isSingleCarrier)
- qsCarrierGroupController.setOnSingleCarrierChangedListener { updateSingleCarrier(it) }
- statusBarIconController.addIconGroup(iconManager)
- } else {
- qsCarrierGroupController.setOnSingleCarrierChangedListener(null)
- statusBarIconController.removeIconGroup(iconManager)
- }
- }
-
- private fun updateSingleCarrier(singleCarrier: Boolean) {
- if (singleCarrier) {
- iconContainer.removeIgnoredSlots(carrierIconSlots)
- } else {
- iconContainer.addIgnoredSlots(carrierIconSlots)
- }
- }
-
- override fun dump(pw: PrintWriter, args: Array<out String>) {
- pw.println("visible: $visible")
- pw.println("shadeExpanded: $shadeExpanded")
- pw.println("shadeExpandedFraction: $shadeExpandedFraction")
- pw.println("active: $active")
- pw.println("qsExpandedFraction: $qsExpandedFraction")
- pw.println("qsScrollY: $qsScrollY")
- if (combinedHeaders) {
- header as MotionLayout
- pw.println("currentState: ${header.currentState.stateToString()}")
- }
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
new file mode 100644
index 000000000000..70ec13b14abd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculator.kt
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.annotation.ColorInt
+import android.graphics.Color
+import android.graphics.Rect
+import android.view.InsetsFlags
+import android.view.ViewDebug
+import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
+import android.view.WindowInsetsController.Appearance
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.util.ContrastColorUtil
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import java.io.PrintWriter
+import java.util.Arrays
+import javax.inject.Inject
+
+class LetterboxAppearance(
+ @Appearance val appearance: Int,
+ val appearanceRegions: Array<AppearanceRegion>
+)
+
+/**
+ * Responsible for calculating the [Appearance] and [AppearanceRegion] for the status bar when apps
+ * are letterboxed.
+ */
+@CentralSurfacesScope
+class LetterboxAppearanceCalculator
+@Inject
+constructor(
+ private val lightBarController: LightBarController,
+ private val dumpManager: DumpManager,
+) : OnStatusBarViewInitializedListener, CentralSurfacesComponent.Startable {
+
+ private var statusBarBoundsProvider: StatusBarBoundsProvider? = null
+
+ override fun start() {
+ dumpManager.registerDumpable(javaClass.simpleName) { printWriter, _ -> dump(printWriter) }
+ }
+
+ override fun stop() {
+ dumpManager.unregisterDumpable(javaClass.simpleName)
+ }
+
+ private var lastAppearance: Int? = null
+ private var lastAppearanceRegions: Array<AppearanceRegion>? = null
+ private var lastLetterboxes: Array<LetterboxDetails>? = null
+ private var lastLetterboxAppearance: LetterboxAppearance? = null
+
+ fun getLetterboxAppearance(
+ @Appearance originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ letterboxes: Array<LetterboxDetails>
+ ): LetterboxAppearance {
+ lastAppearance = originalAppearance
+ lastAppearanceRegions = originalAppearanceRegions
+ lastLetterboxes = letterboxes
+ return getLetterboxAppearanceInternal(
+ letterboxes, originalAppearance, originalAppearanceRegions)
+ .also { lastLetterboxAppearance = it }
+ }
+
+ private fun getLetterboxAppearanceInternal(
+ letterboxes: Array<LetterboxDetails>,
+ originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>
+ ): LetterboxAppearance {
+ if (isScrimNeeded(letterboxes)) {
+ return originalAppearanceWithScrim(originalAppearance, originalAppearanceRegions)
+ }
+ val appearance = appearanceWithoutScrim(originalAppearance)
+ val appearanceRegions = getAppearanceRegions(originalAppearanceRegions, letterboxes)
+ return LetterboxAppearance(appearance, appearanceRegions.toTypedArray())
+ }
+
+ private fun isScrimNeeded(letterboxes: Array<LetterboxDetails>): Boolean {
+ if (isOuterLetterboxMultiColored()) {
+ return true
+ }
+ return letterboxes.any { letterbox ->
+ letterbox.letterboxInnerBounds.overlapsWith(getStartSideIconBounds()) ||
+ letterbox.letterboxInnerBounds.overlapsWith(getEndSideIconsBounds())
+ }
+ }
+
+ private fun getAppearanceRegions(
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ letterboxes: Array<LetterboxDetails>
+ ): List<AppearanceRegion> {
+ return sanitizeAppearanceRegions(originalAppearanceRegions, letterboxes) +
+ getAllOuterAppearanceRegions(letterboxes)
+ }
+
+ private fun sanitizeAppearanceRegions(
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ letterboxes: Array<LetterboxDetails>
+ ): List<AppearanceRegion> =
+ originalAppearanceRegions.map { appearanceRegion ->
+ val matchingLetterbox =
+ letterboxes.find { it.letterboxFullBounds == appearanceRegion.bounds }
+ if (matchingLetterbox == null) {
+ appearanceRegion
+ } else {
+ // When WindowManager sends appearance regions for an app, it sends them for the
+ // full bounds of its window.
+ // Here we want the bounds to be only for the inner bounds of the letterboxed app.
+ AppearanceRegion(
+ appearanceRegion.appearance, matchingLetterbox.letterboxInnerBounds)
+ }
+ }
+
+ private fun originalAppearanceWithScrim(
+ @Appearance originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>
+ ): LetterboxAppearance {
+ return LetterboxAppearance(
+ originalAppearance or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS,
+ originalAppearanceRegions)
+ }
+
+ @Appearance
+ private fun appearanceWithoutScrim(@Appearance originalAppearance: Int): Int =
+ originalAppearance and APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS.inv()
+
+ private fun getAllOuterAppearanceRegions(
+ letterboxes: Array<LetterboxDetails>
+ ): List<AppearanceRegion> = letterboxes.map(this::getOuterAppearanceRegions).flatten()
+
+ private fun getOuterAppearanceRegions(
+ letterboxDetails: LetterboxDetails
+ ): List<AppearanceRegion> {
+ @Appearance val outerAppearance = getOuterAppearance()
+ return getVisibleOuterBounds(letterboxDetails).map { bounds ->
+ AppearanceRegion(outerAppearance, bounds)
+ }
+ }
+
+ private fun getVisibleOuterBounds(letterboxDetails: LetterboxDetails): List<Rect> {
+ val inner = letterboxDetails.letterboxInnerBounds
+ val outer = letterboxDetails.letterboxFullBounds
+ val top = Rect(outer.left, outer.top, outer.right, inner.top)
+ val left = Rect(outer.left, outer.top, inner.left, outer.bottom)
+ val right = Rect(inner.right, outer.top, outer.right, outer.bottom)
+ val bottom = Rect(outer.left, inner.bottom, outer.right, outer.bottom)
+ return listOf(left, top, right, bottom).filter { !it.isEmpty }
+ }
+
+ @Appearance
+ private fun getOuterAppearance(): Int {
+ val backgroundColor = outerLetterboxBackgroundColor()
+ val darkAppearanceContrast =
+ ContrastColorUtil.calculateContrast(
+ lightBarController.darkAppearanceIconColor, backgroundColor)
+ val lightAppearanceContrast =
+ ContrastColorUtil.calculateContrast(
+ lightBarController.lightAppearanceIconColor, backgroundColor)
+ return if (lightAppearanceContrast > darkAppearanceContrast) {
+ WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+ } else {
+ 0 // APPEARANCE_DEFAULT
+ }
+ }
+
+ @ColorInt
+ private fun outerLetterboxBackgroundColor(): Int {
+ // TODO(b/238607453): retrieve this information from WindowManager.
+ return Color.BLACK
+ }
+
+ private fun isOuterLetterboxMultiColored(): Boolean {
+ // TODO(b/238607453): retrieve this information from WindowManager.
+ return false
+ }
+
+ private fun getEndSideIconsBounds(): Rect {
+ return statusBarBoundsProvider?.visibleEndSideBounds ?: Rect()
+ }
+
+ private fun getStartSideIconBounds(): Rect {
+ return statusBarBoundsProvider?.visibleStartSideBounds ?: Rect()
+ }
+
+ override fun onStatusBarViewInitialized(component: StatusBarFragmentComponent) {
+ statusBarBoundsProvider = component.boundsProvider
+ }
+
+ private fun Rect.overlapsWith(other: Rect): Boolean {
+ if (this.contains(other) || other.contains(this)) {
+ return false
+ }
+ return this.intersect(other)
+ }
+
+ private fun dump(printWriter: PrintWriter) {
+ printWriter.println(
+ """
+ lastAppearance: ${lastAppearance?.toAppearanceString()}
+ lastAppearanceRegion: ${Arrays.toString(lastAppearanceRegions)},
+ lastLetterboxes: ${Arrays.toString(lastLetterboxes)},
+ lastLetterboxAppearance: $lastLetterboxAppearance
+ """.trimIndent())
+ }
+}
+
+private fun Int.toAppearanceString(): String =
+ ViewDebug.flagsToString(InsetsFlags::class.java, "appearance", this)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index 2c57328174e8..4d1454232853 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -23,8 +23,8 @@ import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_LIGHTS_OUT_TRANSPARENT;
import static com.android.systemui.statusbar.phone.BarTransitions.MODE_TRANSPARENT;
+import android.annotation.ColorInt;
import android.content.Context;
-import android.graphics.Color;
import android.graphics.Rect;
import android.view.InsetsFlags;
import android.view.ViewDebug;
@@ -63,7 +63,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
private int mStatusBarMode;
private int mNavigationBarMode;
private int mNavigationMode;
- private final Color mDarkModeColor;
+ private final int mDarkIconColor;
+ private final int mLightIconColor;
/**
* Whether the navigation bar should be light factoring in already how much alpha the scrim has
@@ -94,7 +95,8 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
BatteryController batteryController,
NavigationModeController navModeController,
DumpManager dumpManager) {
- mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
+ mDarkIconColor = ctx.getColor(R.color.dark_mode_icon_color_single_tone);
+ mLightIconColor = ctx.getColor(R.color.light_mode_icon_color_single_tone);
mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
mBatteryController = batteryController;
mBatteryController.addCallback(this);
@@ -107,6 +109,16 @@ public class LightBarController implements BatteryController.BatteryStateChangeC
}
}
+ @ColorInt
+ int getLightAppearanceIconColor() {
+ return mDarkIconColor;
+ }
+
+ @ColorInt
+ int getDarkAppearanceIconColor() {
+ return mLightIconColor;
+ }
+
public void setNavigationBar(LightBarTransitionsController navigationBar) {
mNavigationBarController = navigationBar;
updateNavigation();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index c61510cce10e..6e98c49e6d43 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -32,6 +32,7 @@ import android.view.animation.AccelerateInterpolator;
import androidx.lifecycle.Observer;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
@@ -144,7 +145,7 @@ public class LightsOutNotifController extends ViewController<View> {
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
@Behavior int behavior, InsetsVisibilities requestedVisibilities,
- String packageName) {
+ String packageName, LetterboxDetails[] letterboxDetails) {
if (displayId != mDisplayId) {
return;
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 2dc3261eb886..a2140c6ab6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License
*/
-
package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.HeadsUpAppearanceController.CONTENT_FADE_DELAY;
@@ -31,6 +30,7 @@ import android.util.MathUtils;
import android.util.Property;
import android.view.ContextThemeWrapper;
import android.view.View;
+import android.view.ViewGroup;
import android.view.animation.Interpolator;
import androidx.annotation.VisibleForTesting;
@@ -40,7 +40,6 @@ import com.android.internal.statusbar.StatusBarIcon;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
-import com.android.systemui.statusbar.AlphaOptimizedFrameLayout;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.stack.AnimationFilter;
import com.android.systemui.statusbar.notification.stack.AnimationProperties;
@@ -54,7 +53,7 @@ import java.util.function.Consumer;
* A container for notification icons. It handles overflowing icons properly and positions them
* correctly on the screen.
*/
-public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
+public class NotificationIconContainer extends ViewGroup {
/**
* A float value indicating how much before the overflow start the icons should transform into
* a dot. A value of 0 means that they are exactly at the end and a value of 1 means it starts
@@ -232,6 +231,31 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}
@Override
+ public boolean hasOverlappingRendering() {
+ // Does the same as "AlphaOptimizedFrameLayout".
+ return false;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ final int childCount = getChildCount();
+ final int maxVisibleIcons = getMaxVisibleIcons(childCount);
+ final int width = MeasureSpec.getSize(widthMeasureSpec);
+ final int childWidthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.UNSPECIFIED);
+ int totalWidth = (int) (getActualPaddingStart() + getActualPaddingEnd());
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ measureChild(child, childWidthSpec, heightMeasureSpec);
+ if (i <= maxVisibleIcons) {
+ totalWidth += child.getMeasuredWidth();
+ }
+ }
+ final int measuredWidth = resolveSize(totalWidth, widthMeasureSpec);
+ final int measuredHeight = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(measuredWidth, measuredHeight);
+ }
+
+ @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
float centerY = getHeight() / 2.0f;
// we layout all our children on the left at the top
@@ -408,8 +432,7 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
float translationX = getActualPaddingStart();
int firstOverflowIndex = -1;
int childCount = getChildCount();
- int maxVisibleIcons = mOnLockScreen ? MAX_ICONS_ON_AOD :
- mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+ int maxVisibleIcons = getMaxVisibleIcons(childCount);
float layoutEnd = getLayoutEnd();
mVisualOverflowStart = 0;
mFirstVisibleIconState = null;
@@ -493,6 +516,11 @@ public class NotificationIconContainer extends AlphaOptimizedFrameLayout {
}
}
+ private int getMaxVisibleIcons(int childCount) {
+ return mOnLockScreen ? MAX_ICONS_ON_AOD :
+ mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
+ }
+
private float getLayoutEnd() {
return getActualWidth() - getActualPaddingEnd();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
index faae4bbbafd0..905a5f943f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java
@@ -44,6 +44,8 @@ import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.WindowManagerGlobal;
+import androidx.lifecycle.ViewTreeLifecycleOwner;
+
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dumpable;
import com.android.systemui.R;
@@ -52,6 +54,7 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.lifecycle.WindowAddedViewLifecycleOwner;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
import com.android.systemui.statusbar.NotificationShadeWindowController;
@@ -241,6 +244,16 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
mWindowManager.addView(mNotificationShadeView, mLp);
+
+ // Set up and "inject" a LifecycleOwner bound to the Window-View relationship such that all
+ // views in the sub-tree rooted under this view can access the LifecycleOwner using
+ // ViewTreeLifecycleOwner.get(...).
+ if (ViewTreeLifecycleOwner.get(mNotificationShadeView) == null) {
+ ViewTreeLifecycleOwner.set(
+ mNotificationShadeView,
+ new WindowAddedViewLifecycleOwner(mNotificationShadeView));
+ }
+
mLpChanged.copyFrom(mLp);
onThemeChanged();
@@ -502,7 +515,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW
cb.onStateChanged(mCurrentState.mKeyguardShowing,
mCurrentState.mKeyguardOccluded,
mCurrentState.mBouncerShowing,
- mCurrentState.mDozing);
+ mCurrentState.mDozing,
+ mCurrentState.mPanelExpanded);
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
new file mode 100644
index 000000000000..4657e9b84550
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/OWNERS
@@ -0,0 +1,6 @@
+per-file *Notification* = set noparent
+per-file *Notification* = file:../notification/OWNERS
+
+per-file NotificationIcon* = ccassidy@google.com, evanlaird@google.com, pixel@google.com
+
+per-file NotificationShadeWindowControllerImpl.java = dupin@google.com, cinek@google.com, beverlyt@google.com, pixel@google.com, juliacr@google.com
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
index 2052ee6cdac5..15c6dcfd7889 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarTransitions.java
@@ -31,7 +31,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
private final float mIconAlphaWhenOpaque;
- private View mLeftSide, mStatusIcons, mBattery;
+ private View mStartSide, mStatusIcons, mBattery;
private Animator mCurrentAnimation;
/**
@@ -41,7 +41,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
super(backgroundView, R.drawable.status_background);
final Resources res = statusBarView.getContext().getResources();
mIconAlphaWhenOpaque = res.getFraction(R.dimen.status_bar_icon_drawing_alpha, 1, 1);
- mLeftSide = statusBarView.findViewById(R.id.status_bar_left_side);
+ mStartSide = statusBarView.findViewById(R.id.status_bar_start_side_except_heads_up);
mStatusIcons = statusBarView.findViewById(R.id.statusIcons);
mBattery = statusBarView.findViewById(R.id.battery);
applyModeBackground(-1, getMode(), false /*animate*/);
@@ -75,7 +75,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
}
private void applyMode(int mode, boolean animate) {
- if (mLeftSide == null) return; // pre-init
+ if (mStartSide == null) return; // pre-init
float newAlpha = getNonBatteryClockAlphaFor(mode);
float newAlphaBC = getBatteryClockAlpha(mode);
if (mCurrentAnimation != null) {
@@ -84,7 +84,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
if (animate) {
AnimatorSet anims = new AnimatorSet();
anims.playTogether(
- animateTransitionTo(mLeftSide, newAlpha),
+ animateTransitionTo(mStartSide, newAlpha),
animateTransitionTo(mStatusIcons, newAlpha),
animateTransitionTo(mBattery, newAlphaBC)
);
@@ -94,7 +94,7 @@ public final class PhoneStatusBarTransitions extends BarTransitions {
anims.start();
mCurrentAnimation = anims;
} else {
- mLeftSide.setAlpha(newAlpha);
+ mStartSide.setAlpha(newAlpha);
mStatusIcons.setAlpha(newAlpha);
mBattery.setAlpha(newAlphaBC);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 9da2ef734be8..f9c4c8f3b4fe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -21,7 +21,6 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver
-
import com.android.systemui.R
import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
@@ -32,9 +31,7 @@ import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
import com.android.systemui.util.ViewController
import com.android.systemui.util.kotlin.getOrNull
import com.android.systemui.util.view.ViewUtil
-
import java.util.Optional
-
import javax.inject.Inject
import javax.inject.Named
@@ -58,8 +55,8 @@ class PhoneStatusBarViewController private constructor(
override fun onViewAttached() {
if (moveFromCenterAnimationController == null) return
- val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_left_side)
- val systemIconArea: ViewGroup = mView.findViewById(R.id.system_icon_area)
+ val statusBarLeftSide: View = mView.findViewById(R.id.status_bar_start_side_except_heads_up)
+ val systemIconArea: ViewGroup = mView.findViewById(R.id.status_bar_end_side_content)
val viewsToAnimate = arrayOf(
statusBarLeftSide,
@@ -126,11 +123,11 @@ class PhoneStatusBarViewController private constructor(
class StatusBarViewsCenterProvider : UnfoldMoveFromCenterAnimator.ViewCenterProvider {
override fun getViewCenter(view: View, outPoint: Point) =
when (view.id) {
- R.id.status_bar_left_side -> {
+ R.id.status_bar_start_side_except_heads_up -> {
// items aligned to the start, return start center point
getViewEdgeCenter(view, outPoint, isStart = true)
}
- R.id.system_icon_area -> {
+ R.id.status_bar_end_side_content -> {
// items aligned to the end, return end center point
getViewEdgeCenter(view, outPoint, isStart = false)
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 5e7dc115d7f9..cb0a1480c233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -54,8 +54,8 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.scrim.ScrimView;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.notification.stack.ViewState;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.AlarmTimeout;
@@ -265,7 +265,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager,
ConfigurationController configurationController, @Main Executor mainExecutor,
ScreenOffAnimationController screenOffAnimationController,
- PanelExpansionStateManager panelExpansionStateManager,
KeyguardUnlockAnimationController keyguardUnlockAnimationController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
mScrimStateListener = lightBarController::setScrimState;
@@ -305,10 +304,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
ScrimController.this.onThemeChanged();
}
});
- panelExpansionStateManager.addExpansionListener(
- event -> setRawPanelExpansionFraction(event.getFraction())
- );
-
mColors = new GradientColors();
}
@@ -553,13 +548,12 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
*
* The expansion fraction is tied to the scrim opacity.
*
- * See {@link PanelExpansionListener#onPanelExpansionChanged}.
+ * See {@link ScrimShadeTransitionController#onPanelExpansionChanged}.
*
* @param rawPanelExpansionFraction From 0 to 1 where 0 means collapsed and 1 expanded.
*/
- @VisibleForTesting
- void setRawPanelExpansionFraction(
- @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
+ public void setRawPanelExpansionFraction(
+ @FloatRange(from = 0.0, to = 1.0) float rawPanelExpansionFraction) {
if (isNaN(rawPanelExpansionFraction)) {
throw new IllegalArgumentException("rawPanelExpansionFraction should not be NaN");
}
@@ -790,7 +784,8 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump
mInFrontAlpha = 0;
}
- if (mBouncerHiddenFraction != KeyguardBouncer.EXPANSION_HIDDEN) {
+ if (mState == ScrimState.DREAMING
+ && mBouncerHiddenFraction != KeyguardBouncer.EXPANSION_HIDDEN) {
final float interpolatedFraction =
BouncerPanelExpansionCalculator.aboutToShowBouncerProgress(
mBouncerHiddenFraction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
index cee8b335f380..d37ecbc42168 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ShadeControllerImpl.java
@@ -23,6 +23,8 @@ import android.view.WindowManager;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShadeWindowController;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
new file mode 100644
index 000000000000..f5ba399fe51a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarBoundsProvider.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.view.View
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.END_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentModule.START_SIDE_CONTENT
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentScope
+import com.android.systemui.util.boundsOnScreen
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Provides various bounds within the status bar. */
+@StatusBarFragmentScope
+class StatusBarBoundsProvider
+@Inject
+constructor(
+ private val changeListeners: Set<@JvmSuppressWildcards BoundsChangeListener>,
+ @Named(START_SIDE_CONTENT) private val startSideContent: View,
+ @Named(END_SIDE_CONTENT) private val endSideContent: View,
+) : StatusBarFragmentComponent.Startable {
+
+ interface BoundsChangeListener {
+ fun onStatusBarBoundsChanged()
+ }
+
+ private var previousBounds =
+ BoundsPair(start = startSideContent.boundsOnScreen, end = endSideContent.boundsOnScreen)
+
+ private val layoutListener =
+ View.OnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
+ val newBounds = BoundsPair(start = visibleStartSideBounds, end = visibleEndSideBounds)
+ if (previousBounds != newBounds) {
+ previousBounds = newBounds
+ changeListeners.forEach { it.onStatusBarBoundsChanged() }
+ }
+ }
+
+ override fun start() {
+ startSideContent.addOnLayoutChangeListener(layoutListener)
+ endSideContent.addOnLayoutChangeListener(layoutListener)
+ }
+
+ override fun stop() {
+ startSideContent.removeOnLayoutChangeListener(layoutListener)
+ endSideContent.removeOnLayoutChangeListener(layoutListener)
+ }
+
+ /**
+ * Returns the bounds of the end side of the status bar that are visible to the user. The end
+ * side is right when in LTR and is left when in RTL.
+ *
+ * Note that even though the layout might be larger, here we only return the bounds for what is
+ * visible to the user.
+ *
+ * The end side of the status bar contains the multi-user switcher and status icons such as
+ * wi-fi, battery, etc
+ */
+ val visibleEndSideBounds: Rect
+ get() = endSideContent.boundsOnScreen
+
+ /**
+ * Returns the bounds of the start side of the status bar that are visible to the user. The
+ * start side is left when in LTR and is right when in RTL.
+ *
+ * Note that even though the layout might be larger, here we only return the bounds for what is
+ * visible to the user.
+ *
+ * The start side of the status bar contains the operator name, clock, on-going call chip, and
+ * notifications.
+ */
+ val visibleStartSideBounds: Rect
+ get() = startSideContent.boundsOnScreen
+}
+
+private data class BoundsPair(val start: Rect, val end: Rect)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
index f5462bc0fba5..c850d4f9c56b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt
@@ -123,7 +123,7 @@ class StatusBarContentInsetsProvider @Inject constructor(
val point = Point()
context.display.getRealSize(point)
- return topBounds.left <= 0 || topBounds.right >= point.y
+ return topBounds.left <= 0 || topBounds.right >= point.x
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
index 50f21691b044..ebfbf54abe1a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarHeadsUpChangeListener.java
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.phone;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index a94c2b73d1f6..7c31366ba4f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -16,6 +16,8 @@
package com.android.systemui.statusbar.phone;
+import static com.android.systemui.statusbar.phone.StatusBarIconList.Slot;
+
import android.annotation.NonNull;
import android.content.Context;
import android.graphics.drawable.Icon;
@@ -56,11 +58,12 @@ import javax.inject.Inject;
* registered with it.
*/
@SysUISingleton
-public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
+public class StatusBarIconControllerImpl implements Tunable,
ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController, DemoMode {
private static final String TAG = "StatusBarIconController";
+ private final StatusBarIconList mStatusBarIconList;
private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
private final ArraySet<String> mIconHideList = new ArraySet<>();
@@ -74,15 +77,12 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
DemoModeController demoModeController,
ConfigurationController configurationController,
TunerService tunerService,
- DumpManager dumpManager) {
- super(context.getResources().getStringArray(
- com.android.internal.R.array.config_statusBarIcons));
- configurationController.addCallback(this);
-
+ DumpManager dumpManager,
+ StatusBarIconList statusBarIconList) {
+ mStatusBarIconList = statusBarIconList;
mContext = context;
- loadDimens();
-
+ configurationController.addCallback(this);
commandQueue.addCallback(this);
tunerService.addTunable(this, ICON_HIDE_LIST);
demoModeController.addCallback(this);
@@ -101,15 +101,14 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
group.setController(this);
mIconGroups.add(group);
- List<Slot> allSlots = getSlots();
+ List<Slot> allSlots = mStatusBarIconList.getSlots();
for (int i = 0; i < allSlots.size(); i++) {
Slot slot = allSlots.get(i);
List<StatusBarIconHolder> holders = slot.getHolderListInViewOrder();
boolean hidden = mIconHideList.contains(slot.getName());
for (StatusBarIconHolder holder : holders) {
- int tag = holder.getTag();
- int viewIndex = getViewIndex(getSlotIndex(slot.getName()), holder.getTag());
+ int viewIndex = mStatusBarIconList.getViewIndex(slot.getName(), holder.getTag());
group.onIconAdded(viewIndex, slot.getName(), hidden, holder);
}
}
@@ -144,7 +143,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
}
mIconHideList.clear();
mIconHideList.addAll(StatusBarIconController.getIconHideList(mContext, newValue));
- ArrayList<Slot> currentSlots = getSlots();
+ List<Slot> currentSlots = mStatusBarIconList.getSlots();
ArrayMap<Slot, List<StatusBarIconHolder>> slotsToReAdd = new ArrayMap<>();
// This is a little hacky... Peel off all of the holders on all of the slots
@@ -163,17 +162,13 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
List<StatusBarIconHolder> iconsForSlot = slotsToReAdd.get(item);
if (iconsForSlot == null) continue;
for (StatusBarIconHolder holder : iconsForSlot) {
- setIcon(getSlotIndex(item.getName()), holder);
+ setIcon(item.getName(), holder);
}
}
}
- private void loadDimens() {
- }
-
- private void addSystemIcon(int index, StatusBarIconHolder holder) {
- String slot = getSlotName(index);
- int viewIndex = getViewIndex(index, holder.getTag());
+ private void addSystemIcon(String slot, StatusBarIconHolder holder) {
+ int viewIndex = mStatusBarIconList.getViewIndex(slot, holder.getTag());
boolean hidden = mIconHideList.contains(slot);
mIconGroups.forEach(l -> l.onIconAdded(viewIndex, slot, hidden, holder));
@@ -182,18 +177,17 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
/** */
@Override
public void setIcon(String slot, int resourceId, CharSequence contentDescription) {
- int index = getSlotIndex(slot);
- StatusBarIconHolder holder = getIcon(index, 0);
+ StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
if (holder == null) {
StatusBarIcon icon = new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
Icon.createWithResource(
mContext, resourceId), 0, 0, contentDescription);
holder = StatusBarIconHolder.fromIcon(icon);
- setIcon(index, holder);
+ setIcon(slot, holder);
} else {
holder.getIcon().icon = Icon.createWithResource(mContext, resourceId);
holder.getIcon().contentDescription = contentDescription;
- handleSet(index, holder);
+ handleSet(slot, holder);
}
}
@@ -203,21 +197,18 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
*/
@Override
public void setSignalIcon(String slot, WifiIconState state) {
-
- int index = getSlotIndex(slot);
-
if (state == null) {
- removeIcon(index, 0);
+ removeIcon(slot, 0);
return;
}
- StatusBarIconHolder holder = getIcon(index, 0);
+ StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, 0);
if (holder == null) {
holder = StatusBarIconHolder.fromWifiIconState(state);
- setIcon(index, holder);
+ setIcon(slot, holder);
} else {
holder.setWifiState(state);
- handleSet(index, holder);
+ handleSet(slot, holder);
}
}
@@ -229,8 +220,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
*/
@Override
public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
- Slot mobileSlot = getSlot(slot);
- int slotIndex = getSlotIndex(slot);
+ Slot mobileSlot = mStatusBarIconList.getSlot(slot);
// Reverse the sort order to show icons with left to right([Slot1][Slot2]..).
// StatusBarIconList has UI design that first items go to the right of second items.
@@ -241,10 +231,10 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
StatusBarIconHolder holder = mobileSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromMobileIconState(state);
- setIcon(slotIndex, holder);
+ setIcon(slot, holder);
} else {
holder.setMobileState(state);
- handleSet(slotIndex, holder);
+ handleSet(slot, holder);
}
}
}
@@ -256,21 +246,19 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
*/
@Override
public void setCallStrengthIcons(String slot, List<CallIndicatorIconState> states) {
- Slot callStrengthSlot = getSlot(slot);
- int callStrengthSlotIndex = getSlotIndex(slot);
+ Slot callStrengthSlot = mStatusBarIconList.getSlot(slot);
Collections.reverse(states);
for (CallIndicatorIconState state : states) {
if (!state.isNoCalling) {
StatusBarIconHolder holder = callStrengthSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
- setIcon(callStrengthSlotIndex, holder);
} else {
holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
Icon.createWithResource(mContext, state.callStrengthResId), 0, 0,
state.callStrengthDescription));
- setIcon(callStrengthSlotIndex, holder);
}
+ setIcon(slot, holder);
}
setIconVisibility(slot, !state.isNoCalling, state.subId);
}
@@ -283,21 +271,19 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
*/
@Override
public void setNoCallingIcons(String slot, List<CallIndicatorIconState> states) {
- Slot noCallingSlot = getSlot(slot);
- int noCallingSlotIndex = getSlotIndex(slot);
+ Slot noCallingSlot = mStatusBarIconList.getSlot(slot);
Collections.reverse(states);
for (CallIndicatorIconState state : states) {
if (state.isNoCalling) {
StatusBarIconHolder holder = noCallingSlot.getHolderForTag(state.subId);
if (holder == null) {
holder = StatusBarIconHolder.fromCallIndicatorState(mContext, state);
- setIcon(noCallingSlotIndex, holder);
} else {
holder.setIcon(new StatusBarIcon(UserHandle.SYSTEM, mContext.getPackageName(),
Icon.createWithResource(mContext, state.noCallingResId), 0, 0,
state.noCallingDescription));
- setIcon(noCallingSlotIndex, holder);
}
+ setIcon(slot, holder);
}
setIconVisibility(slot, state.isNoCalling, state.subId);
}
@@ -305,42 +291,31 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
@Override
public void setExternalIcon(String slot) {
- int viewIndex = getViewIndex(getSlotIndex(slot), 0);
+ int viewIndex = mStatusBarIconList.getViewIndex(slot, 0);
int height = mContext.getResources().getDimensionPixelSize(
R.dimen.status_bar_icon_drawing_size);
mIconGroups.forEach(l -> l.onIconExternal(viewIndex, height));
}
//TODO: remove this (used in command queue and for 3rd party tiles?)
- @Override
public void setIcon(String slot, StatusBarIcon icon) {
- setIcon(getSlotIndex(slot), icon);
- }
-
- /**
- * For backwards compatibility, in the event that someone gives us a slot and a status bar icon
- */
- private void setIcon(int index, StatusBarIcon icon) {
- String slot = getSlotName(index);
if (icon == null) {
removeAllIconsForSlot(slot);
return;
}
StatusBarIconHolder holder = StatusBarIconHolder.fromIcon(icon);
- setIcon(index, holder);
+ setIcon(slot, holder);
}
- /** */
- @Override
- public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
- boolean isNew = getIcon(index, holder.getTag()) == null;
- super.setIcon(index, holder);
+ private void setIcon(String slot, @NonNull StatusBarIconHolder holder) {
+ boolean isNew = mStatusBarIconList.getIconHolder(slot, holder.getTag()) == null;
+ mStatusBarIconList.setIcon(slot, holder);
if (isNew) {
- addSystemIcon(index, holder);
+ addSystemIcon(slot, holder);
} else {
- handleSet(index, holder);
+ handleSet(slot, holder);
}
}
@@ -351,34 +326,33 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
/** */
public void setIconVisibility(String slot, boolean visibility, int tag) {
- int index = getSlotIndex(slot);
- StatusBarIconHolder holder = getIcon(index, tag);
+ StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, tag);
if (holder == null || holder.isVisible() == visibility) {
return;
}
holder.setVisible(visibility);
- handleSet(index, holder);
+ handleSet(slot, holder);
}
/** */
@Override
public void setIconAccessibilityLiveRegion(String slotName, int accessibilityLiveRegion) {
- Slot slot = getSlot(slotName);
+ Slot slot = mStatusBarIconList.getSlot(slotName);
if (!slot.hasIconsInSlot()) {
return;
}
- int slotIndex = getSlotIndex(slotName);
List<StatusBarIconHolder> iconsToUpdate = slot.getHolderListInViewOrder();
for (StatusBarIconHolder holder : iconsToUpdate) {
- int viewIndex = getViewIndex(slotIndex, holder.getTag());
+ int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
mIconGroups.forEach(l -> l.mGroup.getChildAt(viewIndex)
.setAccessibilityLiveRegion(accessibilityLiveRegion));
}
}
/** */
+ @Override
public void removeIcon(String slot) {
removeAllIconsForSlot(slot);
}
@@ -386,39 +360,34 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
/** */
@Override
public void removeIcon(String slot, int tag) {
- removeIcon(getSlotIndex(slot), tag);
+ if (mStatusBarIconList.getIconHolder(slot, tag) == null) {
+ return;
+ }
+ int viewIndex = mStatusBarIconList.getViewIndex(slot, tag);
+ mStatusBarIconList.removeIcon(slot, tag);
+ mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
}
/** */
@Override
public void removeAllIconsForSlot(String slotName) {
- Slot slot = getSlot(slotName);
+ Slot slot = mStatusBarIconList.getSlot(slotName);
if (!slot.hasIconsInSlot()) {
return;
}
- int slotIndex = getSlotIndex(slotName);
List<StatusBarIconHolder> iconsToRemove = slot.getHolderListInViewOrder();
for (StatusBarIconHolder holder : iconsToRemove) {
- int viewIndex = getViewIndex(slotIndex, holder.getTag());
+ int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
slot.removeForTag(holder.getTag());
mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
}
}
- /** */
- @Override
- public void removeIcon(int index, int tag) {
- if (getIcon(index, tag) == null) {
- return;
- }
- super.removeIcon(index, tag);
- int viewIndex = getViewIndex(index, 0);
- mIconGroups.forEach(l -> l.onRemoveIcon(viewIndex));
- }
- private void handleSet(int index, StatusBarIconHolder holder) {
- int viewIndex = getViewIndex(index, holder.getTag());
+
+ private void handleSet(String slotName, StatusBarIconHolder holder) {
+ int viewIndex = mStatusBarIconList.getViewIndex(slotName, holder.getTag());
mIconGroups.forEach(l -> l.onSetIconHolder(viewIndex, holder));
}
@@ -438,7 +407,7 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
}
}
- super.dump(pw);
+ mStatusBarIconList.dump(pw);
}
/** */
@@ -482,7 +451,6 @@ public class StatusBarIconControllerImpl extends StatusBarIconList implements Tu
/** */
@Override
public void onDensityOrFontScaleChanged() {
- loadDimens();
refreshIconGroups();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
index c876c3228eb8..8800b05fadb7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconList.java
@@ -25,60 +25,72 @@ import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+/** A class holding the list of all the system icons that could be shown in the status bar. */
public class StatusBarIconList {
- private ArrayList<Slot> mSlots = new ArrayList<>();
+ private final ArrayList<Slot> mSlots = new ArrayList<>();
+ private final List<Slot> mViewOnlySlots = Collections.unmodifiableList(mSlots);
public StatusBarIconList(String[] slots) {
final int N = slots.length;
- for (int i=0; i < N; i++) {
+ for (int i = 0; i < N; i++) {
mSlots.add(new Slot(slots[i], null));
}
}
- public int getSlotIndex(String slot) {
- final int N = mSlots.size();
- for (int i=0; i<N; i++) {
- Slot item = mSlots.get(i);
- if (item.getName().equals(slot)) {
- return i;
- }
- }
- // Auto insert new items at the beginning.
- mSlots.add(0, new Slot(slot, null));
- return 0;
- }
-
- protected ArrayList<Slot> getSlots() {
- return new ArrayList<>(mSlots);
- }
-
- protected Slot getSlot(String name) {
- return mSlots.get(getSlotIndex(name));
+ /** Returns the list of current slots. */
+ public List<Slot> getSlots() {
+ return mViewOnlySlots;
}
- public int size() {
- return mSlots.size();
+ /**
+ * Gets the slot with the given {@code name}, or creates a new slot if we don't already have a
+ * slot by that name.
+ *
+ * If a new slot is created, that slot will be inserted at the front of the list.
+ *
+ * TODO(b/237533036): Rename this to getOrCreateSlot to make it more clear that it could create
+ * a new slot. Other methods in this class will also create a new slot if we don't have one,
+ * should those be re-named too?
+ */
+ public Slot getSlot(String name) {
+ return mSlots.get(findOrInsertSlot(name));
}
- public void setIcon(int index, @NonNull StatusBarIconHolder holder) {
- mSlots.get(index).addHolder(holder);
+ /**
+ * Sets the icon in {@code holder} to be associated with the slot with the given
+ * {@code slotName}.
+ */
+ public void setIcon(String slotName, @NonNull StatusBarIconHolder holder) {
+ mSlots.get(findOrInsertSlot(slotName)).addHolder(holder);
}
- public void removeIcon(int index, int tag) {
- mSlots.get(index).removeForTag(tag);
+ /**
+ * Removes the icon holder that we had associated with {@code slotName}'s slot at the given
+ * {@code tag}.
+ */
+ public void removeIcon(String slotName, int tag) {
+ mSlots.get(findOrInsertSlot(slotName)).removeForTag(tag);
}
- public String getSlotName(int index) {
- return mSlots.get(index).getName();
+ /**
+ * Returns the icon holder currently associated with {@code slotName}'s slot at the given
+ * {@code tag}, or null if we don't have one.
+ */
+ @Nullable
+ public StatusBarIconHolder getIconHolder(String slotName, int tag) {
+ return mSlots.get(findOrInsertSlot(slotName)).getHolderForTag(tag);
}
- public StatusBarIconHolder getIcon(int index, int tag) {
- return mSlots.get(index).getHolderForTag(tag);
- }
-
- public int getViewIndex(int slotIndex, int tag) {
+ /**
+ * Returns the index of the icon in {@code slotName}'s slot at the given {@code tag}.
+ *
+ * Note that a single slot can have multiple icons, and this function takes that into account.
+ */
+ public int getViewIndex(String slotName, int tag) {
+ int slotIndex = findOrInsertSlot(slotName);
int count = 0;
for (int i = 0; i < slotIndex; i++) {
Slot item = mSlots.get(i);
@@ -100,6 +112,25 @@ public class StatusBarIconList {
}
}
+ private int findOrInsertSlot(String slot) {
+ final int N = mSlots.size();
+ for (int i = 0; i < N; i++) {
+ Slot item = mSlots.get(i);
+ if (item.getName().equals(slot)) {
+ return i;
+ }
+ }
+ // Auto insert new items at the beginning.
+ mSlots.add(0, new Slot(slot, null));
+ return 0;
+ }
+
+
+ /**
+ * A class representing one slot in the status bar system icons view.
+ *
+ * Note that one slot can have multiple icons associated with it.
+ */
public static class Slot {
private final String mName;
private StatusBarIconHolder mHolder;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 63c5e61ac4e5..f128a4124c94 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -55,6 +55,7 @@ import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.navigationbar.NavigationBarView;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -129,8 +130,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
public void onFullyShown() {
mBouncerAnimating = false;
updateStates();
- mCentralSurfaces.wakeUpIfDozing(SystemClock.uptimeMillis(),
- mCentralSurfaces.getBouncerContainer(), "BOUNCER_VISIBLE");
}
@Override
@@ -163,6 +162,10 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
@Override
public void onVisibilityChanged(boolean isVisible) {
+ mCentralSurfaces
+ .setBouncerShowingOverDream(
+ isVisible && mDreamOverlayStateController.isOverlayActive());
+
if (!isVisible) {
mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
}
@@ -1039,7 +1042,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
}
if (occluded != mLastOccluded || mFirstUpdate) {
- mKeyguardUpdateManager.onKeyguardOccludedChanged(occluded);
mKeyguardStateController.notifyKeyguardState(showing, occluded);
}
if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
@@ -1068,11 +1070,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb
mCentralSurfaces.onKeyguardViewManagerStatesUpdated();
}
- private View getCurrentNavBarView() {
- final NavigationBarView navBarView = mCentralSurfaces.getNavigationBarView();
- return navBarView != null ? navBarView.getCurrentView() : null;
- }
-
/**
* Updates the visibility of the nav bar window (which will cause insets changes).
*/
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 cf776e3b60d1..451612ad4bb1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -52,21 +52,16 @@ import com.android.systemui.EventLogTags;
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -92,24 +87,19 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
private final Context mContext;
- private final CommandQueue mCommandQueue;
private final Handler mMainThreadHandler;
private final Executor mUiBgExecutor;
- private final NotificationEntryManager mEntryManager;
- private final NotifPipeline mNotifPipeline;
private final NotificationVisibilityProvider mVisibilityProvider;
private final HeadsUpManagerPhone mHeadsUpManager;
private final ActivityStarter mActivityStarter;
private final NotificationClickNotifier mClickNotifier;
- private final StatusBarStateController mStatusBarStateController;
private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private final KeyguardManager mKeyguardManager;
private final IDreamManager mDreamManager;
private final Optional<BubblesManager> mBubblesManagerOptional;
private final Lazy<AssistManager> mAssistManagerLazy;
private final NotificationRemoteInputManager mRemoteInputManager;
- private final GroupMembershipManager mGroupMembershipManager;
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final ShadeController mShadeController;
private final KeyguardStateController mKeyguardStateController;
@@ -118,7 +108,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
private final ActivityIntentHelper mActivityIntentHelper;
- private final NotifPipelineFlags mNotifPipelineFlags;
private final MetricsLogger mMetricsLogger;
private final StatusBarNotificationActivityStarterLogger mLogger;
@@ -134,23 +123,19 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
@Inject
StatusBarNotificationActivityStarter(
Context context,
- CommandQueue commandQueue,
Handler mainThreadHandler,
Executor uiBgExecutor,
- NotificationEntryManager entryManager,
NotifPipeline notifPipeline,
NotificationVisibilityProvider visibilityProvider,
HeadsUpManagerPhone headsUpManager,
ActivityStarter activityStarter,
NotificationClickNotifier clickNotifier,
- StatusBarStateController statusBarStateController,
StatusBarKeyguardViewManager statusBarKeyguardViewManager,
KeyguardManager keyguardManager,
IDreamManager dreamManager,
Optional<BubblesManager> bubblesManagerOptional,
Lazy<AssistManager> assistManagerLazy,
NotificationRemoteInputManager remoteInputManager,
- GroupMembershipManager groupMembershipManager,
NotificationLockscreenUserManager lockscreenUserManager,
ShadeController shadeController,
KeyguardStateController keyguardStateController,
@@ -158,7 +143,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
LockPatternUtils lockPatternUtils,
StatusBarRemoteInputCallback remoteInputCallback,
ActivityIntentHelper activityIntentHelper,
- NotifPipelineFlags notifPipelineFlags,
MetricsLogger metricsLogger,
StatusBarNotificationActivityStarterLogger logger,
OnUserInteractionCallback onUserInteractionCallback,
@@ -168,23 +152,18 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
ActivityLaunchAnimator activityLaunchAnimator,
NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
mContext = context;
- mCommandQueue = commandQueue;
mMainThreadHandler = mainThreadHandler;
mUiBgExecutor = uiBgExecutor;
- mEntryManager = entryManager;
- mNotifPipeline = notifPipeline;
mVisibilityProvider = visibilityProvider;
mHeadsUpManager = headsUpManager;
mActivityStarter = activityStarter;
mClickNotifier = clickNotifier;
- mStatusBarStateController = statusBarStateController;
mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
mKeyguardManager = keyguardManager;
mDreamManager = dreamManager;
mBubblesManagerOptional = bubblesManagerOptional;
mAssistManagerLazy = assistManagerLazy;
mRemoteInputManager = remoteInputManager;
- mGroupMembershipManager = groupMembershipManager;
mLockscreenUserManager = lockscreenUserManager;
mShadeController = shadeController;
mKeyguardStateController = keyguardStateController;
@@ -192,7 +171,6 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
mLockPatternUtils = lockPatternUtils;
mStatusBarRemoteInputCallback = remoteInputCallback;
mActivityIntentHelper = activityIntentHelper;
- mNotifPipelineFlags = notifPipelineFlags;
mMetricsLogger = metricsLogger;
mLogger = logger;
mOnUserInteractionCallback = onUserInteractionCallback;
@@ -203,21 +181,12 @@ class StatusBarNotificationActivityStarter implements NotificationActivityStarte
mActivityLaunchAnimator = activityLaunchAnimator;
mNotificationAnimationProvider = notificationAnimationProvider;
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
- } else {
- mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
- @Override
- public void onEntryAdded(NotificationEntry entry) {
- handleFullScreenIntent(entry);
- }
- });
- }
+ notifPipeline.addCollectionListener(new NotifCollectionListener() {
+ @Override
+ public void onEntryAdded(NotificationEntry entry) {
+ handleFullScreenIntent(entry);
+ }
+ });
}
/**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index aa061d74f6c6..4c239314f70f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -30,19 +30,17 @@ import android.util.Log;
import android.util.Slog;
import android.view.View;
import android.view.accessibility.AccessibilityManager;
-import android.widget.TextView;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.widget.MessagingGroup;
-import com.android.internal.widget.MessagingMessage;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.Dependency;
import com.android.systemui.ForegroundServiceNotificationListener;
import com.android.systemui.InitController;
import com.android.systemui.R;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -57,7 +55,6 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.AboveShelfObserver;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
@@ -72,16 +69,12 @@ import com.android.systemui.statusbar.notification.stack.NotificationListContain
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
-import java.util.List;
-
import javax.inject.Inject;
@CentralSurfacesComponent.CentralSurfacesScope
class StatusBarNotificationPresenter implements NotificationPresenter,
- ConfigurationController.ConfigurationListener,
NotificationRowBinderImpl.BindRowCallback,
CommandQueue.Callbacks {
private static final String TAG = "StatusBarNotificationPresenter";
@@ -92,10 +85,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
private final NotificationLockscreenUserManager mLockscreenUserManager;
private final SysuiStatusBarStateController mStatusBarStateController;
private final NotifShadeEventSource mNotifShadeEventSource;
- private final NotificationEntryManager mEntryManager;
private final NotificationMediaManager mMediaManager;
private final NotificationGutsManager mGutsManager;
- private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final LockscreenGestureLogger mLockscreenGestureLogger;
private final NotificationPanelViewController mNotificationPanel;
@@ -116,9 +107,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
private final IStatusBarService mBarService;
private final DynamicPrivacyController mDynamicPrivacyController;
private final NotificationListContainer mNotifListContainer;
- private boolean mReinflateNotificationsOnUserSwitched;
- private boolean mDispatchUiModeChangeOnUserSwitched;
- private TextView mNotificationPanelDebugText;
protected boolean mVrMode;
@@ -143,15 +131,12 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
NotificationLockscreenUserManager lockscreenUserManager,
SysuiStatusBarStateController sysuiStatusBarStateController,
NotifShadeEventSource notifShadeEventSource,
- NotificationEntryManager notificationEntryManager,
NotificationMediaManager notificationMediaManager,
NotificationGutsManager notificationGutsManager,
- KeyguardUpdateMonitor keyguardUpdateMonitor,
LockscreenGestureLogger lockscreenGestureLogger,
InitController initController,
NotificationInterruptStateProvider notificationInterruptStateProvider,
NotificationRemoteInputManager remoteInputManager,
- ConfigurationController configurationController,
NotifPipelineFlags notifPipelineFlags,
NotificationRemoteInputManager.Callback remoteInputManagerCallback,
NotificationListContainer notificationListContainer) {
@@ -170,10 +155,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
mLockscreenUserManager = lockscreenUserManager;
mStatusBarStateController = sysuiStatusBarStateController;
mNotifShadeEventSource = notifShadeEventSource;
- mEntryManager = notificationEntryManager;
mMediaManager = notificationMediaManager;
mGutsManager = notificationGutsManager;
- mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mLockscreenGestureLogger = lockscreenGestureLogger;
mAboveShelfObserver = new AboveShelfObserver(stackScrollerController.getView());
mNotificationShadeWindowController = notificationShadeWindowController;
@@ -208,13 +191,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
mNotifListContainer);
mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- mEntryManager.setUpWithPresenter(this);
- mEntryManager.addNotificationLifetimeExtender(mHeadsUpManager);
- mEntryManager.addNotificationLifetimeExtender(mGutsManager);
- mEntryManager.addNotificationLifetimeExtenders(
- remoteInputManager.getLifetimeExtenders());
- }
notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
mLockscreenUserManager.setUpWithPresenter(this);
mMediaManager.setUpWithPresenter(this);
@@ -227,7 +203,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
onUserSwitched(mLockscreenUserManager.getCurrentUserId());
});
- configurationController.addCallback(this);
}
/** Called when the shade has been emptied to attempt to close the shade */
@@ -242,65 +217,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
}
@Override
- public void onDensityOrFontScaleChanged() {
- // TODO(b/145659174): Remove legacy pipeline code
- if (mNotifPipelineFlags.isNewPipelineEnabled()) return;
- MessagingMessage.dropCache();
- MessagingGroup.dropCache();
- if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
- updateNotificationsOnDensityOrFontScaleChanged();
- } else {
- mReinflateNotificationsOnUserSwitched = true;
- }
- }
-
- @Override
- public void onUiModeChanged() {
- // TODO(b/145659174): Remove legacy pipeline code
- if (mNotifPipelineFlags.isNewPipelineEnabled()) return;
- if (!mKeyguardUpdateMonitor.isSwitchingUser()) {
- updateNotificationsOnUiModeChanged();
- } else {
- mDispatchUiModeChangeOnUserSwitched = true;
- }
- }
-
- @Override
- public void onThemeChanged() {
- onDensityOrFontScaleChanged();
- }
-
- private void updateNotificationsOnUiModeChanged() {
- // TODO(b/145659174): Remove legacy pipeline code
- if (mNotifPipelineFlags.isNewPipelineEnabled()) return;
- List<NotificationEntry> userNotifications =
- mEntryManager.getActiveNotificationsForCurrentUser();
- for (int i = 0; i < userNotifications.size(); i++) {
- NotificationEntry entry = userNotifications.get(i);
- ExpandableNotificationRow row = entry.getRow();
- if (row != null) {
- row.onUiModeChanged();
- }
- }
- }
-
- private void updateNotificationsOnDensityOrFontScaleChanged() {
- // TODO(b/145659174): Remove legacy pipeline code
- if (mNotifPipelineFlags.isNewPipelineEnabled()) return;
- List<NotificationEntry> userNotifications =
- mEntryManager.getActiveNotificationsForCurrentUser();
- for (int i = 0; i < userNotifications.size(); i++) {
- NotificationEntry entry = userNotifications.get(i);
- entry.onDensityOrFontScaleChanged();
- boolean exposedGuts = entry.areGutsExposed();
- if (exposedGuts) {
- mGutsManager.onDensityOrFontScaleChanged(entry);
- }
- }
- }
-
-
- @Override
public boolean isCollapsing() {
return mNotificationPanel.isCollapsing()
|| mNotificationShadeWindowController.isLaunchingActivity();
@@ -340,17 +256,6 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
// End old BaseStatusBar.userSwitched
if (MULTIUSER_DEBUG) mNotificationPanel.setHeaderDebugInfo("USER " + newUserId);
mCommandQueue.animateCollapsePanels();
- if (!mNotifPipelineFlags.isNewPipelineEnabled()) {
- if (mReinflateNotificationsOnUserSwitched) {
- updateNotificationsOnDensityOrFontScaleChanged();
- mReinflateNotificationsOnUserSwitched = false;
- }
- if (mDispatchUiModeChangeOnUserSwitched) {
- updateNotificationsOnUiModeChanged();
- mDispatchUiModeChangeOnUserSwitched = false;
- }
- updateNotificationViews("user switched");
- }
mMediaManager.clearCurrentMediaNotification();
mCentralSurfaces.setLockscreenUser(newUserId);
updateMediaMetaData(true, false);
@@ -494,7 +399,8 @@ class StatusBarNotificationPresenter implements NotificationPresenter,
return true;
}
- if (sbn.getNotification().fullScreenIntent != null) {
+ if (sbn.getNotification().fullScreenIntent != null
+ && !mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()) {
// we don't allow head-up on the lockscreen (unless there's a
// "showWhenLocked" activity currently showing) if
// the potential HUN has a fullscreen intent
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
index 4e9090080c99..75dac1a093dd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarTouchableRegionManager.java
@@ -136,8 +136,9 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
* Notify that the status bar panel gets expanded or collapsed.
*
* @param isExpanded True to notify expanded, false to notify collapsed.
+ * TODO(b/237811427) replace with a listener
*/
- void setPanelExpanded(boolean isExpanded) {
+ public void setPanelExpanded(boolean isExpanded) {
if (isExpanded != mIsStatusBarExpanded) {
mIsStatusBarExpanded = isExpanded;
if (isExpanded) {
@@ -153,7 +154,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
* any existing display cutouts (notch)
* @return the heads up notification touch area
*/
- Region calculateTouchableRegion() {
+ public Region calculateTouchableRegion() {
// Update touchable region for HeadsUp notifications
final Region headsUpTouchableRegion = mHeadsUpManager.getTouchableRegion();
if (headsUpTouchableRegion != null) {
@@ -222,7 +223,7 @@ public final class StatusBarTouchableRegionManager implements Dumpable {
}
}
- void updateRegionForNotch(Region touchableRegion) {
+ public void updateRegionForNotch(Region touchableRegion) {
WindowInsets windowInsets = mNotificationShadeWindowView.getRootWindowInsets();
if (windowInsets == null) {
Log.w(TAG, "StatusBarWindowView is not attached.");
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
index ac43b679da0f..ae48c2d3b6f3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarWindowCallback.java
@@ -17,5 +17,5 @@ package com.android.systemui.statusbar.phone;
public interface StatusBarWindowCallback {
void onStateChanged(boolean keyguardShowing, boolean keyguardOccluded, boolean bouncerShowing,
- boolean isDozing);
+ boolean isDozing, boolean panelExpanded);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
new file mode 100644
index 000000000000..a0415f2f3d7c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.view.InsetsVisibilities
+import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.Behavior
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent
+import com.android.systemui.statusbar.phone.dagger.CentralSurfacesComponent.CentralSurfacesScope
+import java.io.PrintWriter
+import javax.inject.Inject
+
+/**
+ * Top-level listener of system attributes changed. This class is __always the first__ one to be
+ * notified about changes.
+ *
+ * It is responsible for modifying any attributes if necessary, and then notifying the other
+ * downstream listeners.
+ */
+@CentralSurfacesScope
+class SystemBarAttributesListener
+@Inject
+internal constructor(
+ private val centralSurfaces: CentralSurfaces,
+ private val featureFlags: FeatureFlags,
+ private val letterboxAppearanceCalculator: LetterboxAppearanceCalculator,
+ private val statusBarStateController: SysuiStatusBarStateController,
+ private val lightBarController: LightBarController,
+ private val dumpManager: DumpManager,
+) : CentralSurfacesComponent.Startable, StatusBarBoundsProvider.BoundsChangeListener {
+
+ private var lastLetterboxAppearance: LetterboxAppearance? = null
+ private var lastSystemBarAttributesParams: SystemBarAttributesParams? = null
+
+ override fun start() {
+ dumpManager.registerDumpable(javaClass.simpleName, this::dump)
+ }
+
+ override fun stop() {
+ dumpManager.unregisterDumpable(javaClass.simpleName)
+ }
+
+ override fun onStatusBarBoundsChanged() {
+ val params = lastSystemBarAttributesParams
+ if (params != null && shouldUseLetterboxAppearance(params.letterboxesArray)) {
+ onSystemBarAttributesChanged(
+ params.displayId,
+ params.appearance,
+ params.appearanceRegionsArray,
+ params.navbarColorManagedByIme,
+ params.behavior,
+ params.requestedVisibilities,
+ params.packageName,
+ params.letterboxesArray)
+ }
+ }
+
+ fun onSystemBarAttributesChanged(
+ displayId: Int,
+ @Appearance originalAppearance: Int,
+ originalAppearanceRegions: Array<AppearanceRegion>,
+ navbarColorManagedByIme: Boolean,
+ @Behavior behavior: Int,
+ requestedVisibilities: InsetsVisibilities,
+ packageName: String,
+ letterboxDetails: Array<LetterboxDetails>
+ ) {
+ lastSystemBarAttributesParams =
+ SystemBarAttributesParams(
+ displayId,
+ originalAppearance,
+ originalAppearanceRegions.toList(),
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails.toList())
+
+ val (appearance, appearanceRegions) =
+ modifyAppearanceIfNeeded(
+ originalAppearance, originalAppearanceRegions, letterboxDetails)
+
+ val barModeChanged = centralSurfaces.setAppearance(appearance)
+
+ lightBarController.onStatusBarAppearanceChanged(
+ appearanceRegions, barModeChanged, centralSurfaces.barMode, navbarColorManagedByIme)
+
+ centralSurfaces.updateBubblesVisibility()
+ statusBarStateController.setSystemBarAttributes(
+ appearance, behavior, requestedVisibilities, packageName)
+ }
+
+ private fun modifyAppearanceIfNeeded(
+ appearance: Int,
+ appearanceRegions: Array<AppearanceRegion>,
+ letterboxDetails: Array<LetterboxDetails>
+ ): Pair<Int, Array<AppearanceRegion>> =
+ if (shouldUseLetterboxAppearance(letterboxDetails)) {
+ val letterboxAppearance =
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ appearance, appearanceRegions, letterboxDetails)
+ lastLetterboxAppearance = letterboxAppearance
+ Pair(letterboxAppearance.appearance, letterboxAppearance.appearanceRegions)
+ } else {
+ lastLetterboxAppearance = null
+ Pair(appearance, appearanceRegions)
+ }
+
+ private fun shouldUseLetterboxAppearance(letterboxDetails: Array<LetterboxDetails>) =
+ isLetterboxAppearanceFlagEnabled() && letterboxDetails.isNotEmpty()
+
+ private fun isLetterboxAppearanceFlagEnabled() =
+ featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)
+
+ private fun dump(printWriter: PrintWriter, strings: Array<String>) {
+ printWriter.println("lastSystemBarAttributesParams: $lastSystemBarAttributesParams")
+ printWriter.println("lastLetterboxAppearance: $lastLetterboxAppearance")
+ printWriter.println("letterbox appearance flag: ${isLetterboxAppearanceFlagEnabled()}")
+ }
+}
+
+/**
+ * Keeps track of the parameters passed in
+ * [SystemBarAttributesListener.onSystemBarAttributesChanged].
+ */
+private data class SystemBarAttributesParams(
+ val displayId: Int,
+ @Appearance val appearance: Int,
+ val appearanceRegions: List<AppearanceRegion>,
+ val navbarColorManagedByIme: Boolean,
+ @Behavior val behavior: Int,
+ val requestedVisibilities: InsetsVisibilities,
+ val packageName: String,
+ val letterboxes: List<LetterboxDetails>,
+) {
+ val letterboxesArray = letterboxes.toTypedArray()
+ val appearanceRegionsArray = appearanceRegions.toTypedArray()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index 36a045637a87..26bc3e3c0920 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -93,6 +93,17 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
}
public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock) {
+ // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
+ // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
+ // the content and attach listeners.
+ this(context, theme, dismissOnDeviceLock, Dependency.get(SystemUIDialogManager.class),
+ Dependency.get(SysUiState.class), Dependency.get(BroadcastDispatcher.class),
+ Dependency.get(DialogLaunchAnimator.class));
+ }
+
+ public SystemUIDialog(Context context, int theme, boolean dismissOnDeviceLock,
+ SystemUIDialogManager dialogManager, SysUiState sysUiState,
+ BroadcastDispatcher broadcastDispatcher, DialogLaunchAnimator dialogLaunchAnimator) {
super(context, theme);
mContext = context;
@@ -101,13 +112,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
attrs.setTitle(getClass().getSimpleName());
getWindow().setAttributes(attrs);
- mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this) : null;
-
- // TODO(b/219008720): Remove those calls to Dependency.get by introducing a
- // SystemUIDialogFactory and make all other dialogs create a SystemUIDialog to which we set
- // the content and attach listeners.
- mDialogManager = Dependency.get(SystemUIDialogManager.class);
- mSysUiState = Dependency.get(SysUiState.class);
+ mDismissReceiver = dismissOnDeviceLock ? new DismissReceiver(this, broadcastDispatcher,
+ dialogLaunchAnimator) : null;
+ mDialogManager = dialogManager;
+ mSysUiState = sysUiState;
}
@Override
@@ -326,7 +334,10 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
* @param dismissAction An action to run when the dialog is dismissed.
*/
public static void registerDismissListener(Dialog dialog, @Nullable Runnable dismissAction) {
- DismissReceiver dismissReceiver = new DismissReceiver(dialog);
+ // TODO(b/219008720): Remove those calls to Dependency.get.
+ DismissReceiver dismissReceiver = new DismissReceiver(dialog,
+ Dependency.get(BroadcastDispatcher.class),
+ Dependency.get(DialogLaunchAnimator.class));
dialog.setOnDismissListener(d -> {
dismissReceiver.unregister();
if (dismissAction != null) dismissAction.run();
@@ -408,11 +419,11 @@ public class SystemUIDialog extends AlertDialog implements ViewRootImpl.ConfigCh
private final BroadcastDispatcher mBroadcastDispatcher;
private final DialogLaunchAnimator mDialogLaunchAnimator;
- DismissReceiver(Dialog dialog) {
+ DismissReceiver(Dialog dialog, BroadcastDispatcher broadcastDispatcher,
+ DialogLaunchAnimator dialogLaunchAnimator) {
mDialog = dialog;
- // TODO(b/219008720): Remove those calls to Dependency.get.
- mBroadcastDispatcher = Dependency.get(BroadcastDispatcher.class);
- mDialogLaunchAnimator = Dependency.get(DialogLaunchAnimator.class);
+ mBroadcastDispatcher = broadcastDispatcher;
+ mDialogLaunchAnimator = dialogLaunchAnimator;
}
void register() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
index c5e5297ae6ba..64b04e93e69c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesComponent.java
@@ -22,6 +22,10 @@ import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.keyguard.LockIconViewController;
import com.android.systemui.biometrics.AuthRippleController;
+import com.android.systemui.shade.LargeScreenShadeHeaderController;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.core.StatusBarInitializer;
@@ -32,10 +36,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutListContainerModule;
import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks;
import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.LargeScreenShadeHeaderController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.phone.StatusBarHeadsUpChangeListener;
import com.android.systemui.statusbar.phone.StatusBarNotificationActivityStarterModule;
import com.android.systemui.statusbar.phone.StatusBarNotificationPresenterModule;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
index 21e5ad5778b1..d57e6a791489 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/CentralSurfacesStartableModule.java
@@ -16,13 +16,28 @@
package com.android.systemui.statusbar.phone.dagger;
+import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
+
import java.util.Set;
+import dagger.Binds;
import dagger.Module;
+import dagger.multibindings.IntoSet;
import dagger.multibindings.Multibinds;
@Module
interface CentralSurfacesStartableModule {
@Multibinds
Set<CentralSurfacesComponent.Startable> multibindStartables();
+
+ @Binds
+ @IntoSet
+ CentralSurfacesComponent.Startable letterboxAppearanceCalculator(
+ LetterboxAppearanceCalculator letterboxAppearanceCalculator);
+
+ @Binds
+ @IntoSet
+ CentralSurfacesComponent.Startable sysBarAttrsListener(
+ SystemBarAttributesListener systemBarAttributesListener);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
index 06532c4f9d17..c4e7a8a2c17b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarViewModule.java
@@ -30,27 +30,35 @@ import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.biometrics.AuthRippleView;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.privacy.OngoingPrivacyChip;
+import com.android.systemui.shade.CombinedShadeHeadersConstraintManager;
+import com.android.systemui.shade.CombinedShadeHeadersConstraintManagerImpl;
+import com.android.systemui.shade.NotificationPanelView;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.NotificationsQuickSettingsContainer;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationShelf;
import com.android.systemui.statusbar.NotificationShelfController;
import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.connectivity.NetworkController;
+import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewInitializedListener;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.notification.row.dagger.NotificationShelfComponent;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.LetterboxAppearanceCalculator;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationPanelView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.NotificationsQuickSettingsContainer;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.StatusIconContainer;
+import com.android.systemui.statusbar.phone.SystemBarAttributesListener;
import com.android.systemui.statusbar.phone.TapAgainView;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragmentLogger;
@@ -68,8 +76,10 @@ import java.util.concurrent.Executor;
import javax.inject.Named;
+import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.IntoSet;
@Module(subcomponents = StatusBarFragmentComponent.class)
public abstract class StatusBarViewModule {
@@ -177,6 +187,14 @@ public abstract class StatusBarViewModule {
/** */
@Provides
@CentralSurfacesComponent.CentralSurfacesScope
+ public static CombinedShadeHeadersConstraintManager
+ provideCombinedShadeHeadersConstraintManager() {
+ return CombinedShadeHeadersConstraintManagerImpl.INSTANCE;
+ }
+
+ /** */
+ @Provides
+ @CentralSurfacesComponent.CentralSurfacesScope
public static OngoingPrivacyChip getSplitShadeOngoingPrivacyChip(
@Named(LARGE_SCREEN_SHADE_HEADER) View header) {
return header.findViewById(R.id.privacy_chip);
@@ -236,6 +254,17 @@ public abstract class StatusBarViewModule {
return notificationShadeWindowView.findViewById(R.id.notification_container_parent);
}
+ @Binds
+ @IntoSet
+ abstract OnStatusBarViewInitializedListener statusBarInitializedListener(
+ LetterboxAppearanceCalculator letterboxAppearanceCalculator
+ );
+
+ @Binds
+ @IntoSet
+ abstract StatusBarBoundsProvider.BoundsChangeListener sysBarAttrsListenerAsBoundsListener(
+ SystemBarAttributesListener systemBarAttributesListener);
+
/**
* Creates a new {@link CollapsedStatusBarFragment}.
*
@@ -268,7 +297,8 @@ public abstract class StatusBarViewModule {
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
- @Main Executor mainExecutor
+ @Main Executor mainExecutor,
+ DumpManager dumpManager
) {
return new CollapsedStatusBarFragment(statusBarFragmentComponentFactory,
ongoingCallController,
@@ -288,6 +318,20 @@ public abstract class StatusBarViewModule {
collapsedStatusBarFragmentLogger,
operatorNameViewControllerFactory,
secureSettings,
- mainExecutor);
+ mainExecutor,
+ dumpManager);
}
+
+ /**
+ * Constructs a new, unattached {@link KeyguardBottomAreaView}.
+ *
+ * Note that this is explicitly _not_ a singleton, as we want to be able to reinflate it
+ */
+ @Provides
+ public static KeyguardBottomAreaView providesKeyguardBottomAreaView(
+ NotificationPanelView npv, LayoutInflater layoutInflater) {
+ return (KeyguardBottomAreaView) layoutInflater.inflate(R
+ .layout.keyguard_bottom_area, npv, false);
+ }
+
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 597c949168d4..0848729781bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -34,6 +34,8 @@ import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.telephony.SubscriptionManager;
+import android.util.ArrayMap;
+import android.util.IndentingPrintWriter;
import android.util.SparseArray;
import android.view.LayoutInflater;
import android.view.View;
@@ -43,11 +45,14 @@ import android.widget.LinearLayout;
import androidx.annotation.VisibleForTesting;
+import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger.DisableState;
import com.android.systemui.statusbar.OperatorNameView;
@@ -59,13 +64,13 @@ import com.android.systemui.statusbar.connectivity.SignalCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationCallback;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarIconController.DarkIconManager;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent;
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent.Startable;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController;
import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
@@ -76,9 +81,12 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen
import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener;
import com.android.systemui.util.settings.SecureSettings;
+import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import java.util.concurrent.Executor;
/**
@@ -89,7 +97,7 @@ import java.util.concurrent.Executor;
@SuppressLint("ValidFragment")
public class CollapsedStatusBarFragment extends Fragment implements CommandQueue.Callbacks,
StatusBarStateController.StateListener,
- SystemStatusAnimationCallback {
+ SystemStatusAnimationCallback, Dumpable {
public static final String TAG = "CollapsedStatusBarFragment";
private static final String EXTRA_PANEL_STATE = "panel_state";
@@ -102,7 +110,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final KeyguardStateController mKeyguardStateController;
private final NotificationPanelViewController mNotificationPanelViewController;
private final NetworkController mNetworkController;
- private LinearLayout mSystemIconArea;
+ private LinearLayout mEndSideContent;
private View mClockView;
private View mOngoingCallChip;
private View mNotificationIconAreaInner;
@@ -124,8 +132,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
private final SecureSettings mSecureSettings;
private final Executor mMainExecutor;
+ private final DumpManager mDumpManager;
private List<String> mBlockedIcons = new ArrayList<>();
+ private Map<Startable, Startable.State> mStartableStates = new ArrayMap<>();
private SignalCallback mSignalCallback = new SignalCallback() {
@Override
@@ -185,7 +195,8 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
CollapsedStatusBarFragmentLogger collapsedStatusBarFragmentLogger,
OperatorNameViewController.Factory operatorNameViewControllerFactory,
SecureSettings secureSettings,
- @Main Executor mainExecutor
+ @Main Executor mainExecutor,
+ DumpManager dumpManager
) {
mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory;
mOngoingCallController = ongoingCallController;
@@ -206,6 +217,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mOperatorNameViewControllerFactory = operatorNameViewControllerFactory;
mSecureSettings = secureSettings;
mMainExecutor = mainExecutor;
+ mDumpManager = dumpManager;
}
@Override
@@ -217,8 +229,15 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
+ mDumpManager.registerDumpable(getClass().getSimpleName(), this);
mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create(this);
mStatusBarFragmentComponent.init();
+ mStartableStates.clear();
+ for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+ mStartableStates.put(startable, Startable.State.STARTING);
+ startable.start();
+ mStartableStates.put(startable, Startable.State.STARTED);
+ }
mStatusBar = (PhoneStatusBarView) view;
View contents = mStatusBar.findViewById(R.id.status_bar_contents);
@@ -232,16 +251,16 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mDarkIconManager.setShouldLog(true);
updateBlockedIcons();
mStatusBarIconController.addIconGroup(mDarkIconManager);
- mSystemIconArea = mStatusBar.findViewById(R.id.system_icon_area);
+ mEndSideContent = mStatusBar.findViewById(R.id.status_bar_end_side_content);
mClockView = mStatusBar.findViewById(R.id.clock);
mOngoingCallChip = mStatusBar.findViewById(R.id.ongoing_call_chip);
- showSystemIconArea(false);
+ showEndSideContent(false);
showClock(false);
initEmergencyCryptkeeperText();
initOperatorName();
initNotificationIconArea();
mSystemEventAnimator =
- new StatusBarSystemEventAnimator(mSystemIconArea, getResources());
+ new StatusBarSystemEventAnimator(mEndSideContent, getResources());
mCarrierConfigTracker.addCallback(mCarrierConfigCallback);
mCarrierConfigTracker.addDefaultDataSubscriptionChangedListener(mDefaultDataListener);
}
@@ -321,6 +340,13 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
}
mCarrierConfigTracker.removeCallback(mCarrierConfigCallback);
mCarrierConfigTracker.removeDataSubscriptionChangedListener(mDefaultDataListener);
+
+ for (Startable startable : mStatusBarFragmentComponent.getStartables()) {
+ mStartableStates.put(startable, Startable.State.STOPPING);
+ startable.stop();
+ mStartableStates.put(startable, Startable.State.STOPPED);
+ }
+ mDumpManager.unregisterDumpable(getClass().getSimpleName());
}
/** Initializes views related to the notification icon area. */
@@ -370,10 +396,10 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
mDisabled2 = state2;
if ((diff1 & DISABLE_SYSTEM_INFO) != 0 || ((diff2 & DISABLE2_SYSTEM_ICONS) != 0)) {
if ((state1 & DISABLE_SYSTEM_INFO) != 0 || ((state2 & DISABLE2_SYSTEM_ICONS) != 0)) {
- hideSystemIconArea(animate);
+ hideEndSideContent(animate);
hideOperatorName(animate);
} else {
- showSystemIconArea(animate);
+ showEndSideContent(animate);
showOperatorName(animate);
}
}
@@ -474,15 +500,15 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
return mStatusBarHideIconsForBouncerManager.getShouldHideStatusBarIconsForBouncer();
}
- private void hideSystemIconArea(boolean animate) {
- animateHide(mSystemIconArea, animate);
+ private void hideEndSideContent(boolean animate) {
+ animateHide(mEndSideContent, animate);
}
- private void showSystemIconArea(boolean animate) {
+ private void showEndSideContent(boolean animate) {
// Only show the system icon area if we are not currently animating
int state = mAnimationScheduler.getAnimationState();
if (state == IDLE || state == SHOWING_PERSISTENT_DOT) {
- animateShow(mSystemIconArea, animate);
+ animateShow(mEndSideContent, animate);
}
}
@@ -670,4 +696,23 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue
updateStatusBarLocation(left, right);
}
};
+
+ @Override
+ public void dump(PrintWriter printWriter, String[] args) {
+ IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, /* singleIndent= */" ");
+ StatusBarFragmentComponent component = mStatusBarFragmentComponent;
+ if (component == null) {
+ pw.println("StatusBarFragmentComponent is null");
+ } else {
+ Set<Startable> startables = component.getStartables();
+ pw.println("Startables: " + startables.size());
+ pw.increaseIndent();
+ for (Startable startable : startables) {
+ Startable.State startableState = mStartableStates.getOrDefault(startable,
+ Startable.State.NONE);
+ pw.println(startable + ", state: " + startableState);
+ }
+ pw.decreaseIndent();
+ }
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
index 6717bc768fbb..d9a58442d856 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentComponent.java
@@ -23,9 +23,12 @@ import com.android.systemui.statusbar.phone.LightsOutNotifController;
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.StatusBarDemoMode;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
+import java.util.Set;
+
import dagger.BindsInstance;
import dagger.Subcomponent;
@@ -41,7 +44,10 @@ import dagger.Subcomponent;
* should be included here or in {@link StatusBarFragmentModule}.
*/
-@Subcomponent(modules = {StatusBarFragmentModule.class})
+@Subcomponent(modules = {
+ StatusBarFragmentModule.class,
+ StatusBarStartablesModule.class
+})
@StatusBarFragmentScope
public interface StatusBarFragmentComponent {
/** Simple factory. */
@@ -52,6 +58,18 @@ public interface StatusBarFragmentComponent {
}
/**
+ * Performs initialization logic after {@link StatusBarFragmentComponent} has been constructed.
+ */
+ interface Startable {
+ void start();
+ void stop();
+
+ enum State {
+ NONE, STARTING, STARTED, STOPPING, STOPPED
+ }
+ }
+
+ /**
* Initialize anything extra for the component. Must be called after the component is created.
*/
default void init() {
@@ -92,4 +110,10 @@ public interface StatusBarFragmentComponent {
/** */
@StatusBarFragmentScope
PhoneStatusBarTransitions getPhoneStatusBarTransitions();
+
+ /** */
+ Set<Startable> getStartables();
+
+ /** */
+ StatusBarBoundsProvider getBoundsProvider();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
index d5f5362eaf3c..41f1f9589ce4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarFragmentModule.java
@@ -21,11 +21,12 @@ import android.view.View;
import com.android.systemui.R;
import com.android.systemui.battery.BatteryMeterView;
import com.android.systemui.dagger.qualifiers.RootView;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions;
import com.android.systemui.statusbar.phone.PhoneStatusBarView;
import com.android.systemui.statusbar.phone.PhoneStatusBarViewController;
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider;
import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController;
@@ -34,12 +35,14 @@ import com.android.systemui.statusbar.policy.Clock;
import com.android.systemui.statusbar.window.StatusBarWindowController;
import java.util.Optional;
+import java.util.Set;
import javax.inject.Named;
import dagger.Binds;
import dagger.Module;
import dagger.Provides;
+import dagger.multibindings.Multibinds;
/** Dagger module for {@link StatusBarFragmentComponent}. */
@Module
@@ -48,6 +51,8 @@ public interface StatusBarFragmentModule {
String LIGHTS_OUT_NOTIF_VIEW = "lights_out_notif_view";
String OPERATOR_NAME_VIEW = "operator_name_view";
String OPERATOR_NAME_FRAME_VIEW = "operator_name_frame_view";
+ String START_SIDE_CONTENT = "start_side_content";
+ String END_SIDE_CONTENT = "end_side_content";
/** */
@Provides
@@ -68,6 +73,22 @@ public interface StatusBarFragmentModule {
/** */
@Provides
@StatusBarFragmentScope
+ @Named(START_SIDE_CONTENT)
+ static View startSideContent(@RootView PhoneStatusBarView view) {
+ return view.findViewById(R.id.status_bar_start_side_content);
+ }
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
+ @Named(END_SIDE_CONTENT)
+ static View endSideContent(@RootView PhoneStatusBarView view) {
+ return view.findViewById(R.id.status_bar_end_side_content);
+ }
+
+ /** */
+ @Provides
+ @StatusBarFragmentScope
@Named(LIGHTS_OUT_NOTIF_VIEW)
static View provideLightsOutNotifView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.notification_lights_out);
@@ -138,4 +159,8 @@ public interface StatusBarFragmentModule {
static HeadsUpStatusBarView providesHeasdUpStatusBarView(@RootView PhoneStatusBarView view) {
return view.findViewById(R.id.heads_up_status_bar_view);
}
+
+ /** */
+ @Multibinds
+ Set<StatusBarBoundsProvider.BoundsChangeListener> boundsChangeListeners();
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
new file mode 100644
index 000000000000..9003d13df0a0
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/dagger/StatusBarStartablesModule.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone.fragment.dagger
+
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+internal interface StatusBarStartablesModule {
+
+ @Binds
+ @IntoSet
+ fun statusBarBoundsCalculator(
+ statusBarBoundsProvider: StatusBarBoundsProvider
+ ): StatusBarFragmentComponent.Startable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
index c0384b444511..7c61b299cff9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionChangeEvent.kt
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.systemui.statusbar.phone.panelstate
import android.annotation.FloatRange
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
index 911e750f666e..a6160aaf7756 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/panelstate/PanelExpansionStateManager.kt
@@ -20,6 +20,7 @@ import android.annotation.IntDef
import android.util.Log
import androidx.annotation.FloatRange
import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.util.Compile
import javax.inject.Inject
/**
@@ -109,8 +110,8 @@ class PanelExpansionStateManager @Inject constructor() {
debugLog(
"panelExpansionChanged:" +
- "start state=${oldState.stateToString()} " +
- "end state=${state.stateToString()} " +
+ "start state=${oldState.panelStateToString()} " +
+ "end state=${state.panelStateToString()} " +
"f=$fraction " +
"expanded=$expanded " +
"tracking=$tracking" +
@@ -126,14 +127,15 @@ class PanelExpansionStateManager @Inject constructor() {
/** Updates the panel state if necessary. */
fun updateState(@PanelState state: Int) {
- debugLog("update state: ${this.state.stateToString()} -> ${state.stateToString()}")
+ debugLog(
+ "update state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
if (this.state != state) {
updateStateInternal(state)
}
}
private fun updateStateInternal(@PanelState state: Int) {
- debugLog("go state: ${this.state.stateToString()} -> ${state.stateToString()}")
+ debugLog("go state: ${this.state.panelStateToString()} -> ${state.panelStateToString()}")
this.state = state
stateListeners.forEach { it.onPanelStateChanged(state) }
}
@@ -154,7 +156,7 @@ const val STATE_OPENING = 1
const val STATE_OPEN = 2
@PanelState
-private fun Int.stateToString(): String {
+fun Int.panelStateToString(): String {
return when (this) {
STATE_CLOSED -> "CLOSED"
STATE_OPENING -> "OPENING"
@@ -163,5 +165,5 @@ private fun Int.stateToString(): String {
}
}
-private const val DEBUG = false
private val TAG = PanelExpansionStateManager::class.simpleName
+private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
new file mode 100644
index 000000000000..bd6cf9a1d101
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/ConnectivityInfoProcessor.kt
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+/**
+ * A processor that transforms raw connectivity information that we get from callbacks and turns it
+ * into a list of displayable connectivity information.
+ *
+ * This will be used for the new status bar pipeline to calculate the list of icons that should be
+ * displayed in the RHS of the status bar.
+ */
+@SysUISingleton
+class ConnectivityInfoProcessor @Inject constructor()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
new file mode 100644
index 000000000000..734bd2d8e5b3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/dagger/StatusBarPipelineModule.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.pipeline.ConnectivityInfoProcessor
+import dagger.Lazy
+import dagger.Module
+import dagger.Provides
+import java.util.Optional
+
+@Module
+class StatusBarPipelineModule {
+ @Provides
+ @SysUISingleton
+ fun provideConnectivityInfoProcessor(
+ featureFlags: FeatureFlags,
+ processorLazy: Lazy<ConnectivityInfoProcessor>
+ ): Optional<ConnectivityInfoProcessor> {
+ return if (featureFlags.isEnabled(Flags.NEW_STATUS_BAR_PIPELINE)) {
+ Optional.of(processorLazy.get())
+ } else {
+ Optional.empty()
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
index a89c128dd584..753e94015751 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryController.java
@@ -119,6 +119,17 @@ public interface BatteryController extends DemoMode, Dumpable,
}
/**
+ * Returns {@code true} if the charging source is
+ * {@link android.os.BatteryManager#BATTERY_PLUGGED_DOCK}.
+ *
+ * <P>Note that charging from dock is not considered as wireless charging. In other words,
+ * {@link BatteryController#isWirelessCharging()} and this are mutually exclusive.
+ */
+ default boolean isChargingSourceDock() {
+ return false;
+ }
+
+ /**
* A listener that will be notified whenever a change in battery level or power save mode has
* occurred.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
index 917a5e0b9374..33ddf7eed006 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BatteryControllerImpl.java
@@ -76,7 +76,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
protected int mLevel;
protected boolean mPluggedIn;
- private boolean mPluggedInWireless;
+ private int mPluggedChargingSource;
protected boolean mCharging;
private boolean mStateUnknown = false;
private boolean mCharged;
@@ -195,10 +195,8 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
mLevel = (int)(100f
* intent.getIntExtra(BatteryManager.EXTRA_LEVEL, 0)
/ intent.getIntExtra(BatteryManager.EXTRA_SCALE, 100));
- mPluggedIn = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0) != 0;
- mPluggedInWireless = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0)
- == BatteryManager.BATTERY_PLUGGED_WIRELESS;
-
+ mPluggedChargingSource = intent.getIntExtra(BatteryManager.EXTRA_PLUGGED, 0);
+ mPluggedIn = mPluggedChargingSource != 0;
final int status = intent.getIntExtra(BatteryManager.EXTRA_STATUS,
BatteryManager.BATTERY_STATUS_UNKNOWN);
mCharged = status == BatteryManager.BATTERY_STATUS_FULL;
@@ -284,7 +282,7 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
@Override
public boolean isPluggedInWireless() {
- return mPluggedInWireless;
+ return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_WIRELESS;
}
@Override
@@ -441,4 +439,9 @@ public class BatteryControllerImpl extends BroadcastReceiver implements BatteryC
registerReceiver();
updatePowerSave();
}
+
+ @Override
+ public boolean isChargingSourceDock() {
+ return mPluggedChargingSource == BatteryManager.BATTERY_PLUGGED_DOCK;
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 5bd20ff2d090..86e74564fba0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -28,9 +28,9 @@ import android.widget.FrameLayout;
import com.android.systemui.R;
import com.android.systemui.settings.brightness.BrightnessSliderController;
import com.android.systemui.settings.brightness.ToggleSlider;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.statusbar.NotificationShadeDepthController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import java.util.Objects;
import java.util.function.Consumer;
@@ -68,6 +68,7 @@ public class BrightnessMirrorController
mBrightnessMirror.setVisibility(View.INVISIBLE);
});
mVisibilityCallback = visibilityCallback;
+ updateResources();
}
public void showMirror() {
@@ -154,6 +155,7 @@ public class BrightnessMirrorController
.inflate(R.layout.brightness_mirror_container, mStatusBarWindow, false);
mToggleSliderController = setMirrorLayout();
mStatusBarWindow.addView(mBrightnessMirror, index);
+ updateResources();
for (int i = 0; i < mBrightnessMirrorListeners.size(); i++) {
mBrightnessMirrorListeners.valueAt(i).onBrightnessMirrorReinflated(mBrightnessMirror);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
index 4cf1d2b3f91d..9946b4b9ecaa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/FlashlightControllerImpl.java
@@ -17,15 +17,11 @@
package com.android.systemui.statusbar.policy;
import android.annotation.WorkerThread;
-import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Process;
import android.provider.Settings;
import android.provider.Settings.Secure;
import android.text.TextUtils;
@@ -33,12 +29,19 @@ import android.util.Log;
import androidx.annotation.NonNull;
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.broadcast.BroadcastSender;
import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.util.settings.SecureSettings;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
@@ -59,65 +62,88 @@ public class FlashlightControllerImpl implements FlashlightController {
"com.android.settings.flashlight.action.FLASHLIGHT_CHANGED";
private final CameraManager mCameraManager;
- private final Context mContext;
- /** Call {@link #ensureHandler()} before using */
- private Handler mHandler;
+ private final Executor mExecutor;
+ private final SecureSettings mSecureSettings;
+ private final DumpManager mDumpManager;
+ private final BroadcastSender mBroadcastSender;
- /** Lock on mListeners when accessing */
+ private final boolean mHasFlashlight;
+
+ @GuardedBy("mListeners")
private final ArrayList<WeakReference<FlashlightListener>> mListeners = new ArrayList<>(1);
- /** Lock on {@code this} when accessing */
+ @GuardedBy("this")
private boolean mFlashlightEnabled;
-
- private String mCameraId;
+ @GuardedBy("this")
private boolean mTorchAvailable;
+ private final AtomicReference<String> mCameraId;
+ private final AtomicBoolean mInitted = new AtomicBoolean(false);
+
@Inject
- public FlashlightControllerImpl(Context context, DumpManager dumpManager) {
- mContext = context;
- mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
+ public FlashlightControllerImpl(
+ DumpManager dumpManager,
+ CameraManager cameraManager,
+ @Background Executor bgExecutor,
+ SecureSettings secureSettings,
+ BroadcastSender broadcastSender,
+ PackageManager packageManager
+ ) {
+ mCameraManager = cameraManager;
+ mExecutor = bgExecutor;
+ mCameraId = new AtomicReference<>(null);
+ mSecureSettings = secureSettings;
+ mDumpManager = dumpManager;
+ mBroadcastSender = broadcastSender;
+
+ mHasFlashlight = packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ init();
+ }
- dumpManager.registerDumpable(getClass().getSimpleName(), this);
- tryInitCamera();
+ private void init() {
+ if (!mInitted.getAndSet(true)) {
+ mDumpManager.registerDumpable(getClass().getSimpleName(), this);
+ mExecutor.execute(this::tryInitCamera);
+ }
}
+ @WorkerThread
private void tryInitCamera() {
+ if (!mHasFlashlight || mCameraId.get() != null) return;
try {
- mCameraId = getCameraId();
+ mCameraId.set(getCameraId());
} catch (Throwable e) {
Log.e(TAG, "Couldn't initialize.", e);
return;
}
- if (mCameraId != null) {
- ensureHandler();
- mCameraManager.registerTorchCallback(mTorchCallback, mHandler);
+ if (mCameraId.get() != null) {
+ mCameraManager.registerTorchCallback(mExecutor, mTorchCallback);
}
}
public void setFlashlight(boolean enabled) {
- boolean pendingError = false;
- synchronized (this) {
- if (mCameraId == null) return;
- if (mFlashlightEnabled != enabled) {
- mFlashlightEnabled = enabled;
- try {
- mCameraManager.setTorchMode(mCameraId, enabled);
- } catch (CameraAccessException e) {
- Log.e(TAG, "Couldn't set torch mode", e);
- mFlashlightEnabled = false;
- pendingError = true;
+ if (!mHasFlashlight) return;
+ if (mCameraId.get() == null) {
+ mExecutor.execute(this::tryInitCamera);
+ }
+ mExecutor.execute(() -> {
+ if (mCameraId.get() == null) return;
+ synchronized (this) {
+ if (mFlashlightEnabled != enabled) {
+ try {
+ mCameraManager.setTorchMode(mCameraId.get(), enabled);
+ } catch (CameraAccessException e) {
+ Log.e(TAG, "Couldn't set torch mode", e);
+ dispatchError();
+ }
}
}
- }
- dispatchModeChanged(mFlashlightEnabled);
- if (pendingError) {
- dispatchError();
- }
+ });
}
public boolean hasFlashlight() {
- return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH);
+ return mHasFlashlight;
}
public synchronized boolean isEnabled() {
@@ -131,13 +157,13 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
public void addCallback(@NonNull FlashlightListener l) {
synchronized (mListeners) {
- if (mCameraId == null) {
- tryInitCamera();
+ if (mCameraId.get() == null) {
+ mExecutor.execute(this::tryInitCamera);
}
cleanUpListenersLocked(l);
mListeners.add(new WeakReference<>(l));
- l.onFlashlightAvailabilityChanged(mTorchAvailable);
- l.onFlashlightChanged(mFlashlightEnabled);
+ l.onFlashlightAvailabilityChanged(isAvailable());
+ l.onFlashlightChanged(isEnabled());
}
}
@@ -148,14 +174,7 @@ public class FlashlightControllerImpl implements FlashlightController {
}
}
- private synchronized void ensureHandler() {
- if (mHandler == null) {
- HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
- thread.start();
- mHandler = new Handler(thread.getLooper());
- }
- }
-
+ @WorkerThread
private String getCameraId() throws CameraAccessException {
String[] ids = mCameraManager.getCameraIdList();
for (String id : ids) {
@@ -221,10 +240,9 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
@WorkerThread
public void onTorchModeUnavailable(String cameraId) {
- if (TextUtils.equals(cameraId, mCameraId)) {
+ if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(false);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
+ mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 0);
}
}
@@ -232,14 +250,12 @@ public class FlashlightControllerImpl implements FlashlightController {
@Override
@WorkerThread
public void onTorchModeChanged(String cameraId, boolean enabled) {
- if (TextUtils.equals(cameraId, mCameraId)) {
+ if (TextUtils.equals(cameraId, mCameraId.get())) {
setCameraAvailable(true);
setTorchMode(enabled);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
- Settings.Secure.putInt(
- mContext.getContentResolver(), Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
- mContext.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
+ mSecureSettings.putInt(Settings.Secure.FLASHLIGHT_AVAILABLE, 1);
+ mSecureSettings.putInt(Secure.FLASHLIGHT_ENABLED, enabled ? 1 : 0);
+ mBroadcastSender.sendBroadcast(new Intent(ACTION_FLASHLIGHT_CHANGED));
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
index bce5a159f79c..d3837d7347dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.java
@@ -142,7 +142,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
protected void setEntryPinned(
@NonNull HeadsUpManager.HeadsUpEntry headsUpEntry, boolean isPinned) {
- mLogger.logSetEntryPinned(headsUpEntry.mEntry.getKey(), isPinned);
+ mLogger.logSetEntryPinned(headsUpEntry.mEntry, isPinned);
NotificationEntry entry = headsUpEntry.mEntry;
if (entry.isRowPinned() != isPinned) {
entry.setRowPinned(isPinned);
@@ -183,7 +183,7 @@ public abstract class HeadsUpManager extends AlertingNotificationManager {
entry.setHeadsUp(false);
setEntryPinned((HeadsUpEntry) alertEntry, false /* isPinned */);
EventLogTags.writeSysuiHeadsUpStatus(entry.getKey(), 0 /* visible */);
- mLogger.logNotificationActuallyRemoved(entry.getKey());
+ mLogger.logNotificationActuallyRemoved(entry);
for (OnHeadsUpChangedListener listener : mListeners) {
listener.onHeadsUpStateChanged(entry, false);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 6a74ba957b4b..d7c81af53d8b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -20,6 +20,8 @@ import com.android.systemui.log.LogBuffer
import com.android.systemui.log.LogLevel.INFO
import com.android.systemui.log.LogLevel.VERBOSE
import com.android.systemui.log.dagger.NotificationHeadsUpLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.logKey
import javax.inject.Inject
/** Logger for [HeadsUpManager]. */
@@ -56,9 +58,9 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
- fun logShowNotification(key: String) {
+ fun logShowNotification(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"show notification $str1"
})
@@ -66,16 +68,16 @@ class HeadsUpManagerLogger @Inject constructor(
fun logRemoveNotification(key: String, releaseImmediately: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = logKey(key)
bool1 = releaseImmediately
}, {
"remove notification $str1 releaseImmediately: $bool1"
})
}
- fun logNotificationActuallyRemoved(key: String) {
+ fun logNotificationActuallyRemoved(entry: NotificationEntry) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
}, {
"notification removed $str1 "
})
@@ -83,7 +85,7 @@ class HeadsUpManagerLogger @Inject constructor(
fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = logKey(key)
bool1 = alert
bool2 = hasEntry
}, {
@@ -91,12 +93,12 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
- fun logUpdateEntry(key: String, updatePostTime: Boolean) {
+ fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean) {
buffer.log(TAG, INFO, {
- str1 = key
+ str1 = entry.logKey
bool1 = updatePostTime
}, {
- "update entry $key updatePostTime: $bool1"
+ "update entry $str1 updatePostTime: $bool1"
})
}
@@ -108,9 +110,9 @@ class HeadsUpManagerLogger @Inject constructor(
})
}
- fun logSetEntryPinned(key: String, isPinned: Boolean) {
+ fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean) {
buffer.log(TAG, VERBOSE, {
- str1 = key
+ str1 = entry.logKey
bool1 = isPinned
}, {
"set entry pinned $str1 pinned: $bool1"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 2fb16ee9b3b9..bdac88837969 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -424,11 +424,6 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum
}
@Override
- public void onFaceUnlockStateChanged(boolean running, int userId) {
- update(false /* updateAlways */);
- }
-
- @Override
public void onStrongAuthStateChanged(int userId) {
update(false /* updateAlways */);
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
deleted file mode 100644
index 3d317143eb51..000000000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PreviewInflater.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.policy;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-
-import com.android.internal.widget.LockPatternUtils;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.ActivityIntentHelper;
-import com.android.systemui.statusbar.phone.KeyguardPreviewContainer;
-
-import java.util.List;
-
-/**
- * Utility class to inflate previews for phone and camera affordance.
- */
-public class PreviewInflater {
-
- private static final String TAG = "PreviewInflater";
-
- private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
- private final ActivityIntentHelper mActivityIntentHelper;
-
- private Context mContext;
- private LockPatternUtils mLockPatternUtils;
-
- public PreviewInflater(Context context, LockPatternUtils lockPatternUtils,
- ActivityIntentHelper activityIntentHelper) {
- mContext = context;
- mLockPatternUtils = lockPatternUtils;
- mActivityIntentHelper = activityIntentHelper;
- }
-
- public View inflatePreview(Intent intent) {
- WidgetInfo info = getWidgetInfo(intent);
- return inflatePreview(info);
- }
-
- public View inflatePreviewFromService(ComponentName componentName) {
- WidgetInfo info = getWidgetInfoFromService(componentName);
- return inflatePreview(info);
- }
-
- private KeyguardPreviewContainer inflatePreview(WidgetInfo info) {
- if (info == null) {
- return null;
- }
- View v = inflateWidgetView(info);
- if (v == null) {
- return null;
- }
- KeyguardPreviewContainer container = new KeyguardPreviewContainer(mContext, null);
- container.addView(v);
- return container;
- }
-
- private View inflateWidgetView(WidgetInfo widgetInfo) {
- View widgetView = null;
- try {
- Context appContext = mContext.createPackageContext(
- widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
- LayoutInflater appInflater = (LayoutInflater)
- appContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- appInflater = appInflater.cloneInContext(appContext);
- widgetView = appInflater.inflate(widgetInfo.layoutId, null, false);
- } catch (PackageManager.NameNotFoundException|RuntimeException e) {
- Log.w(TAG, "Error creating widget view", e);
- }
- return widgetView;
- }
-
- private WidgetInfo getWidgetInfoFromService(ComponentName componentName) {
- PackageManager packageManager = mContext.getPackageManager();
- // Look for the preview specified in the service meta-data
- try {
- Bundle metaData = packageManager.getServiceInfo(
- componentName, PackageManager.GET_META_DATA).metaData;
- return getWidgetInfoFromMetaData(componentName.getPackageName(), metaData);
- } catch (PackageManager.NameNotFoundException e) {
- Log.w(TAG, "Failed to load preview; " + componentName.flattenToShortString()
- + " not found", e);
- }
- return null;
- }
-
- private WidgetInfo getWidgetInfoFromMetaData(String contextPackage,
- Bundle metaData) {
- if (metaData == null) {
- return null;
- }
- int layoutId = metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
- if (layoutId == 0) {
- return null;
- }
- WidgetInfo info = new WidgetInfo();
- info.contextPackage = contextPackage;
- info.layoutId = layoutId;
- return info;
- }
-
- private WidgetInfo getWidgetInfo(Intent intent) {
- PackageManager packageManager = mContext.getPackageManager();
- int flags = PackageManager.MATCH_DEFAULT_ONLY
- | PackageManager.MATCH_DIRECT_BOOT_AWARE
- | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
- final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
- intent, flags, KeyguardUpdateMonitor.getCurrentUser());
- if (appList.size() == 0) {
- return null;
- }
- ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
- flags | PackageManager.GET_META_DATA,
- KeyguardUpdateMonitor.getCurrentUser());
- if (mActivityIntentHelper.wouldLaunchResolverActivity(resolved, appList)) {
- return null;
- }
- if (resolved == null || resolved.activityInfo == null) {
- return null;
- }
- return getWidgetInfoFromMetaData(resolved.activityInfo.packageName,
- resolved.activityInfo.metaData);
- }
-
- private static class WidgetInfo {
- String contextPackage;
- int layoutId;
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 1b685d0aad7a..62fc01f9cbbc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -45,6 +45,7 @@ import android.os.UserManager;
import android.provider.Settings;
import android.telephony.TelephonyCallback;
import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -61,11 +62,12 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.LatencyTracker;
import com.android.settingslib.RestrictedLockUtilsInternal;
import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
import com.android.systemui.Dumpable;
+import com.android.systemui.GuestResetOrExitSessionReceiver;
import com.android.systemui.GuestResumeSessionReceiver;
import com.android.systemui.R;
import com.android.systemui.SystemUISecondaryUserService;
+import com.android.systemui.animation.DialogCuj;
import com.android.systemui.animation.DialogLaunchAnimator;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.broadcast.BroadcastSender;
@@ -114,6 +116,9 @@ public class UserSwitcherController implements Dumpable {
private static final String PERMISSION_SELF = "com.android.systemui.permission.SELF";
private static final long MULTI_USER_JOURNEY_TIMEOUT = 20000l;
+ private static final String INTERACTION_JANK_ADD_NEW_USER_TAG = "add_new_user";
+ private static final String INTERACTION_JANK_EXIT_GUEST_MODE_TAG = "exit_guest_mode";
+
protected final Context mContext;
protected final UserTracker mUserTracker;
protected final UserManager mUserManager;
@@ -121,6 +126,8 @@ public class UserSwitcherController implements Dumpable {
private final ArrayList<WeakReference<BaseUserAdapter>> mAdapters = new ArrayList<>();
@VisibleForTesting
final GuestResumeSessionReceiver mGuestResumeSessionReceiver;
+ @VisibleForTesting
+ final GuestResetOrExitSessionReceiver mGuestResetOrExitSessionReceiver;
private final KeyguardStateController mKeyguardStateController;
private final DeviceProvisionedController mDeviceProvisionedController;
private final DevicePolicyManager mDevicePolicyManager;
@@ -159,6 +166,7 @@ public class UserSwitcherController implements Dumpable {
private final AtomicBoolean mGuestIsResetting;
private final AtomicBoolean mGuestCreationScheduled;
private FalsingManager mFalsingManager;
+ @Nullable
private View mView;
private String mCreateSupervisedUserPackage;
private GlobalSettings mGlobalSettings;
@@ -188,7 +196,9 @@ public class UserSwitcherController implements Dumpable {
InteractionJankMonitor interactionJankMonitor,
LatencyTracker latencyTracker,
DumpManager dumpManager,
- DialogLaunchAnimator dialogLaunchAnimator) {
+ DialogLaunchAnimator dialogLaunchAnimator,
+ GuestResumeSessionReceiver guestResumeSessionReceiver,
+ GuestResetOrExitSessionReceiver guestResetOrExitSessionReceiver) {
mContext = context;
mActivityManager = activityManager;
mUserTracker = userTracker;
@@ -200,14 +210,13 @@ public class UserSwitcherController implements Dumpable {
mInteractionJankMonitor = interactionJankMonitor;
mLatencyTracker = latencyTracker;
mGlobalSettings = globalSettings;
- mGuestResumeSessionReceiver = new GuestResumeSessionReceiver(
- this, mUserTracker, mUiEventLogger, secureSettings);
+ mGuestResumeSessionReceiver = guestResumeSessionReceiver;
+ mGuestResetOrExitSessionReceiver = guestResetOrExitSessionReceiver;
mBgExecutor = bgExecutor;
mLongRunningExecutor = longRunningExecutor;
mUiExecutor = uiExecutor;
- if (!UserManager.isGuestUserEphemeral()) {
- mGuestResumeSessionReceiver.register(mBroadcastDispatcher);
- }
+ mGuestResumeSessionReceiver.register();
+ mGuestResetOrExitSessionReceiver.register();
mGuestUserAutoCreated = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_guestUserAutoCreated);
mGuestIsResetting = new AtomicBoolean();
@@ -278,6 +287,10 @@ public class UserSwitcherController implements Dumpable {
refreshUsers(UserHandle.USER_NULL);
}
+ private static boolean isEnableGuestModeUxChanges(Context context) {
+ return FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_GUEST_MODE_UX_CHANGES);
+ }
+
/**
* Refreshes users from UserManager.
*
@@ -524,20 +537,31 @@ public class UserSwitcherController implements Dumpable {
private void onUserListItemClicked(int id, UserRecord record, DialogShower dialogShower) {
int currUserId = mUserTracker.getUserId();
+ // If switching from guest and guest is ephemeral, then follow the flow
+ // of showExitGuestDialog to remove current guest,
+ // and switch to selected user
+ UserInfo currUserInfo = mUserTracker.getUserInfo();
if (currUserId == id) {
if (record.isGuest) {
- showExitGuestDialog(id, dialogShower);
+ showExitGuestDialog(id, currUserInfo.isEphemeral(), dialogShower);
}
return;
}
- if (UserManager.isGuestUserEphemeral()) {
- // If switching from guest, we want to bring up the guest exit dialog instead of switching
- UserInfo currUserInfo = mUserManager.getUserInfo(currUserId);
- if (currUserInfo != null && currUserInfo.isGuest()) {
- showExitGuestDialog(currUserId, record.resolveId(), dialogShower);
+
+ if (currUserInfo != null && currUserInfo.isGuest()) {
+ if (isEnableGuestModeUxChanges(mContext)) {
+ showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
+ record.resolveId(), dialogShower);
return;
+ } else {
+ if (currUserInfo.isEphemeral()) {
+ showExitGuestDialog(currUserId, currUserInfo.isEphemeral(),
+ record.resolveId(), dialogShower);
+ return;
+ }
}
}
+
if (dialogShower != null) {
// If we haven't morphed into another dialog, it means we have just switched users.
// Then, dismiss the dialog.
@@ -548,9 +572,11 @@ public class UserSwitcherController implements Dumpable {
protected void switchToUserId(int id) {
try {
- mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
- .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
- .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ if (mView != null) {
+ mInteractionJankMonitor.begin(InteractionJankMonitor.Configuration.Builder
+ .withView(InteractionJankMonitor.CUJ_USER_SWITCH, mView)
+ .setTimeout(MULTI_USER_JOURNEY_TIMEOUT));
+ }
mLatencyTracker.onActionStart(LatencyTracker.ACTION_USER_SWITCH);
pauseRefreshUsers();
mActivityManager.switchUser(id);
@@ -559,7 +585,7 @@ public class UserSwitcherController implements Dumpable {
}
}
- private void showExitGuestDialog(int id, DialogShower dialogShower) {
+ private void showExitGuestDialog(int id, boolean isGuestEphemeral, DialogShower dialogShower) {
int newId = UserHandle.USER_SYSTEM;
if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
@@ -567,16 +593,19 @@ public class UserSwitcherController implements Dumpable {
newId = info.id;
}
}
- showExitGuestDialog(id, newId, dialogShower);
+ showExitGuestDialog(id, isGuestEphemeral, newId, dialogShower);
}
- private void showExitGuestDialog(int id, int targetId, DialogShower dialogShower) {
+ private void showExitGuestDialog(int id, boolean isGuestEphemeral,
+ int targetId, DialogShower dialogShower) {
if (mExitGuestDialog != null && mExitGuestDialog.isShowing()) {
mExitGuestDialog.cancel();
}
- mExitGuestDialog = new ExitGuestDialog(mContext, id, targetId);
+ mExitGuestDialog = new ExitGuestDialog(mContext, id, isGuestEphemeral, targetId);
if (dialogShower != null) {
- dialogShower.showDialog(mExitGuestDialog);
+ dialogShower.showDialog(mExitGuestDialog, new DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_EXIT_GUEST_MODE_TAG));
} else {
mExitGuestDialog.show();
}
@@ -588,7 +617,11 @@ public class UserSwitcherController implements Dumpable {
}
mAddUserDialog = new AddUserDialog(mContext);
if (dialogShower != null) {
- dialogShower.showDialog(mAddUserDialog);
+ dialogShower.showDialog(mAddUserDialog,
+ new DialogCuj(
+ InteractionJankMonitor.CUJ_USER_DIALOG_OPEN,
+ INTERACTION_JANK_ADD_NEW_USER_TAG
+ ));
} else {
mAddUserDialog.show();
}
@@ -808,6 +841,52 @@ public class UserSwitcherController implements Dumpable {
}
}
+ /**
+ * Exits guest user and switches to previous non-guest user. The guest must be the current
+ * user.
+ *
+ * @param guestUserId user id of the guest user to exit
+ * @param targetUserId user id of the guest user to exit, set to UserHandle.USER_NULL when
+ * target user id is not known
+ * @param forceRemoveGuestOnExit true: remove guest before switching user,
+ * false: remove guest only if its ephemeral, else keep guest
+ */
+ public void exitGuestUser(@UserIdInt int guestUserId, @UserIdInt int targetUserId,
+ boolean forceRemoveGuestOnExit) {
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (currentUser.id != guestUserId) {
+ Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
+ + " is not current user (" + currentUser.id + ")");
+ return;
+ }
+ if (!currentUser.isGuest()) {
+ Log.w(TAG, "User requesting to start a new session (" + guestUserId + ")"
+ + " is not a guest");
+ return;
+ }
+
+ int newUserId = UserHandle.USER_SYSTEM;
+ if (targetUserId == UserHandle.USER_NULL) {
+ // when target user is not specified switch to last non guest user
+ if (mResumeUserOnGuestLogout && mLastNonGuestUser != UserHandle.USER_SYSTEM) {
+ UserInfo info = mUserManager.getUserInfo(mLastNonGuestUser);
+ if (info != null && info.isEnabled() && info.supportsSwitchToByUser()) {
+ newUserId = info.id;
+ }
+ }
+ } else {
+ newUserId = targetUserId;
+ }
+
+ if (currentUser.isEphemeral() || forceRemoveGuestOnExit) {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+ removeGuestUser(currentUser.id, newUserId);
+ } else {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_SWITCH);
+ switchToUserId(newUserId);
+ }
+ }
+
private void scheduleGuestCreation() {
if (!mGuestCreationScheduled.compareAndSet(false, true)) {
return;
@@ -859,15 +938,17 @@ public class UserSwitcherController implements Dumpable {
guestCreationProgressDialog.show();
// userManager.createGuest will block the thread so post is needed for the dialog to show
- ThreadUtils.postOnMainThread(() -> {
+ mBgExecutor.execute(() -> {
final int guestId = createGuest();
- guestCreationProgressDialog.dismiss();
- if (guestId == UserHandle.USER_NULL) {
- Toast.makeText(mContext,
- com.android.settingslib.R.string.add_guest_failed,
- Toast.LENGTH_SHORT).show();
- }
- callback.accept(guestId);
+ mUiExecutor.execute(() -> {
+ guestCreationProgressDialog.dismiss();
+ if (guestId == UserHandle.USER_NULL) {
+ Toast.makeText(mContext,
+ com.android.settingslib.R.string.add_guest_failed,
+ Toast.LENGTH_SHORT).show();
+ }
+ callback.accept(guestId);
+ });
});
}
@@ -975,9 +1056,14 @@ public class UserSwitcherController implements Dumpable {
public String getName(Context context, UserRecord item) {
if (item.isGuest) {
if (item.isCurrent) {
- return context.getString(mController.mGuestUserAutoCreated
+ if (isEnableGuestModeUxChanges(context)) {
+ return context.getString(
+ com.android.settingslib.R.string.guest_exit_quick_settings_button);
+ } else {
+ return context.getString(mController.mGuestUserAutoCreated
? com.android.settingslib.R.string.guest_reset_guest
: com.android.settingslib.R.string.guest_exit_guest);
+ }
} else {
if (item.info != null) {
return context.getString(com.android.internal.R.string.guest_name);
@@ -994,8 +1080,13 @@ public class UserSwitcherController implements Dumpable {
? com.android.settingslib.R.string.guest_resetting
: com.android.internal.R.string.guest_name);
} else {
- return context.getString(
- com.android.settingslib.R.string.guest_new_guest);
+ if (isEnableGuestModeUxChanges(context)) {
+ // we always show "guest" as string, instead of "add guest"
+ return context.getString(com.android.internal.R.string.guest_name);
+ } else {
+ return context.getString(
+ com.android.settingslib.R.string.guest_new_guest);
+ }
}
}
}
@@ -1017,7 +1108,11 @@ public class UserSwitcherController implements Dumpable {
protected static Drawable getIconDrawable(Context context, UserRecord item) {
int iconRes;
if (item.isAddUser) {
- iconRes = R.drawable.ic_account_circle_filled;
+ if (isEnableGuestModeUxChanges(context)) {
+ iconRes = R.drawable.ic_add;
+ } else {
+ iconRes = R.drawable.ic_account_circle_filled;
+ }
} else if (item.isGuest) {
iconRes = R.drawable.ic_account_circle;
} else if (item.isAddSupervisedUser) {
@@ -1189,24 +1284,58 @@ public class UserSwitcherController implements Dumpable {
private final int mGuestId;
private final int mTargetId;
+ private final boolean mIsGuestEphemeral;
- public ExitGuestDialog(Context context, int guestId, int targetId) {
+ ExitGuestDialog(Context context, int guestId, boolean isGuestEphemeral,
+ int targetId) {
super(context);
- setTitle(mGuestUserAutoCreated
- ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
- : com.android.settingslib.R.string.guest_remove_guest_dialog_title);
- setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
- setButton(DialogInterface.BUTTON_NEUTRAL,
- context.getString(android.R.string.cancel), this);
- setButton(DialogInterface.BUTTON_POSITIVE,
- context.getString(mGuestUserAutoCreated
+ if (isEnableGuestModeUxChanges(context)) {
+ if (isGuestEphemeral) {
+ setTitle(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_title));
+ setMessage(context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_dialog_button), this);
+ } else {
+ setTitle(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_title_non_ephemeral));
+ setMessage(context.getString(
+ com.android.settingslib
+ .R.string.guest_exit_dialog_message_non_ephemeral));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_NEGATIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_clear_data_button),
+ this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(
+ com.android.settingslib.R.string.guest_exit_save_data_button),
+ this);
+ }
+ } else {
+ setTitle(mGuestUserAutoCreated
+ ? com.android.settingslib.R.string.guest_reset_guest_dialog_title
+ : com.android.settingslib.R.string.guest_remove_guest_dialog_title);
+ setMessage(context.getString(R.string.guest_exit_guest_dialog_message));
+ setButton(DialogInterface.BUTTON_NEUTRAL,
+ context.getString(android.R.string.cancel), this);
+ setButton(DialogInterface.BUTTON_POSITIVE,
+ context.getString(mGuestUserAutoCreated
? com.android.settingslib.R.string.guest_reset_guest_confirm_button
: com.android.settingslib.R.string.guest_remove_guest_confirm_button),
- this);
+ this);
+ }
SystemUIDialog.setWindowOnTop(this, mKeyguardStateController.isShowing());
setCanceledOnTouchOutside(false);
mGuestId = guestId;
mTargetId = targetId;
+ mIsGuestEphemeral = isGuestEphemeral;
}
@Override
@@ -1216,12 +1345,40 @@ public class UserSwitcherController implements Dumpable {
if (mFalsingManager.isFalseTap(penalty)) {
return;
}
- if (which == BUTTON_NEUTRAL) {
- cancel();
+ if (isEnableGuestModeUxChanges(getContext())) {
+ if (mIsGuestEphemeral) {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mDialogLaunchAnimator.dismissStack(this);
+ // Ephemeral guest: exit guest, guest is removed by the system
+ // on exit, since its marked ephemeral
+ exitGuestUser(mGuestId, mTargetId, false);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ } else {
+ if (which == DialogInterface.BUTTON_POSITIVE) {
+ mDialogLaunchAnimator.dismissStack(this);
+ // Non-ephemeral guest: exit guest, guest is not removed by the system
+ // on exit, since its marked non-ephemeral
+ exitGuestUser(mGuestId, mTargetId, false);
+ } else if (which == DialogInterface.BUTTON_NEGATIVE) {
+ mDialogLaunchAnimator.dismissStack(this);
+ // Non-ephemeral guest: remove guest and then exit
+ exitGuestUser(mGuestId, mTargetId, true);
+ } else if (which == DialogInterface.BUTTON_NEUTRAL) {
+ // Cancel clicked, do nothing
+ cancel();
+ }
+ }
} else {
- mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
- mDialogLaunchAnimator.dismissStack(this);
- removeGuestUser(mGuestId, mTargetId);
+ if (which == BUTTON_NEUTRAL) {
+ cancel();
+ } else {
+ mUiEventLogger.log(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE);
+ mDialogLaunchAnimator.dismissStack(this);
+ removeGuestUser(mGuestId, mTargetId);
+ }
}
}
}
@@ -1230,10 +1387,17 @@ public class UserSwitcherController implements Dumpable {
final class AddUserDialog extends SystemUIDialog implements
DialogInterface.OnClickListener {
- public AddUserDialog(Context context) {
+ AddUserDialog(Context context) {
super(context);
+
setTitle(com.android.settingslib.R.string.user_add_user_title);
- setMessage(com.android.settingslib.R.string.user_add_user_message_short);
+ String message = context.getString(
+ com.android.settingslib.R.string.user_add_user_message_short);
+ UserInfo currentUser = mUserTracker.getUserInfo();
+ if (currentUser != null && currentUser.isGuest() && currentUser.isEphemeral()) {
+ message += context.getString(R.string.user_add_user_message_guest_remove);
+ }
+ setMessage(message);
setButton(DialogInterface.BUTTON_NEUTRAL,
context.getString(android.R.string.cancel), this);
setButton(DialogInterface.BUTTON_POSITIVE,
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIInitializer.java
index c99ad23ab23d..fabbb2c1f44d 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIFactory.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIInitializer.java
@@ -18,18 +18,20 @@ package com.android.systemui.tv;
import android.content.Context;
-import com.android.systemui.SystemUIFactory;
+import com.android.systemui.SystemUIInitializer;
import com.android.systemui.dagger.GlobalRootComponent;
/**
- * TV variant {@link SystemUIFactory}, that substitutes default {@link GlobalRootComponent} for
+ * TV variant {@link SystemUIInitializer}, that substitutes default {@link GlobalRootComponent} for
* {@link TvGlobalRootComponent}
*/
-public class TvSystemUIFactory extends SystemUIFactory {
+public class TvSystemUIInitializer extends SystemUIInitializer {
+ public TvSystemUIInitializer(Context context) {
+ super(context);
+ }
+
@Override
- protected GlobalRootComponent buildGlobalRootComponent(Context context) {
- return DaggerTvGlobalRootComponent.builder()
- .context(context)
- .build();
+ protected GlobalRootComponent.Builder getGlobalRootComponentBuilder() {
+ return DaggerTvGlobalRootComponent.builder();
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 9a19d8d11190..f1e89ac81b1b 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -35,6 +35,7 @@ import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dock.DockManager;
import com.android.systemui.dock.DockManagerImpl;
import com.android.systemui.doze.DozeHost;
+import com.android.systemui.navigationbar.gestural.GestureModule;
import com.android.systemui.plugins.qs.QSFactory;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.power.EnhancedEstimates;
@@ -45,6 +46,7 @@ import com.android.systemui.qs.dagger.QSModule;
import com.android.systemui.qs.tileimpl.QSFactoryImpl;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsImplementation;
+import com.android.systemui.screenshot.ReferenceScreenshotModule;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationListener;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -87,8 +89,10 @@ import dagger.multibindings.IntoSet;
* overridden by the System UI implementation.
*/
@Module(includes = {
+ GestureModule.class,
PowerModule.class,
QSModule.class,
+ ReferenceScreenshotModule.class,
VolumeModule.class,
},
subcomponents = {
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
index 7350b37e4b66..13ac39c77c93 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/SysUIUnfoldModule.kt
@@ -18,7 +18,7 @@ package com.android.systemui.unfold
import com.android.keyguard.KeyguardUnfoldTransition
import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.phone.NotificationPanelUnfoldAnimationController
+import com.android.systemui.shade.NotificationPanelUnfoldAnimationController
import com.android.systemui.statusbar.phone.StatusBarMoveFromCenterAnimationController
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index d2d2361d613d..eea6ac0e72e9 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -20,6 +20,7 @@ import android.content.Context
import android.view.IWindowManager
import com.android.systemui.keyguard.LifecycleScreenStatusProvider
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.system.SystemUnfoldSharedModule
import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.util.NaturalRotationUnfoldProgressProvider
@@ -34,7 +35,7 @@ import java.util.Optional
import javax.inject.Named
import javax.inject.Singleton
-@Module(includes = [UnfoldSharedModule::class])
+@Module(includes = [UnfoldSharedModule::class, SystemUnfoldSharedModule::class])
class UnfoldTransitionModule {
@Provides @UnfoldTransitionATracePrefix fun tracingTagPrefix() = "systemui"
@@ -62,11 +63,6 @@ class UnfoldTransitionModule {
@Provides
@Singleton
- fun provideUnfoldTransitionConfig(context: Context): UnfoldTransitionConfig =
- createConfig(context)
-
- @Provides
- @Singleton
fun provideNaturalRotationProgressProvider(
context: Context,
windowManager: IWindowManager,
diff --git a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
index cc6bf6a70d4e..f01712653e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/user/CreateUserActivity.java
@@ -31,6 +31,7 @@ import androidx.annotation.Nullable;
import com.android.settingslib.users.EditUserInfoController;
import com.android.systemui.R;
+import com.android.systemui.plugins.ActivityStarter;
import javax.inject.Inject;
@@ -55,15 +56,18 @@ public class CreateUserActivity extends Activity {
private final UserCreator mUserCreator;
private final EditUserInfoController mEditUserInfoController;
private final IActivityManager mActivityManager;
+ private final ActivityStarter mActivityStarter;
private Dialog mSetupUserDialog;
@Inject
public CreateUserActivity(UserCreator userCreator,
- EditUserInfoController editUserInfoController, IActivityManager activityManager) {
+ EditUserInfoController editUserInfoController, IActivityManager activityManager,
+ ActivityStarter activityStarter) {
mUserCreator = userCreator;
mEditUserInfoController = editUserInfoController;
mActivityManager = activityManager;
+ mActivityStarter = activityStarter;
}
@Override
@@ -104,10 +108,7 @@ public class CreateUserActivity extends Activity {
return mEditUserInfoController.createDialog(
this,
- (intent, requestCode) -> {
- mEditUserInfoController.startingActivityForResult();
- startActivityForResult(intent, requestCode);
- },
+ this::startActivity,
null,
defaultUserName,
getString(com.android.settingslib.R.string.user_add_user),
@@ -160,4 +161,17 @@ public class CreateUserActivity extends Activity {
Log.e(TAG, "Couldn't switch user.", e);
}
}
+
+ /**
+ * Lambda to start activity from an intent. Ensures that device is unlocked first.
+ * @param intent
+ * @param requestCode
+ */
+ private void startActivity(Intent intent, int requestCode) {
+ mActivityStarter.dismissKeyguardThenExecute(() -> {
+ mEditUserInfoController.startingActivityForResult();
+ startActivityForResult(intent, requestCode);
+ return true;
+ }, /* cancel= */ null, /* afterKeyguardGone= */ true);
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java b/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
deleted file mode 100644
index 0686071c1718..000000000000
--- a/packages/SystemUI/src/com/android/systemui/user/UserCreator.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.user;
-
-import android.app.Dialog;
-import android.content.Context;
-import android.content.pm.UserInfo;
-import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
-import android.os.UserManager;
-
-import com.android.internal.util.UserIcons;
-import com.android.settingslib.users.UserCreatingDialog;
-import com.android.settingslib.utils.ThreadUtils;
-
-import java.util.function.Consumer;
-
-import javax.inject.Inject;
-
-/**
- * A class to do the user creation process. It shows a progress dialog, and manages the user
- * creation
- */
-public class UserCreator {
-
- private final Context mContext;
- private final UserManager mUserManager;
-
- @Inject
- public UserCreator(Context context, UserManager userManager) {
- mContext = context;
- mUserManager = userManager;
- }
-
- /**
- * Shows a progress dialog then starts the user creation process on the main thread.
- *
- * @param successCallback is called when the user creation is successful.
- * @param errorCallback is called when userManager.createUser returns null.
- * (Exceptions are not handled by this class)
- */
- public void createUser(String userName, Drawable userIcon, Consumer<UserInfo> successCallback,
- Runnable errorCallback) {
-
- Dialog userCreationProgressDialog = new UserCreatingDialog(mContext);
- userCreationProgressDialog.show();
-
- // userManager.createUser will block the thread so post is needed for the dialog to show
- ThreadUtils.postOnMainThread(() -> {
- UserInfo user =
- mUserManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0);
- if (user == null) {
- // Couldn't create user for some reason
- userCreationProgressDialog.dismiss();
- errorCallback.run();
- return;
- }
-
- Drawable newUserIcon = userIcon;
- Resources res = mContext.getResources();
- if (newUserIcon == null) {
- newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false);
- }
- mUserManager.setUserIcon(
- user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon));
-
- userCreationProgressDialog.dismiss();
- successCallback.accept(user);
- });
- }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
new file mode 100644
index 000000000000..dcbbe7441922
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserCreator.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.user
+
+import android.app.Dialog
+import android.content.Context
+import android.content.pm.UserInfo
+import android.graphics.drawable.Drawable
+import android.os.UserManager
+import com.android.internal.util.UserIcons
+import com.android.settingslib.users.UserCreatingDialog
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import java.util.concurrent.Executor
+import java.util.function.Consumer
+import javax.inject.Inject
+
+/**
+ * A class to do the user creation process. It shows a progress dialog, and manages the user
+ * creation
+ */
+class UserCreator @Inject constructor(
+ private val context: Context,
+ private val userManager: UserManager,
+ @Main private val mainExecutor: Executor,
+ @Background private val bgExecutor: Executor
+) {
+ /**
+ * Shows a progress dialog then starts the user creation process on the main thread.
+ *
+ * @param successCallback is called when the user creation is successful.
+ * @param errorCallback is called when userManager.createUser returns null.
+ * (Exceptions are not handled by this class)
+ */
+ fun createUser(
+ userName: String?,
+ userIcon: Drawable?,
+ successCallback: Consumer<UserInfo?>,
+ errorCallback: Runnable
+ ) {
+ val userCreationProgressDialog: Dialog = UserCreatingDialog(context)
+ userCreationProgressDialog.show()
+
+ // userManager.createUser will block the thread so post is needed for the dialog to show
+ bgExecutor.execute {
+ val user = userManager.createUser(userName, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ mainExecutor.execute main@{
+ if (user == null) {
+ // Couldn't create user for some reason
+ userCreationProgressDialog.dismiss()
+ errorCallback.run()
+ return@main
+ }
+ bgExecutor.execute {
+ var newUserIcon = userIcon
+ val res = context.resources
+ if (newUserIcon == null) {
+ newUserIcon = UserIcons.getDefaultUserIcon(res, user.id, false)
+ }
+ userManager.setUserIcon(
+ user.id, UserIcons.convertToBitmapAtUserIconSize(res, newUserIcon))
+ }
+ userCreationProgressDialog.dismiss()
+ successCallback.accept(user)
+ }
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
index ad734914170b..74d51112deeb 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherActivity.kt
@@ -28,6 +28,7 @@ import android.os.Bundle
import android.os.UserManager
import android.provider.Settings
import android.view.LayoutInflater
+import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
@@ -38,8 +39,10 @@ import androidx.constraintlayout.helper.widget.Flow
import com.android.internal.annotations.VisibleForTesting
import com.android.internal.util.UserIcons
import com.android.settingslib.Utils
+import com.android.systemui.Gefingerpoken
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.FalsingManager.LOW_PENALTY
import com.android.systemui.settings.UserTracker
@@ -61,12 +64,13 @@ class UserSwitcherActivity @Inject constructor(
private val userSwitcherController: UserSwitcherController,
private val broadcastDispatcher: BroadcastDispatcher,
private val layoutInflater: LayoutInflater,
+ private val falsingCollector: FalsingCollector,
private val falsingManager: FalsingManager,
private val userManager: UserManager,
private val userTracker: UserTracker
) : LifecycleActivity() {
- private lateinit var parent: ViewGroup
+ private lateinit var parent: UserSwitcherRootView
private lateinit var broadcastReceiver: BroadcastReceiver
private var popupMenu: UserSwitcherPopupMenu? = null
private lateinit var addButton: View
@@ -202,7 +206,14 @@ class UserSwitcherActivity @Inject constructor(
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION)
- parent = requireViewById<ViewGroup>(R.id.user_switcher_root)
+ parent = requireViewById<UserSwitcherRootView>(R.id.user_switcher_root)
+
+ parent.touchHandler = object : Gefingerpoken {
+ override fun onTouchEvent(ev: MotionEvent?): Boolean {
+ falsingCollector.onTouchEvent(ev)
+ return false
+ }
+ }
requireViewById<View>(R.id.cancel).apply {
setOnClickListener {
@@ -241,7 +252,7 @@ class UserSwitcherActivity @Inject constructor(
)
popupMenuAdapter.addAll(items)
- popupMenu = UserSwitcherPopupMenu(this, falsingManager).apply {
+ popupMenu = UserSwitcherPopupMenu(this).apply {
setAnchorView(addButton)
setAdapter(popupMenuAdapter)
setOnItemClickListener {
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
index 754a9342bfb0..ee785b62bd50 100644
--- a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherPopupMenu.kt
@@ -23,16 +23,13 @@ import android.view.View.MeasureSpec
import android.widget.ListAdapter
import android.widget.ListPopupWindow
import android.widget.ListView
-
import com.android.systemui.R
-import com.android.systemui.plugins.FalsingManager
/**
* Popup menu for displaying items on the fullscreen user switcher.
*/
class UserSwitcherPopupMenu(
- private val context: Context,
- private val falsingManager: FalsingManager
+ private val context: Context
) : ListPopupWindow(context) {
private val res = context.resources
diff --git a/packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt
new file mode 100644
index 000000000000..66a301744025
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/user/UserSwitcherRootView.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.user
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.MotionEvent
+import androidx.constraintlayout.widget.ConstraintLayout
+import com.android.systemui.Gefingerpoken
+
+/** A simple subclass that allows for observing touch events as they happen. */
+class UserSwitcherRootView(
+ context: Context,
+ attrs: AttributeSet?
+) : ConstraintLayout(context, attrs) {
+
+ /** Assign this field to observer touch events. */
+ var touchHandler: Gefingerpoken? = null
+
+ override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
+ touchHandler?.onTouchEvent(ev)
+ return super.dispatchTouchEvent(ev)
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt b/packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt
new file mode 100644
index 000000000000..80d0e4b90359
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/AsyncActivityLauncher.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.IActivityTaskManager
+import android.app.WaitResult
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.dagger.qualifiers.UiBackground
+import java.util.concurrent.Executor
+import javax.inject.Inject
+
+/**
+ * Helper class that allows to launch an activity and asynchronously wait
+ * for it to be launched. This class uses application context, so the intent
+ * will be launched with FLAG_ACTIVITY_NEW_TASK.
+ */
+class AsyncActivityLauncher @Inject constructor(
+ private val context: Context,
+ private val activityTaskManager: IActivityTaskManager,
+ @UiBackground private val backgroundExecutor: Executor,
+ @Main private val mainExecutor: Executor
+) {
+
+ private var pendingCallback: ((WaitResult) -> Unit)? = null
+
+ /**
+ * Starts activity and notifies about the result using the provided [callback].
+ * If there is already pending activity launch the call will be ignored.
+ *
+ * @return true if launch has started, false otherwise
+ */
+ fun startActivityAsUser(intent: Intent, userHandle: UserHandle,
+ activityOptions: Bundle? = null,
+ callback: (WaitResult) -> Unit): Boolean {
+ if (pendingCallback != null) return false
+
+ pendingCallback = callback
+
+ intent.flags = intent.flags or Intent.FLAG_ACTIVITY_NEW_TASK
+
+ backgroundExecutor.execute {
+ val waitResult = activityTaskManager.startActivityAndWait(
+ /* caller = */ null,
+ /* callingPackage = */ context.packageName,
+ /* callingFeatureId = */ context.attributionTag,
+ /* intent = */ intent,
+ /* resolvedType = */ null,
+ /* resultTo = */ null,
+ /* resultWho = */ null,
+ /* requestCode = */ 0,
+ /* flags = */ 0,
+ /* profilerInfo = */ null,
+ /* options = */ activityOptions,
+ /* userId = */ userHandle.identifier
+ )
+ mainExecutor.execute {
+ pendingCallback?.invoke(waitResult)
+ }
+ }
+
+ return true
+ }
+
+ /**
+ * Cancels pending activity launches. It guarantees that the callback won't be fired
+ * but the activity will be launched anyway.
+ */
+ fun destroy() {
+ pendingCallback = null
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
index 57b3f53c48fe..ec7aabb58b49 100644
--- a/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/ConvenienceExtensions.kt
@@ -16,7 +16,9 @@
package com.android.systemui.util
+import android.graphics.Rect
import android.util.IndentingPrintWriter
+import android.view.View
import android.view.ViewGroup
import java.io.PrintWriter
@@ -45,4 +47,12 @@ inline fun PrintWriter.indentIfPossible(block: PrintWriter.() -> Unit) {
if (this is IndentingPrintWriter) increaseIndent()
block()
if (this is IndentingPrintWriter) decreaseIndent()
-} \ No newline at end of file
+}
+
+/** Convenience extension property for [View.getBoundsOnScreen]. */
+val View.boundsOnScreen: Rect
+ get() {
+ val bounds = Rect()
+ getBoundsOnScreen(bounds)
+ return bounds
+ }
diff --git a/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt b/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
new file mode 100644
index 000000000000..8b90547f7bdb
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/DelayableMarqueeTextView.kt
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.Context
+import android.util.AttributeSet
+import com.android.systemui.R
+
+class DelayableMarqueeTextView @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet? = null,
+ defStyleAttr: Int = 0,
+ defStyleRes: Int = 0
+) : SafeMarqueeTextView(context, attrs, defStyleAttr, defStyleRes) {
+
+ var marqueeDelay: Long = DEFAULT_MARQUEE_DELAY
+ private var wantsMarquee = false
+ private var marqueeBlocked = true
+
+ private val enableMarquee = Runnable {
+ if (wantsMarquee) {
+ marqueeBlocked = false
+ startMarquee()
+ }
+ }
+
+ init {
+ val typedArray = context.theme.obtainStyledAttributes(
+ attrs,
+ R.styleable.DelayableMarqueeTextView,
+ defStyleAttr,
+ defStyleRes
+ )
+ marqueeDelay = typedArray.getInteger(
+ R.styleable.DelayableMarqueeTextView_marqueeDelay,
+ DEFAULT_MARQUEE_DELAY.toInt()
+ ).toLong()
+ typedArray.recycle()
+ }
+
+ override fun startMarquee() {
+ if (!isSelected) {
+ return
+ }
+ wantsMarquee = true
+ if (marqueeBlocked) {
+ if (handler?.hasCallbacks(enableMarquee) == false) {
+ postDelayed(enableMarquee, marqueeDelay)
+ }
+ return
+ }
+ super.startMarquee()
+ }
+
+ override fun stopMarquee() {
+ handler?.removeCallbacks(enableMarquee)
+ wantsMarquee = false
+ marqueeBlocked = true
+ super.stopMarquee()
+ }
+
+ companion object {
+ const val DEFAULT_MARQUEE_DELAY = 2000L
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
index f9524769d304..018ef968ff2d 100644
--- a/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/DumpUtils.kt
@@ -55,6 +55,10 @@ fun IndentingPrintWriter.withIncreasedIndent(runnable: Runnable) {
}
}
+/** Print a line which is '$label=$value' */
+fun IndentingPrintWriter.println(label: String, value: Any) =
+ append(label).append('=').println(value)
+
/** Return a readable string for the visibility */
fun visibilityString(@View.Visibility visibility: Int): String = when (visibility) {
View.GONE -> "gone"
diff --git a/packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt b/packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt
new file mode 100644
index 000000000000..f53b6824d45d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/InitializationChecker.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.app.ActivityThread
+import android.os.Process
+import com.android.systemui.dagger.qualifiers.InstrumentationTest
+import javax.inject.Inject
+
+/**
+ * Used to check whether SystemUI should be fully initialized.
+ */
+class InitializationChecker @Inject constructor(
+ @InstrumentationTest private val instrumentationTest: Boolean
+) {
+
+ /**
+ * Only initialize components for the main system ui process running as the primary user
+ */
+ fun initializeComponents(): Boolean =
+ !instrumentationTest &&
+ Process.myUserHandle().isSystem &&
+ ActivityThread.currentProcessName() == ActivityThread.currentPackageName()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
new file mode 100644
index 000000000000..3095d80d1630
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/NoRemeasureMotionLayout.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.content.Context
+import android.util.AttributeSet
+import android.view.Choreographer
+import androidx.constraintlayout.motion.widget.MotionLayout
+
+/**
+ * [MotionLayout] that avoids remeasuring with the same inputs in the same frame.
+ *
+ * This is important when this view is the child of a view that performs more than one measure pass
+ * (e.g. [LinearLayout] or [ConstraintLayout]). In those cases, if this view is measured with the
+ * same inputs in the same frame, we use the last result.
+ */
+class NoRemeasureMotionLayout @JvmOverloads constructor(
+ context: Context,
+ attrs: AttributeSet?,
+ defStyle: Int = 0
+) : MotionLayout(context, attrs, defStyle) {
+
+ private var lastWidthSpec: Int? = null
+ private var lastHeightSpec: Int? = null
+ private var lastFrame: Long? = null
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ if (
+ lastWidthSpec == widthMeasureSpec &&
+ lastHeightSpec == heightMeasureSpec &&
+ Choreographer.getMainThreadInstance()?.frameTime == lastFrame
+ ) {
+ setMeasuredDimension(measuredWidth, measuredHeight)
+ return
+ }
+ traceSection("NoRemeasureMotionLayout - measure") {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ lastWidthSpec = widthMeasureSpec
+ lastHeightSpec = heightMeasureSpec
+ lastFrame = Choreographer.getMainThreadInstance()?.frameTime
+ }
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
index 76dfcb182e97..53da213eb38e 100644
--- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
+++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java
@@ -35,11 +35,15 @@ import javax.inject.Inject;
public class NotificationChannels extends CoreStartable {
public static String ALERTS = "ALR";
public static String SCREENSHOTS_HEADSUP = "SCN_HEADSUP";
- public static String GENERAL = "GEN";
+ // Deprecated. Please use or create a more specific channel that users will better understand
+ @Deprecated
+ static String GENERAL = "GEN";
public static String STORAGE = "DSK";
public static String BATTERY = "BAT";
public static String TVPIP = TvPipNotificationController.NOTIFICATION_CHANNEL; // "TVPIP"
public static String HINTS = "HNT";
+ public static String INSTANT = "INS";
+ public static String SETUP = "STP";
@Inject
public NotificationChannels(Context context) {
@@ -64,11 +68,17 @@ public class NotificationChannels extends CoreStartable {
context.getString(R.string.notification_channel_alerts),
NotificationManager.IMPORTANCE_HIGH);
- final NotificationChannel general = new NotificationChannel(
- GENERAL,
- context.getString(R.string.notification_channel_general),
+ final NotificationChannel instant = new NotificationChannel(
+ INSTANT,
+ context.getString(R.string.notification_channel_instant),
NotificationManager.IMPORTANCE_MIN);
+ final NotificationChannel setup = new NotificationChannel(
+ SETUP,
+ context.getString(R.string.notification_channel_setup),
+ NotificationManager.IMPORTANCE_DEFAULT);
+ setup.setSound(null, null);
+
final NotificationChannel storage = new NotificationChannel(
STORAGE,
context.getString(R.string.notification_channel_storage),
@@ -84,7 +94,8 @@ public class NotificationChannels extends CoreStartable {
nm.createNotificationChannels(Arrays.asList(
alerts,
- general,
+ instant,
+ setup,
storage,
createScreenshotChannel(
context.getString(R.string.notification_channel_screenshot)),
@@ -123,6 +134,11 @@ public class NotificationChannels extends CoreStartable {
@Override
public void start() {
createAll(mContext);
+ cleanUp();
+ }
+
+ private void cleanUp() {
+ mContext.getSystemService(NotificationManager.class).deleteNotificationChannel(GENERAL);
}
private static boolean isTv(Context context) {
diff --git a/packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt b/packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt
new file mode 100644
index 000000000000..83b471dff4b1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/PluralMessageFormater.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util
+
+import android.annotation.StringRes
+import android.content.res.Resources
+import android.util.PluralsMessageFormatter
+
+/**
+ * Utility method that provides the localized plural string for the given [messageId]
+ * using the [count] parameter.
+ */
+fun icuMessageFormat(res: Resources, @StringRes messageId: Int, count: Int): String {
+ return PluralsMessageFormatter.format(res, mapOf<String, Any>("count" to count), messageId)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
index d3c6e9aa0da9..dac8a0bff28a 100644
--- a/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
+++ b/packages/SystemUI/src/com/android/systemui/util/condition/Monitor.java
@@ -16,16 +16,17 @@
package com.android.systemui.util.condition;
+import android.util.ArraySet;
import android.util.Log;
-import com.android.systemui.statusbar.policy.CallbackController;
+import com.android.systemui.dagger.qualifiers.Main;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.HashSet;
-import java.util.Iterator;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
@@ -36,162 +37,208 @@ import javax.inject.Inject;
* {@link Monitor} takes in a set of conditions, monitors whether all of them have
* been fulfilled, and informs any registered listeners.
*/
-public class Monitor implements CallbackController<Monitor.Callback> {
+public class Monitor {
private final String mTag = getClass().getSimpleName();
+ private final Executor mExecutor;
- private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+ private final HashMap<Condition, ArraySet<Subscription.Token>> mConditions = new HashMap<>();
+ private final HashMap<Subscription.Token, SubscriptionState> mSubscriptions = new HashMap<>();
- // Set of all conditions that need to be monitored.
- private final Set<Condition> mConditions;
- private final Executor mExecutor;
+ private static class SubscriptionState {
+ private final Subscription mSubscription;
+ private Boolean mAllConditionsMet;
+
+ SubscriptionState(Subscription subscription) {
+ mSubscription = subscription;
+ }
+
+ public Set<Condition> getConditions() {
+ return mSubscription.mConditions;
+ }
+
+ public void update() {
+ // Overriding conditions do not override each other
+ final Collection<Condition> overridingConditions = mSubscription.mConditions.stream()
+ .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
+
+ final Collection<Condition> targetCollection = overridingConditions.isEmpty()
+ ? mSubscription.mConditions : overridingConditions;
+
+ final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
+ .stream()
+ .map(Condition::isConditionMet)
+ .allMatch(conditionMet -> conditionMet);
- // Whether all conditions have been met.
- private boolean mAllConditionsMet = false;
+ if (mAllConditionsMet != null && newAllConditionsMet == mAllConditionsMet) {
+ return;
+ }
- // Whether the monitor has started listening for all the conditions.
- private boolean mHaveConditionsStarted = false;
+ mAllConditionsMet = newAllConditionsMet;
+ mSubscription.mCallback.onConditionsChanged(mAllConditionsMet);
+ }
+ }
// Callback for when each condition has been updated.
private final Condition.Callback mConditionCallback = new Condition.Callback() {
@Override
public void onConditionChanged(Condition condition) {
- mExecutor.execute(() -> updateConditionMetState());
+ mExecutor.execute(() -> updateConditionMetState(condition));
}
};
@Inject
- public Monitor(Executor executor, Set<Condition> conditions, Set<Callback> callbacks) {
- mConditions = new HashSet<>();
+ public Monitor(@Main Executor executor) {
mExecutor = executor;
+ }
- if (conditions != null) {
- mConditions.addAll(conditions);
- }
+ private void updateConditionMetState(Condition condition) {
+ final ArraySet<Subscription.Token> subscriptions = mConditions.get(condition);
- if (callbacks == null) {
+ // It's possible the condition was removed between the time the callback occurred and
+ // update was executed on the main thread.
+ if (subscriptions == null) {
return;
}
- for (Callback callback : callbacks) {
- addCallbackLocked(callback);
- }
+ subscriptions.stream().forEach(token -> mSubscriptions.get(token).update());
}
- private void updateConditionMetState() {
- // Overriding conditions do not override each other
- final Collection<Condition> overridingConditions = mConditions.stream()
- .filter(Condition::isOverridingCondition).collect(Collectors.toSet());
-
- final Collection<Condition> targetCollection = overridingConditions.isEmpty()
- ? mConditions : overridingConditions;
-
- final boolean newAllConditionsMet = targetCollection.isEmpty() ? true : targetCollection
- .stream()
- .map(Condition::isConditionMet)
- .allMatch(conditionMet -> conditionMet);
-
- if (newAllConditionsMet == mAllConditionsMet) {
- return;
- }
+ /**
+ * Registers a callback and the set of conditions to trigger it.
+ * @param subscription A {@link Subscription} detailing the desired conditions and callback.
+ * @return A {@link Subscription.Token} that can be used to remove the subscription.
+ */
+ public Subscription.Token addSubscription(@NotNull Subscription subscription) {
+ final Subscription.Token token = new Subscription.Token();
+ final SubscriptionState state = new SubscriptionState(subscription);
- if (shouldLog()) Log.d(mTag, "all conditions met: " + newAllConditionsMet);
- mAllConditionsMet = newAllConditionsMet;
-
- // Updates all callbacks.
- final Iterator<Callback> iterator = mCallbacks.iterator();
- while (iterator.hasNext()) {
- final Callback callback = iterator.next();
- if (callback == null) {
- iterator.remove();
- } else {
- callback.onConditionsChanged(mAllConditionsMet);
- }
- }
- }
+ mExecutor.execute(() -> {
+ mSubscriptions.put(token, state);
- private void addConditionLocked(@NotNull Condition condition) {
- mConditions.add(condition);
+ // Add and associate conditions.
+ subscription.getConditions().stream().forEach(condition -> {
+ if (!mConditions.containsKey(condition)) {
+ mConditions.put(condition, new ArraySet<>());
+ condition.addCallback(mConditionCallback);
+ }
- if (!mHaveConditionsStarted) {
- return;
- }
+ mConditions.get(condition).add(token);
+ });
- condition.addCallback(mConditionCallback);
- updateConditionMetState();
- }
+ // Update subscription state.
+ state.update();
- /**
- * Adds a condition for the monitor to listen to and consider when determining whether the
- * overall condition state is met.
- */
- public void addCondition(@NotNull Condition condition) {
- mExecutor.execute(() -> addConditionLocked(condition));
+ });
+ return token;
}
/**
- * Removes a condition from further consideration.
+ * Removes a subscription from participating in future callbacks.
+ * @param token The {@link Subscription.Token} returned when the {@link Subscription} was
+ * originally added.
*/
- public void removeCondition(@NotNull Condition condition) {
+ public void removeSubscription(@NotNull Subscription.Token token) {
mExecutor.execute(() -> {
- mConditions.remove(condition);
-
- if (!mHaveConditionsStarted) {
+ if (shouldLog()) Log.d(mTag, "removing callback");
+ if (!mSubscriptions.containsKey(token)) {
+ Log.e(mTag, "subscription not present:" + token);
return;
}
- condition.removeCallback(mConditionCallback);
- updateConditionMetState();
+ mSubscriptions.remove(token).getConditions().forEach(condition -> {
+ if (!mConditions.containsKey(condition)) {
+ Log.e(mTag, "condition not present:" + condition);
+ return;
+
+ }
+ final Set<Subscription.Token> conditionSubscriptions = mConditions.get(condition);
+
+ conditionSubscriptions.remove(token);
+ if (conditionSubscriptions.isEmpty()) {
+ condition.removeCallback(mConditionCallback);
+ mConditions.remove(condition);
+ }
+ });
});
}
- private void addCallbackLocked(@NotNull Callback callback) {
- if (mCallbacks.contains(callback)) {
- return;
- }
+ private boolean shouldLog() {
+ return Log.isLoggable(mTag, Log.DEBUG);
+ }
- if (shouldLog()) Log.d(mTag, "adding callback");
- mCallbacks.add(callback);
+ /**
+ * A {@link Subscription} represents a set of conditions and a callback that is informed when
+ * these conditions change.
+ */
+ public static class Subscription {
+ private final Set<Condition> mConditions;
+ private final Callback mCallback;
+
+ /** */
+ public Subscription(Set<Condition> conditions, Callback callback) {
+ this.mConditions = Collections.unmodifiableSet(conditions);
+ this.mCallback = callback;
+ }
- // Updates the callback immediately.
- callback.onConditionsChanged(mAllConditionsMet);
+ public Set<Condition> getConditions() {
+ return mConditions;
+ }
- if (!mHaveConditionsStarted) {
- if (shouldLog()) Log.d(mTag, "starting all conditions");
- mConditions.forEach(condition -> condition.addCallback(mConditionCallback));
- updateConditionMetState();
- mHaveConditionsStarted = true;
+ public Callback getCallback() {
+ return mCallback;
}
- }
- @Override
- public void addCallback(@NotNull Callback callback) {
- mExecutor.execute(() -> addCallbackLocked(callback));
- }
+ /**
+ * A {@link Token} is an identifier that is associated with a {@link Subscription} which is
+ * registered with a {@link Monitor}.
+ */
+ public static class Token {
+ }
- @Override
- public void removeCallback(@NotNull Callback callback) {
- mExecutor.execute(() -> {
- if (shouldLog()) Log.d(mTag, "removing callback");
- final Iterator<Callback> iterator = mCallbacks.iterator();
- while (iterator.hasNext()) {
- final Callback cb = iterator.next();
- if (cb == null || cb == callback) {
- iterator.remove();
- }
+ /**
+ * {@link Builder} is a helper class for constructing a {@link Subscription}.
+ */
+ public static class Builder {
+ private final Callback mCallback;
+ private final ArraySet<Condition> mConditions;
+
+ /**
+ * Default constructor specifying the {@link Callback} for the {@link Subscription}.
+ * @param callback
+ */
+ public Builder(Callback callback) {
+ mCallback = callback;
+ mConditions = new ArraySet<>();
}
- if (mCallbacks.isEmpty() && mHaveConditionsStarted) {
- if (shouldLog()) Log.d(mTag, "stopping all conditions");
- mConditions.forEach(condition -> condition.removeCallback(mConditionCallback));
+ /**
+ * Adds a {@link Condition} to be associated with the {@link Subscription}.
+ * @param condition
+ * @return The updated {@link Builder}.
+ */
+ public Builder addCondition(Condition condition) {
+ mConditions.add(condition);
+ return this;
+ }
- mAllConditionsMet = false;
- mHaveConditionsStarted = false;
+ /**
+ * Adds a set of {@link Condition} to be associated with the {@link Subscription}.
+ * @param condition
+ * @return The updated {@link Builder}.
+ */
+ public Builder addConditions(Set<Condition> condition) {
+ mConditions.addAll(condition);
+ return this;
}
- });
- }
- private boolean shouldLog() {
- return Log.isLoggable(mTag, Log.DEBUG);
+ /**
+ * Builds the {@link Subscription}.
+ * @return The resulting {@link Subscription}.
+ */
+ public Subscription build() {
+ return new Subscription(mConditions, mCallback);
+ }
+ }
}
/**
@@ -199,6 +246,13 @@ public class Monitor implements CallbackController<Monitor.Callback> {
*/
public interface Callback {
/**
+ * Returns the conditions associated with this callback.
+ */
+ default ArrayList<Condition> getConditions() {
+ return new ArrayList<>();
+ }
+
+ /**
* Triggered when the fulfillment of all conditions have been met.
*
* @param allConditionsMet True if all conditions have been fulfilled. False if none or
diff --git a/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java b/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
deleted file mode 100644
index fc67973fe278..000000000000
--- a/packages/SystemUI/src/com/android/systemui/util/condition/dagger/MonitorComponent.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.util.condition.dagger;
-
-import com.android.systemui.util.condition.Condition;
-import com.android.systemui.util.condition.Monitor;
-
-import java.util.Set;
-
-import dagger.BindsInstance;
-import dagger.Subcomponent;
-
-/**
- * Component for {@link Monitor}.
- */
-@Subcomponent
-public interface MonitorComponent {
- /**
- * Factory for {@link MonitorComponent}.
- */
- @Subcomponent.Factory
- interface Factory {
- MonitorComponent create(@BindsInstance Set<Condition> conditions,
- @BindsInstance Set<Monitor.Callback> callbacks);
- }
-
- /**
- * Provides {@link Monitor}.
- * @return
- */
- Monitor getMonitor();
-}
diff --git a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
index 7892d6eec98d..981bf01164e3 100644
--- a/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/dagger/UtilModule.java
@@ -18,7 +18,6 @@ package com.android.systemui.util.dagger;
import com.android.systemui.util.RingerModeTracker;
import com.android.systemui.util.RingerModeTrackerImpl;
-import com.android.systemui.util.condition.dagger.MonitorComponent;
import com.android.systemui.util.wrapper.UtilWrapperModule;
import dagger.Binds;
@@ -27,9 +26,6 @@ import dagger.Module;
/** Dagger Module for code in the util package. */
@Module(includes = {
UtilWrapperModule.class
- },
- subcomponents = {
- MonitorComponent.class,
})
public interface UtilModule {
/** */
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
new file mode 100644
index 000000000000..05d087e232ba
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/CoroutinesModule.kt
@@ -0,0 +1,41 @@
+package com.android.systemui.util.kotlin
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import dagger.Module
+import dagger.Provides
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+
+/** Providers for various coroutines-related constructs. */
+@Module
+object CoroutinesModule {
+ @Provides
+ @SysUISingleton
+ @Application
+ fun applicationScope(
+ @Main dispatcher: CoroutineDispatcher,
+ ): CoroutineScope = CoroutineScope(dispatcher)
+
+ @Provides
+ @SysUISingleton
+ @Main
+ fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate
+
+ /**
+ * Provide a [CoroutineDispatcher] backed by a thread pool containing at most X threads, where
+ * X is the number of CPU cores available.
+ *
+ * Because there are multiple threads at play, there is no serialization order guarantee. You
+ * should use a [kotlinx.coroutines.channels.Channel] for serialization if necessary.
+ *
+ * @see Dispatchers.Default
+ */
+ @Provides
+ @SysUISingleton
+ @Background
+ fun bgDispatcher(): CoroutineDispatcher = Dispatchers.Default
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
index c57dbe37ca5f..064c224b0568 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/ObservableServiceConnection.java
@@ -155,11 +155,6 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
* Disconnect from the service if bound.
*/
public void unbind() {
- if (!mBoundCalled) {
- return;
- }
- mBoundCalled = false;
- mContext.unbindService(this);
onDisconnected(DISCONNECT_REASON_UNBIND);
}
@@ -210,12 +205,15 @@ public class ObservableServiceConnection<T> implements ServiceConnection {
Log.d(TAG, "onDisconnected:" + reason);
}
+ // If not bound or already unbound, do not proceed setting reason, unbinding, and
+ // notifying
if (!mBoundCalled) {
return;
}
+ mBoundCalled = false;
mLastDisconnectReason = Optional.of(reason);
- unbind();
+ mContext.unbindService(this);
mProxy = null;
applyToCallbacksLocked(callback-> callback.onDisconnected(this,
diff --git a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
index 292c062369c1..6e19bed49626 100644
--- a/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/service/PersistentConnectionManager.java
@@ -72,6 +72,11 @@ public class PersistentConnectionManager<T> {
@Override
public void onDisconnected(ObservableServiceConnection connection, int reason) {
+ // Do not attempt to reconnect if we were manually unbound
+ if (reason == ObservableServiceConnection.DISCONNECT_REASON_UNBIND) {
+ return;
+ }
+
if (mSystemClock.currentTimeMillis() - mStartTime > mMinConnectionDuration) {
initiateConnectionAttempt();
} else {
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 83fc28189db5..708a8ab4fcf6 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -21,14 +21,10 @@ import static android.app.NotificationManager.BUBBLE_PREFERENCE_SELECTED;
import static android.provider.Settings.Secure.NOTIFICATION_BUBBLES;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CLICK;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static android.service.notification.NotificationStats.DISMISSAL_BUBBLE;
import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
-import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -38,7 +34,6 @@ import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import android.content.pm.UserInfo;
-import android.content.res.Configuration;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.UserHandle;
@@ -55,7 +50,6 @@ import androidx.annotation.Nullable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
import com.android.systemui.Dumpable;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dump.DumpManager;
@@ -63,9 +57,7 @@ import com.android.systemui.model.SysUiState;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationChannelHelper;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotifCollection;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -78,7 +70,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
import com.android.wm.shell.bubbles.Bubble;
@@ -113,7 +105,6 @@ public class BubblesManager implements Dumpable {
private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
private final NotificationLockscreenUserManager mNotifUserManager;
private final NotificationGroupManagerLegacy mNotificationGroupManager;
- private final NotificationEntryManager mNotificationEntryManager;
private final CommonNotifCollection mCommonNotifCollection;
private final NotifPipeline mNotifPipeline;
private final Executor mSysuiMainExecutor;
@@ -121,6 +112,7 @@ public class BubblesManager implements Dumpable {
private final Bubbles.SysuiProxy mSysuiProxy;
// TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
private final List<NotifCallback> mCallbacks = new ArrayList<>();
+ private final StatusBarWindowCallback mStatusBarWindowCallback;
/**
* Creates {@link BubblesManager}, returns {@code null} if Optional {@link Bubbles} not present
@@ -132,7 +124,6 @@ public class BubblesManager implements Dumpable {
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
ShadeController shadeController,
- ConfigurationController configurationController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
NotificationVisibilityProvider visibilityProvider,
@@ -140,11 +131,9 @@ public class BubblesManager implements Dumpable {
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
- NotifPipelineFlags notifPipelineFlags,
DumpManager dumpManager,
Executor sysuiMainExecutor) {
if (bubblesOptional.isPresent()) {
@@ -153,7 +142,6 @@ public class BubblesManager implements Dumpable {
notificationShadeWindowController,
keyguardStateController,
shadeController,
- configurationController,
statusBarService,
notificationManager,
visibilityProvider,
@@ -161,11 +149,9 @@ public class BubblesManager implements Dumpable {
zenModeController,
notifUserManager,
groupManager,
- entryManager,
notifCollection,
notifPipeline,
sysUiState,
- notifPipelineFlags,
dumpManager,
sysuiMainExecutor);
} else {
@@ -179,7 +165,6 @@ public class BubblesManager implements Dumpable {
NotificationShadeWindowController notificationShadeWindowController,
KeyguardStateController keyguardStateController,
ShadeController shadeController,
- ConfigurationController configurationController,
@Nullable IStatusBarService statusBarService,
INotificationManager notificationManager,
NotificationVisibilityProvider visibilityProvider,
@@ -187,11 +172,9 @@ public class BubblesManager implements Dumpable {
ZenModeController zenModeController,
NotificationLockscreenUserManager notifUserManager,
NotificationGroupManagerLegacy groupManager,
- NotificationEntryManager entryManager,
CommonNotifCollection notifCollection,
NotifPipeline notifPipeline,
SysUiState sysUiState,
- NotifPipelineFlags notifPipelineFlags,
DumpManager dumpManager,
Executor sysuiMainExecutor) {
mContext = context;
@@ -203,7 +186,6 @@ public class BubblesManager implements Dumpable {
mNotificationInterruptStateProvider = interruptionStateProvider;
mNotifUserManager = notifUserManager;
mNotificationGroupManager = groupManager;
- mNotificationEntryManager = entryManager;
mCommonNotifCollection = notifCollection;
mNotifPipeline = notifPipeline;
mSysuiMainExecutor = sysuiMainExecutor;
@@ -213,11 +195,7 @@ public class BubblesManager implements Dumpable {
ServiceManager.getService(Context.STATUS_BAR_SERVICE))
: statusBarService;
- if (notifPipelineFlags.isNewPipelineEnabled()) {
- setupNotifPipeline();
- } else {
- setupNEM();
- }
+ setupNotifPipeline();
dumpManager.registerDumpable(TAG, this);
@@ -230,23 +208,6 @@ public class BubblesManager implements Dumpable {
}
});
- configurationController.addCallback(new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- mBubbles.onConfigChanged(newConfig);
- }
-
- @Override
- public void onUiModeChanged() {
- mBubbles.updateForThemeChanges();
- }
-
- @Override
- public void onThemeChanged() {
- mBubbles.updateForThemeChanges();
- }
- });
-
zenModeController.addCallback(new ZenModeController.Callback() {
@Override
public void onZenChanged(int zen) {
@@ -278,9 +239,15 @@ public class BubblesManager implements Dumpable {
});
+ // Store callback in a field so it won't get GC'd
+ mStatusBarWindowCallback =
+ (keyguardShowing, keyguardOccluded, bouncerShowing, isDozing, panelExpanded) ->
+ mBubbles.onNotificationPanelExpandedChanged(panelExpanded);
+ notificationShadeWindowController.registerCallback(mStatusBarWindowCallback);
+
mSysuiProxy = new Bubbles.SysuiProxy() {
@Override
- public void isNotificationShadeExpand(Consumer<Boolean> callback) {
+ public void isNotificationPanelExpand(Consumer<Boolean> callback) {
sysuiMainExecutor.execute(() -> {
callback.accept(mNotificationShadeWindowController.getPanelExpanded());
});
@@ -430,141 +397,6 @@ public class BubblesManager implements Dumpable {
mBubbles.setSysuiProxy(mSysuiProxy);
}
- private void setupNEM() {
- mNotificationEntryManager.addNotificationEntryListener(
- new NotificationEntryListener() {
- @Override
- public void onPendingEntryAdded(NotificationEntry entry) {
- BubblesManager.this.onEntryAdded(entry);
- }
-
- @Override
- public void onPreEntryUpdated(NotificationEntry entry) {
- BubblesManager.this.onEntryUpdated(entry);
- }
-
- @Override
- public void onEntryRemoved(
- NotificationEntry entry,
- @Nullable NotificationVisibility visibility,
- boolean removedByUser,
- int reason) {
- BubblesManager.this.onEntryRemoved(entry);
- }
-
- @Override
- public void onNotificationRankingUpdated(RankingMap rankingMap) {
- BubblesManager.this.onRankingUpdate(rankingMap);
- }
-
- @Override
- public void onNotificationChannelModified(
- String pkgName,
- UserHandle user,
- NotificationChannel channel,
- int modificationType) {
- BubblesManager.this.onNotificationChannelModified(pkgName,
- user,
- channel,
- modificationType);
- }
- });
-
- // The new pipeline takes care of this as a NotifDismissInterceptor BubbleCoordinator
- mNotificationEntryManager.addNotificationRemoveInterceptor(
- (key, entry, dismissReason) -> {
- final boolean isClearAll = dismissReason == REASON_CANCEL_ALL;
- final boolean isUserDismiss = dismissReason == REASON_CANCEL
- || dismissReason == REASON_CLICK;
- final boolean isAppCancel = dismissReason == REASON_APP_CANCEL
- || dismissReason == REASON_APP_CANCEL_ALL;
- final boolean isSummaryCancel =
- dismissReason == REASON_GROUP_SUMMARY_CANCELED;
-
- // Need to check for !appCancel here because the notification may have
- // previously been dismissed & entry.isRowDismissed would still be true
- boolean userRemovedNotif =
- (entry != null && entry.isRowDismissed() && !isAppCancel)
- || isClearAll || isUserDismiss || isSummaryCancel;
-
- if (userRemovedNotif) {
- return handleDismissalInterception(entry);
- }
- return false;
- });
-
- mNotificationGroupManager.registerGroupChangeListener(
- new NotificationGroupManagerLegacy.OnGroupChangeListener() {
- @Override
- public void onGroupSuppressionChanged(
- NotificationGroupManagerLegacy.NotificationGroup group,
- boolean suppressed) {
- // More notifications could be added causing summary to no longer
- // be suppressed -- in this case need to remove the key.
- final String groupKey = group.summary != null
- ? group.summary.getSbn().getGroupKey()
- : null;
- if (!suppressed && groupKey != null) {
- mBubbles.removeSuppressedSummaryIfNecessary(groupKey, null, null);
- }
- }
- });
-
- addNotifCallback(new NotifCallback() {
- @Override
- public void removeNotification(NotificationEntry entry,
- DismissedByUserStats dismissedByUserStats, int reason) {
- mNotificationEntryManager.performRemoveNotification(entry.getSbn(),
- dismissedByUserStats, reason);
- }
-
- @Override
- public void invalidateNotifications(String reason) {
- mNotificationEntryManager.updateNotifications(reason);
- }
-
- @Override
- public void maybeCancelSummary(NotificationEntry entry) {
- // Check if removed bubble has an associated suppressed group summary that needs
- // to be removed now.
- final String groupKey = entry.getSbn().getGroupKey();
- mBubbles.removeSuppressedSummaryIfNecessary(groupKey, (summaryKey) -> {
- final NotificationEntry summary =
- mNotificationEntryManager.getActiveNotificationUnfiltered(summaryKey);
- if (summary != null) {
- mNotificationEntryManager.performRemoveNotification(
- summary.getSbn(),
- getDismissedByUserStats(summary, false),
- UNDEFINED_DISMISS_REASON);
- }
- }, mSysuiMainExecutor);
-
- // Check if we still need to remove the summary from NoManGroup because the summary
- // may not be in the mBubbleData.mSuppressedGroupKeys list and removed above.
- // For example:
- // 1. Bubbled notifications (group) is posted to shade and are visible bubbles
- // 2. User expands bubbles so now their respective notifications in the shade are
- // hidden, including the group summary
- // 3. User removes all bubbles
- // 4. We expect all the removed bubbles AND the summary (note: the summary was
- // never added to the suppressedSummary list in BubbleData, so we add this check)
- NotificationEntry summary = mNotificationGroupManager.getLogicalGroupSummary(entry);
- if (summary != null) {
- ArrayList<NotificationEntry> summaryChildren =
- mNotificationGroupManager.getLogicalChildren(summary.getSbn());
- boolean isSummaryThisNotif = summary.getKey().equals(entry.getKey());
- if (!isSummaryThisNotif && (summaryChildren == null
- || summaryChildren.isEmpty())) {
- mNotificationEntryManager.performRemoveNotification(
- summary.getSbn(),
- getDismissedByUserStats(summary, false),
- UNDEFINED_DISMISS_REASON);
- }
- }
- }
- });
- }
-
private void setupNotifPipeline() {
mNotifPipeline.addCollectionListener(new NotifCollectionListener() {
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
index a6db2aa48a88..12597e0896b1 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/WMShell.java
@@ -47,7 +47,6 @@ import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.shared.tracing.ProtoTraceable;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
@@ -55,10 +54,6 @@ import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
import com.android.systemui.tracing.nano.SystemUiTraceProto;
-import com.android.wm.shell.ShellCommandHandler;
-import com.android.wm.shell.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.nano.WmShellTraceProto;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
@@ -67,6 +62,7 @@ import com.android.wm.shell.onehanded.OneHandedUiEventLogger;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.protolog.ShellProtoLogImpl;
import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.sysui.ShellInterface;
import java.io.PrintWriter;
import java.util.Arrays;
@@ -106,19 +102,15 @@ public final class WMShell extends CoreStartable
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
// Shell interfaces
+ private final ShellInterface mShell;
private final Optional<Pip> mPipOptional;
private final Optional<SplitScreen> mSplitScreenOptional;
private final Optional<OneHanded> mOneHandedOptional;
- private final Optional<HideDisplayCutout> mHideDisplayCutoutOptional;
- private final Optional<ShellCommandHandler> mShellCommandHandler;
- private final Optional<CompatUI> mCompatUIOptional;
- private final Optional<DragAndDrop> mDragAndDropOptional;
private final CommandQueue mCommandQueue;
private final ConfigurationController mConfigurationController;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- private final NavigationModeController mNavigationModeController;
private final ScreenLifecycle mScreenLifecycle;
private final SysUiState mSysUiState;
private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -127,26 +119,19 @@ public final class WMShell extends CoreStartable
private final Executor mSysUiMainExecutor;
private boolean mIsSysUiStateValid;
- private KeyguardUpdateMonitorCallback mSplitScreenKeyguardCallback;
- private KeyguardUpdateMonitorCallback mPipKeyguardCallback;
private KeyguardUpdateMonitorCallback mOneHandedKeyguardCallback;
- private KeyguardStateController.Callback mCompatUIKeyguardCallback;
private WakefulnessLifecycle.Observer mWakefulnessObserver;
@Inject
public WMShell(Context context,
+ ShellInterface shell,
Optional<Pip> pipOptional,
Optional<SplitScreen> splitScreenOptional,
Optional<OneHanded> oneHandedOptional,
- Optional<HideDisplayCutout> hideDisplayCutoutOptional,
- Optional<ShellCommandHandler> shellCommandHandler,
- Optional<CompatUI> sizeCompatUIOptional,
- Optional<DragAndDrop> dragAndDropOptional,
CommandQueue commandQueue,
ConfigurationController configurationController,
KeyguardStateController keyguardStateController,
KeyguardUpdateMonitor keyguardUpdateMonitor,
- NavigationModeController navigationModeController,
ScreenLifecycle screenLifecycle,
SysUiState sysUiState,
ProtoTracer protoTracer,
@@ -154,28 +139,49 @@ public final class WMShell extends CoreStartable
UserInfoController userInfoController,
@Main Executor sysUiMainExecutor) {
super(context);
+ mShell = shell;
mCommandQueue = commandQueue;
mConfigurationController = configurationController;
mKeyguardStateController = keyguardStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
- mNavigationModeController = navigationModeController;
mScreenLifecycle = screenLifecycle;
mSysUiState = sysUiState;
mPipOptional = pipOptional;
mSplitScreenOptional = splitScreenOptional;
mOneHandedOptional = oneHandedOptional;
- mHideDisplayCutoutOptional = hideDisplayCutoutOptional;
mWakefulnessLifecycle = wakefulnessLifecycle;
mProtoTracer = protoTracer;
- mShellCommandHandler = shellCommandHandler;
- mCompatUIOptional = sizeCompatUIOptional;
- mDragAndDropOptional = dragAndDropOptional;
mUserInfoController = userInfoController;
mSysUiMainExecutor = sysUiMainExecutor;
}
@Override
public void start() {
+ // Notify with the initial configuration and subscribe for new config changes
+ mShell.onConfigurationChanged(mContext.getResources().getConfiguration());
+ mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
+ @Override
+ public void onConfigChanged(Configuration newConfig) {
+ mShell.onConfigurationChanged(newConfig);
+ }
+ });
+
+ // Subscribe to keyguard changes
+ mKeyguardStateController.addCallback(new KeyguardStateController.Callback() {
+ @Override
+ public void onKeyguardShowingChanged() {
+ mShell.onKeyguardVisibilityChanged(mKeyguardStateController.isShowing(),
+ mKeyguardStateController.isOccluded(),
+ mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
+ }
+ });
+ mKeyguardUpdateMonitor.registerCallback(new KeyguardUpdateMonitorCallback() {
+ @Override
+ public void onKeyguardDismissAnimationFinished() {
+ mShell.onKeyguardDismissAnimationFinished();
+ }
+ });
+
// TODO: Consider piping config change and other common calls to a shell component to
// delegate internally
mProtoTracer.add(this);
@@ -183,9 +189,6 @@ public final class WMShell extends CoreStartable
mPipOptional.ifPresent(this::initPip);
mSplitScreenOptional.ifPresent(this::initSplitScreen);
mOneHandedOptional.ifPresent(this::initOneHanded);
- mHideDisplayCutoutOptional.ifPresent(this::initHideDisplayCutout);
- mCompatUIOptional.ifPresent(this::initCompatUi);
- mDragAndDropOptional.ifPresent(this::initDragAndDrop);
}
@VisibleForTesting
@@ -197,42 +200,11 @@ public final class WMShell extends CoreStartable
}
});
- mPipKeyguardCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- pip.onKeyguardVisibilityChanged(showing,
- mKeyguardStateController.isAnimatingBetweenKeyguardAndSurfaceBehind());
- }
-
- @Override
- public void onKeyguardDismissAnimationFinished() {
- pip.onKeyguardDismissAnimationFinished();
- }
- };
- mKeyguardUpdateMonitor.registerCallback(mPipKeyguardCallback);
-
mSysUiState.addCallback(sysUiStateFlag -> {
mIsSysUiStateValid = (sysUiStateFlag & INVALID_SYSUI_STATE_MASK) == 0;
pip.onSystemUiStateChanged(mIsSysUiStateValid, sysUiStateFlag);
});
- mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- pip.onConfigurationChanged(newConfig);
- }
-
- @Override
- public void onDensityOrFontScaleChanged() {
- pip.onDensityOrFontScaleChanged();
- }
-
- @Override
- public void onThemeChanged() {
- pip.onOverlayChanged();
- }
- });
-
// The media session listener needs to be re-registered when switching users
mUserInfoController.addCallback((String name, Drawable picture, String userAccount) ->
pip.registerSessionListenerForCurrentUser());
@@ -240,14 +212,6 @@ public final class WMShell extends CoreStartable
@VisibleForTesting
void initSplitScreen(SplitScreen splitScreen) {
- mSplitScreenKeyguardCallback = new KeyguardUpdateMonitorCallback() {
- @Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- splitScreen.onKeyguardVisibilityChanged(showing);
- }
- };
- mKeyguardUpdateMonitor.registerCallback(mSplitScreenKeyguardCallback);
-
mWakefulnessLifecycle.addObserver(new WakefulnessLifecycle.Observer() {
@Override
public void onFinishedWakingUp() {
@@ -293,14 +257,9 @@ public final class WMShell extends CoreStartable
}
});
+ // TODO: Either move into ShellInterface or register a receiver on the Shell side directly
mOneHandedKeyguardCallback = new KeyguardUpdateMonitorCallback() {
@Override
- public void onKeyguardVisibilityChanged(boolean showing) {
- oneHanded.onKeyguardVisibilityChanged(showing);
- oneHanded.stopOneHanded();
- }
-
- @Override
public void onUserSwitchComplete(int userId) {
oneHanded.onUserSwitch(userId);
}
@@ -348,48 +307,6 @@ public final class WMShell extends CoreStartable
}
}
});
-
- mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- oneHanded.onConfigChanged(newConfig);
- }
- });
- }
-
- @VisibleForTesting
- void initHideDisplayCutout(HideDisplayCutout hideDisplayCutout) {
- mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- hideDisplayCutout.onConfigurationChanged(newConfig);
- }
- });
- }
-
- @VisibleForTesting
- void initCompatUi(CompatUI sizeCompatUI) {
- mCompatUIKeyguardCallback = new KeyguardStateController.Callback() {
- @Override
- public void onKeyguardShowingChanged() {
- sizeCompatUI.onKeyguardShowingChanged(mKeyguardStateController.isShowing());
- }
- };
- mKeyguardStateController.addCallback(mCompatUIKeyguardCallback);
- }
-
- void initDragAndDrop(DragAndDrop dragAndDrop) {
- mConfigurationController.addCallback(new ConfigurationController.ConfigurationListener() {
- @Override
- public void onConfigChanged(Configuration newConfig) {
- dragAndDrop.onConfigChanged(newConfig);
- }
-
- @Override
- public void onThemeChanged() {
- dragAndDrop.onThemeChanged();
- }
- });
}
@Override
@@ -404,8 +321,7 @@ public final class WMShell extends CoreStartable
@Override
public void dump(PrintWriter pw, String[] args) {
// Handle commands if provided
- if (mShellCommandHandler.isPresent()
- && mShellCommandHandler.get().handleCommand(args, pw)) {
+ if (mShell.handleCommand(args, pw)) {
return;
}
// Handle logging commands if provided
@@ -413,8 +329,7 @@ public final class WMShell extends CoreStartable
return;
}
// Dump WMShell stuff here if no commands were handled
- mShellCommandHandler.ifPresent(
- shellCommandHandler -> shellCommandHandler.dump(pw));
+ mShell.dump(pw);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
index 5db2cf49636c..8b9a1e022d26 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerTest.java
@@ -51,6 +51,7 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.plugins.ClockPlugin;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
index 8717a0eaf57f..6c6f0acd7085 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchTest.java
@@ -43,6 +43,7 @@ import android.widget.TextClock;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ClockPlugin;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.StatusBarState;
import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
index ac1a83c269e0..4021652295c1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardHostViewControllerTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -31,6 +32,7 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableResources;
import android.view.Gravity;
+import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
@@ -42,6 +44,7 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -104,6 +107,17 @@ public class KeyguardHostViewControllerTest extends SysuiTestCase {
}
@Test
+ public void onBouncerVisible_propagatesToKeyguardSecurityContainerController() {
+ mKeyguardHostViewController.onBouncerVisibilityChanged(ViewGroup.VISIBLE);
+ mKeyguardHostViewController.onBouncerVisibilityChanged(ViewGroup.INVISIBLE);
+
+ InOrder order = inOrder(mKeyguardSecurityContainerController);
+ order.verify(mKeyguardSecurityContainerController).onBouncerVisibilityChanged(View.VISIBLE);
+ order.verify(mKeyguardSecurityContainerController).onBouncerVisibilityChanged(
+ View.INVISIBLE);
+ }
+
+ @Test
public void testGravityReappliedOnConfigurationChange() {
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index b1e2012ecd40..ad6d146fb5f3 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -92,7 +92,6 @@ private fun faceModel(user: Int) = KeyguardFaceListenModel(
keyguardAwake = false,
keyguardGoingAway = false,
listeningForFaceAssistant = false,
- lockIconPressed = false,
occludingAppRequestingFaceAuth = false,
primaryUser = false,
scanningAllowedByStrongAuth = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
index 1518ea160016..8293cc21f3d1 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardMessageAreaControllerTest.java
@@ -17,11 +17,13 @@
package com.android.keyguard;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
+import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
@@ -90,4 +92,19 @@ public class KeyguardMessageAreaControllerTest extends SysuiTestCase {
mMessageAreaController.setBouncerShowing(true);
verify(mKeyguardMessageArea).setBouncerShowing(true);
}
+
+ @Test
+ public void testSetMessageIfEmpty_empty() {
+ mMessageAreaController.setMessage("");
+ mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
+ verify(mKeyguardMessageArea).setMessage(R.string.keyguard_enter_your_pin);
+ }
+
+ @Test
+ public void testSetMessageIfEmpty_notEmpty() {
+ mMessageAreaController.setMessage("abc");
+ mMessageAreaController.setMessageIfEmpty(R.string.keyguard_enter_your_pin);
+ verify(mKeyguardMessageArea, never()).setMessage(getContext()
+ .getResources().getText(R.string.keyguard_enter_your_pin));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
index a1e23a201b72..ec856031c23c 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt
@@ -73,25 +73,25 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- Mockito.`when`(keyguardPasswordView
- .findViewById<KeyguardMessageArea>(R.id.keyguard_message_area))
- .thenReturn(mKeyguardMessageArea)
+ Mockito.`when`(
+ keyguardPasswordView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area)
+ ).thenReturn(mKeyguardMessageArea)
Mockito.`when`(messageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
+ .thenReturn(mKeyguardMessageAreaController)
keyguardPasswordViewController = KeyguardPasswordViewController(
- keyguardPasswordView,
- keyguardUpdateMonitor,
- securityMode,
- lockPatternUtils,
- keyguardSecurityCallback,
- messageAreaControllerFactory,
- latencyTracker,
- inputMethodManager,
- emergencyButtonController,
- mainExecutor,
- mContext.resources,
- falsingCollector,
- keyguardViewController
+ keyguardPasswordView,
+ keyguardUpdateMonitor,
+ securityMode,
+ lockPatternUtils,
+ keyguardSecurityCallback,
+ messageAreaControllerFactory,
+ latencyTracker,
+ inputMethodManager,
+ emergencyButtonController,
+ mainExecutor,
+ mContext.resources,
+ falsingCollector,
+ keyguardViewController
)
}
@@ -110,4 +110,11 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() {
keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED)
verify(keyguardPasswordView, never()).requestFocus()
}
-} \ No newline at end of file
+
+ @Test
+ fun onResume_testSetInitialText() {
+ keyguardPasswordViewController.onResume(KeyguardSecurityView.SCREEN_ON)
+ verify(mKeyguardMessageAreaController)
+ .setMessageIfEmpty(R.string.keyguard_enter_your_password)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
index 8e1e42a9c9a2..616a1056d161 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPatternViewControllerTest.kt
@@ -31,8 +31,8 @@ import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -41,28 +41,39 @@ import org.mockito.MockitoAnnotations
class KeyguardPatternViewControllerTest : SysuiTestCase() {
@Mock
private lateinit var mKeyguardPatternView: KeyguardPatternView
+
@Mock
private lateinit var mKeyguardUpdateMonitor: KeyguardUpdateMonitor
+
@Mock
private lateinit var mSecurityMode: KeyguardSecurityModel.SecurityMode
+
@Mock
private lateinit var mLockPatternUtils: LockPatternUtils
+
@Mock
private lateinit var mKeyguardSecurityCallback: KeyguardSecurityCallback
+
@Mock
private lateinit var mLatencyTracker: LatencyTracker
private var mFalsingCollector: FalsingCollector = FalsingCollectorFake()
+
@Mock
private lateinit var mEmergencyButtonController: EmergencyButtonController
+
@Mock
private lateinit
var mKeyguardMessageAreaControllerFactory: KeyguardMessageAreaController.Factory
+
@Mock
private lateinit var mKeyguardMessageArea: KeyguardMessageArea
+
@Mock
private lateinit var mKeyguardMessageAreaController: KeyguardMessageAreaController
+
@Mock
private lateinit var mLockPatternView: LockPatternView
+
@Mock
private lateinit var mPostureController: DevicePostureController
@@ -73,15 +84,17 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
`when`(mKeyguardPatternView.isAttachedToWindow).thenReturn(true)
`when`(mKeyguardPatternView.findViewById<KeyguardMessageArea>(R.id.keyguard_message_area))
- .thenReturn(mKeyguardMessageArea)
+ .thenReturn(mKeyguardMessageArea)
`when`(mKeyguardPatternView.findViewById<LockPatternView>(R.id.lockPatternView))
- .thenReturn(mLockPatternView)
+ .thenReturn(mLockPatternView)
`when`(mKeyguardMessageAreaControllerFactory.create(mKeyguardMessageArea))
- .thenReturn(mKeyguardMessageAreaController)
- mKeyguardPatternViewController = KeyguardPatternViewController(mKeyguardPatternView,
- mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
- mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
- mKeyguardMessageAreaControllerFactory, mPostureController)
+ .thenReturn(mKeyguardMessageAreaController)
+ mKeyguardPatternViewController = KeyguardPatternViewController(
+ mKeyguardPatternView,
+ mKeyguardUpdateMonitor, mSecurityMode, mLockPatternUtils, mKeyguardSecurityCallback,
+ mLatencyTracker, mFalsingCollector, mEmergencyButtonController,
+ mKeyguardMessageAreaControllerFactory, mPostureController
+ )
}
@Test
@@ -90,4 +103,11 @@ class KeyguardPatternViewControllerTest : SysuiTestCase() {
mKeyguardPatternViewController.onPause()
verify(mKeyguardMessageAreaController).setMessage("")
}
+
+ @Test
+ fun onResume_setInitialText() {
+ mKeyguardPatternViewController.onResume(KeyguardSecurityView.SCREEN_ON)
+ verify(mKeyguardMessageAreaController)
+ .setMessageIfEmpty(R.string.keyguard_enter_your_pattern)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
index 9597cabb2eb8..7bc8e8a722f0 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinBasedInputViewControllerTest.java
@@ -113,5 +113,11 @@ public class KeyguardPinBasedInputViewControllerTest extends SysuiTestCase {
mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
verify(mPasswordEntry).requestFocus();
}
+
+ @Test
+ public void onResume_setInitialText() {
+ mKeyguardPinViewController.onResume(KeyguardSecurityView.SCREEN_ON);
+ verify(mKeyguardMessageAreaController).setMessageIfEmpty(R.string.keyguard_enter_your_pin);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index 4d3343059718..68e49c0a1d4b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -34,9 +35,11 @@ import static org.mockito.Mockito.when;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.hardware.biometrics.BiometricSourceType;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.MotionEvent;
+import android.view.View;
import android.view.WindowInsetsController;
import androidx.test.filters.SmallTest;
@@ -47,6 +50,7 @@ import com.android.internal.widget.LockPatternUtils;
import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.SidefpsController;
import com.android.systemui.classifier.FalsingCollector;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.SessionTracker;
@@ -60,16 +64,18 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
+import java.util.Optional;
+
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
- private static final int VIEW_WIDTH = 1600;
-
@Rule
public MockitoRule mRule = MockitoJUnit.rule();
@@ -104,6 +110,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Mock
private KeyguardMessageAreaController.Factory mKeyguardMessageAreaControllerFactory;
@Mock
+ private KeyguardMessageAreaController mKeyguardMessageAreaController;
+ @Mock
private KeyguardMessageArea mKeyguardMessageArea;
@Mock
private ConfigurationController mConfigurationController;
@@ -125,6 +133,14 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
private SessionTracker mSessionTracker;
@Mock
private KeyguardViewController mKeyguardViewController;
+ @Mock
+ private SidefpsController mSidefpsController;
+ @Mock
+ private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
+
+ @Captor
+ private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
+
private Configuration mConfiguration;
private KeyguardSecurityContainerController mKeyguardSecurityContainerController;
@@ -139,7 +155,6 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
when(mResources.getConfiguration()).thenReturn(mConfiguration);
when(mView.getContext()).thenReturn(mContext);
when(mView.getResources()).thenReturn(mResources);
- when(mView.getWidth()).thenReturn(VIEW_WIDTH);
when(mAdminSecondaryLockScreenControllerFactory.create(any(KeyguardSecurityCallback.class)))
.thenReturn(mAdminSecondaryLockScreenController);
when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
@@ -147,6 +162,8 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
when(mKeyguardPasswordView.getRootView()).thenReturn(mSecurityViewFlipper);
when(mKeyguardPasswordView.findViewById(R.id.keyguard_message_area))
.thenReturn(mKeyguardMessageArea);
+ when(mKeyguardMessageAreaControllerFactory.create(any(KeyguardMessageArea.class)))
+ .thenReturn(mKeyguardMessageAreaController);
when(mKeyguardPasswordView.getWindowInsetsController()).thenReturn(mWindowInsetsController);
mKeyguardPasswordViewController = new KeyguardPasswordViewController(
(KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor,
@@ -160,7 +177,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mKeyguardStateController, mKeyguardSecurityViewFlipperController,
mConfigurationController, mFalsingCollector, mFalsingManager,
mUserSwitcherController, mFeatureFlags, mGlobalSettings,
- mSessionTracker).create(mSecurityCallback);
+ mSessionTracker, Optional.of(mSidefpsController)).create(mSecurityCallback);
}
@Test
@@ -208,49 +225,26 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
mUserSwitcherController);
}
- private void touchDownLeftSide() {
+ private void touchDown() {
mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
MotionEvent.obtain(
/* downTime= */0,
/* eventTime= */0,
MotionEvent.ACTION_DOWN,
- /* x= */VIEW_WIDTH / 3f,
- /* y= */0,
- /* metaState= */0));
- }
-
- private void touchDownRightSide() {
- mKeyguardSecurityContainerController.mGlobalTouchListener.onTouchEvent(
- MotionEvent.obtain(
- /* downTime= */0,
- /* eventTime= */0,
- MotionEvent.ACTION_DOWN,
- /* x= */(VIEW_WIDTH / 3f) * 2,
+ /* x= */0,
/* y= */0,
/* metaState= */0));
}
@Test
- public void onInterceptTap_inhibitsFalsingInOneHandedMode() {
- when(mView.getMode()).thenReturn(MODE_ONE_HANDED);
- when(mView.isOneHandedModeLeftAligned()).thenReturn(true);
+ public void onInterceptTap_inhibitsFalsingInSidedSecurityMode() {
- touchDownLeftSide();
+ when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(false);
+ touchDown();
verify(mFalsingCollector, never()).avoidGesture();
- // Now on the right.
- touchDownRightSide();
- verify(mFalsingCollector).avoidGesture();
-
- // Move and re-test
- reset(mFalsingCollector);
- when(mView.isOneHandedModeLeftAligned()).thenReturn(false);
-
- // On the right...
- touchDownRightSide();
- verify(mFalsingCollector, never()).avoidGesture();
-
- touchDownLeftSide();
+ when(mView.isTouchOnTheOtherSideOfSecurity(any())).thenReturn(true);
+ touchDown();
verify(mFalsingCollector).avoidGesture();
}
@@ -281,9 +275,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
@Test
public void showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
when(mResources.getBoolean(R.bool.can_use_one_handed_bouncer)).thenReturn(true);
- when(mKeyguardSecurityViewFlipperController.getSecurityView(
- eq(SecurityMode.Password), any(KeyguardSecurityCallback.class)))
- .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewController);
+ setupGetSecurityView();
mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
verify(mView).initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
@@ -299,4 +291,126 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase {
verify(mUserSwitcherController)
.removeUserSwitchCallback(any(UserSwitcherController.UserSwitchCallback.class));
}
+
+ @Test
+ public void onBouncerVisibilityChanged_allConditionsGood_sideFpsHintShown() {
+ setupConditionsToEnableSideFpsHint();
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+
+ verify(mSidefpsController).show();
+ verify(mSidefpsController, never()).hide();
+ }
+
+ @Test
+ public void onBouncerVisibilityChanged_fpsSensorNotRunning_sideFpsHintHidden() {
+ setupConditionsToEnableSideFpsHint();
+ setFingerprintDetectionRunning(false);
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ @Test
+ public void onBouncerVisibilityChanged_withoutSidedSecurity_sideFpsHintHidden() {
+ setupConditionsToEnableSideFpsHint();
+ setSidedSecurityMode(false);
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ @Test
+ public void onBouncerVisibilityChanged_needsStrongAuth_sideFpsHintHidden() {
+ setupConditionsToEnableSideFpsHint();
+ setNeedsStrongAuth(true);
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ @Test
+ public void onBouncerVisibilityChanged_sideFpsHintShown_sideFpsHintHidden() {
+ setupGetSecurityView();
+ setupConditionsToEnableSideFpsHint();
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+ verify(mSidefpsController, atLeastOnce()).show();
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.INVISIBLE);
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ @Test
+ public void onStartingToHide_sideFpsHintShown_sideFpsHintHidden() {
+ setupGetSecurityView();
+ setupConditionsToEnableSideFpsHint();
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+ verify(mSidefpsController, atLeastOnce()).show();
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onStartingToHide();
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ @Test
+ public void onPause_sideFpsHintShown_sideFpsHintHidden() {
+ setupGetSecurityView();
+ setupConditionsToEnableSideFpsHint();
+ mKeyguardSecurityContainerController.onBouncerVisibilityChanged(View.VISIBLE);
+ verify(mSidefpsController, atLeastOnce()).show();
+ reset(mSidefpsController);
+
+ mKeyguardSecurityContainerController.onPause();
+
+ verify(mSidefpsController).hide();
+ verify(mSidefpsController, never()).show();
+ }
+
+ private void setupConditionsToEnableSideFpsHint() {
+ attachView();
+ setSidedSecurityMode(true);
+ setFingerprintDetectionRunning(true);
+ setNeedsStrongAuth(false);
+ }
+
+ private void attachView() {
+ mKeyguardSecurityContainerController.onViewAttached();
+ verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture());
+ }
+
+ private void setFingerprintDetectionRunning(boolean running) {
+ when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(running);
+ mKeyguardUpdateMonitorCallback.getValue().onBiometricRunningStateChanged(running,
+ BiometricSourceType.FINGERPRINT);
+ }
+
+ private void setSidedSecurityMode(boolean sided) {
+ when(mView.isSidedSecurityMode()).thenReturn(sided);
+ }
+
+ private void setNeedsStrongAuth(boolean needed) {
+ when(mKeyguardUpdateMonitor.userNeedsStrongAuth()).thenReturn(needed);
+ mKeyguardUpdateMonitorCallback.getValue().onStrongAuthStateChanged(/* userId= */ 0);
+ }
+
+ private void setupGetSecurityView() {
+ when(mKeyguardSecurityViewFlipperController.getSecurityView(
+ any(), any(KeyguardSecurityCallback.class)))
+ .thenReturn((KeyguardInputViewController) mKeyguardPasswordViewControllerMock);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index d49f4d8172dc..f2ac0c7a7736 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -16,6 +16,11 @@
package com.android.keyguard;
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE;
+import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT;
+import static android.provider.Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
@@ -25,7 +30,9 @@ import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
@@ -34,14 +41,13 @@ import static org.mockito.Mockito.when;
import android.content.pm.UserInfo;
import android.content.res.Configuration;
import android.graphics.Insets;
-import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Gravity;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowInsets;
-import android.view.WindowInsetsController;
import android.widget.FrameLayout;
import androidx.test.filters.SmallTest;
@@ -70,6 +76,9 @@ import java.util.ArrayList;
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper()
public class KeyguardSecurityContainerTest extends SysuiTestCase {
+
+ private static final int VIEW_WIDTH = 1600;
+
private int mScreenWidth;
private int mFakeMeasureSpec;
@@ -77,8 +86,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
public MockitoRule mRule = MockitoJUnit.rule();
@Mock
- private WindowInsetsController mWindowInsetsController;
- @Mock
private KeyguardSecurityViewFlipper mSecurityViewFlipper;
@Mock
private GlobalSettings mGlobalSettings;
@@ -102,7 +109,6 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
mSecurityViewFlipperLayoutParams = new FrameLayout.LayoutParams(
MATCH_PARENT, MATCH_PARENT);
- when(mSecurityViewFlipper.getWindowInsetsController()).thenReturn(mWindowInsetsController);
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
mKeyguardSecurityContainer = new KeyguardSecurityContainer(getContext());
mKeyguardSecurityContainer.mSecurityViewFlipper = mSecurityViewFlipper;
@@ -212,14 +218,12 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
mKeyguardSecurityContainer.updatePositionByTouchX(
mKeyguardSecurityContainer.getWidth() - 1f);
- verify(mGlobalSettings).putInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE_RIGHT);
- verify(mSecurityViewFlipper).setTranslationX(
+ verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_RIGHT);
+ assertSecurityTranslationX(
mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
mKeyguardSecurityContainer.updatePositionByTouchX(1f);
- verify(mGlobalSettings).putInt(Settings.Global.ONE_HANDED_KEYGUARD_SIDE,
- Settings.Global.ONE_HANDED_KEYGUARD_SIDE_LEFT);
+ verify(mGlobalSettings).putInt(ONE_HANDED_KEYGUARD_SIDE, ONE_HANDED_KEYGUARD_SIDE_LEFT);
verify(mSecurityViewFlipper).setTranslationX(0.0f);
}
@@ -237,49 +241,43 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
}
@Test
- public void testUserSwitcherModeViewGravityLandscape() {
+ public void testUserSwitcherModeViewPositionLandscape() {
// GIVEN one user has been setup and in landscape
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
- Configuration config = new Configuration();
- config.orientation = Configuration.ORIENTATION_LANDSCAPE;
- when(getContext().getResources().getConfiguration()).thenReturn(config);
+ Configuration landscapeConfig = configuration(ORIENTATION_LANDSCAPE);
+ when(getContext().getResources().getConfiguration()).thenReturn(landscapeConfig);
// WHEN UserSwitcherViewMode is initialized and config has changed
setupUserSwitcher();
- reset(mSecurityViewFlipper);
- when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
- mKeyguardSecurityContainer.onConfigurationChanged(config);
+ mKeyguardSecurityContainer.onConfigurationChanged(landscapeConfig);
// THEN views are oriented side by side
- verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture());
- assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.RIGHT | Gravity.BOTTOM);
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ assertSecurityGravity(Gravity.LEFT | Gravity.BOTTOM);
+ assertUserSwitcherGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
+ assertSecurityTranslationX(
+ mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ assertUserSwitcherTranslationX(0f);
+
}
@Test
public void testUserSwitcherModeViewGravityPortrait() {
// GIVEN one user has been setup and in landscape
when(mUserSwitcherController.getUsers()).thenReturn(buildUserRecords(1));
- Configuration config = new Configuration();
- config.orientation = Configuration.ORIENTATION_PORTRAIT;
- when(getContext().getResources().getConfiguration()).thenReturn(config);
+ Configuration portraitConfig = configuration(ORIENTATION_PORTRAIT);
+ when(getContext().getResources().getConfiguration()).thenReturn(portraitConfig);
// WHEN UserSwitcherViewMode is initialized and config has changed
setupUserSwitcher();
reset(mSecurityViewFlipper);
when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
- mKeyguardSecurityContainer.onConfigurationChanged(config);
+ mKeyguardSecurityContainer.onConfigurationChanged(portraitConfig);
// THEN views are both centered horizontally
- verify(mSecurityViewFlipper).setLayoutParams(mLayoutCaptor.capture());
- assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(Gravity.CENTER_HORIZONTAL);
- ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
- R.id.keyguard_bouncer_user_switcher);
- assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
- .isEqualTo(Gravity.CENTER_HORIZONTAL);
+ assertSecurityGravity(Gravity.CENTER_HORIZONTAL);
+ assertUserSwitcherGravity(Gravity.CENTER_HORIZONTAL);
+ assertSecurityTranslationX(0);
+ assertUserSwitcherTranslationX(0);
}
@Test
@@ -310,9 +308,102 @@ public class KeyguardSecurityContainerTest extends SysuiTestCase {
assertThat(anchor.isClickable()).isTrue();
}
+ @Test
+ public void testTouchesAreRecognizedAsBeingOnTheOtherSideOfSecurity() {
+ setupUserSwitcher();
+ setViewWidth(VIEW_WIDTH);
+
+ // security is on the right side by default
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventLeftSide())).isTrue();
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventRightSide())).isFalse();
+
+ // move security to the left side
+ when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
+ mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
+
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventLeftSide())).isFalse();
+ assertThat(mKeyguardSecurityContainer.isTouchOnTheOtherSideOfSecurity(
+ touchEventRightSide())).isTrue();
+ }
+
+ @Test
+ public void testSecuritySwitchesSidesInLandscapeUserSwitcherMode() {
+ when(getContext().getResources().getConfiguration())
+ .thenReturn(configuration(ORIENTATION_LANDSCAPE));
+ setupUserSwitcher();
+
+ // switch sides
+ when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_LEFT);
+ mKeyguardSecurityContainer.onConfigurationChanged(new Configuration());
+
+ assertSecurityTranslationX(0);
+ assertUserSwitcherTranslationX(
+ mKeyguardSecurityContainer.getWidth() - mSecurityViewFlipper.getWidth());
+ }
+
+ private Configuration configuration(@Configuration.Orientation int orientation) {
+ Configuration config = new Configuration();
+ config.orientation = orientation;
+ return config;
+ }
+
+ private void assertSecurityTranslationX(float translation) {
+ verify(mSecurityViewFlipper).setTranslationX(translation);
+ }
+
+ private void assertUserSwitcherTranslationX(float translation) {
+ ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(userSwitcher.getTranslationX()).isEqualTo(translation);
+ }
+
+ private void assertUserSwitcherGravity(@Gravity.GravityFlags int gravity) {
+ ViewGroup userSwitcher = mKeyguardSecurityContainer.findViewById(
+ R.id.keyguard_bouncer_user_switcher);
+ assertThat(((FrameLayout.LayoutParams) userSwitcher.getLayoutParams()).gravity)
+ .isEqualTo(gravity);
+ }
+
+ private void assertSecurityGravity(@Gravity.GravityFlags int gravity) {
+ verify(mSecurityViewFlipper, atLeastOnce()).setLayoutParams(mLayoutCaptor.capture());
+ assertThat(mLayoutCaptor.getValue().gravity).isEqualTo(gravity);
+ }
+
+ private void setViewWidth(int width) {
+ mKeyguardSecurityContainer.setRight(width);
+ mKeyguardSecurityContainer.setLeft(0);
+ }
+
+ private MotionEvent touchEventLeftSide() {
+ return MotionEvent.obtain(
+ /* downTime= */0,
+ /* eventTime= */0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */VIEW_WIDTH / 3f,
+ /* y= */0,
+ /* metaState= */0);
+ }
+
+ private MotionEvent touchEventRightSide() {
+ return MotionEvent.obtain(
+ /* downTime= */0,
+ /* eventTime= */0,
+ MotionEvent.ACTION_DOWN,
+ /* x= */(VIEW_WIDTH / 3f) * 2,
+ /* y= */0,
+ /* metaState= */0);
+ }
+
private void setupUserSwitcher() {
+ when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
mGlobalSettings, mFalsingManager, mUserSwitcherController);
+ // reset mSecurityViewFlipper so setup doesn't influence test verifications
+ reset(mSecurityViewFlipper);
+ when(mSecurityViewFlipper.getLayoutParams()).thenReturn(mSecurityViewFlipperLayoutParams);
}
private ArrayList<UserRecord> buildUserRecords(int count) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
index 70025230fa83..4dcaa7cf8c09 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewControllerTest.java
@@ -89,28 +89,6 @@ public class KeyguardStatusViewControllerTest extends SysuiTestCase {
}
@Test
- public void timeFormatUpdateNotifiesClockSwitchController() {
- mController.onViewAttached();
-
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
-
- mKeyguardUpdateMonitorCallbackCaptor.getValue().onTimeFormatChanged("");
- verify(mKeyguardClockSwitchController).refreshFormat();
- }
-
- @Test
- public void userChangeNotifiesClockSwitchController() {
- mController.onViewAttached();
-
- verify(mKeyguardUpdateMonitor).registerCallback(
- mKeyguardUpdateMonitorCallbackCaptor.capture());
-
- mKeyguardUpdateMonitorCallbackCaptor.getValue().onUserSwitchComplete(0);
- verify(mKeyguardClockSwitchController).refreshFormat();
- }
-
- @Test
public void setTranslationYExcludingMedia_forwardsCallToView() {
float translationY = 123f;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 2dc066c8a9db..84903d17852f 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -1109,8 +1109,7 @@ public class KeyguardUpdateMonitorTest extends SysuiTestCase {
// THEN face unlock is not running b/c status bar state changes don't cause biometric
// listening state to update
- assertThat(mKeyguardUpdateMonitor.isFaceUnlockRunning(
- KeyguardUpdateMonitor.getCurrentUser())).isEqualTo(false);
+ assertThat(mKeyguardUpdateMonitor.isFaceDetectionRunning()).isEqualTo(false);
// WHEN biometric listening state is updated
mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
index 5d8e4351cfd9..a0fdc8f1555e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/DependencyTest.java
@@ -26,6 +26,8 @@ import com.android.systemui.statusbar.policy.FlashlightController;
import org.junit.Assert;
import org.junit.Test;
+import java.util.concurrent.ExecutionException;
+
@SmallTest
public class DependencyTest extends SysuiTestCase {
@@ -44,10 +46,12 @@ public class DependencyTest extends SysuiTestCase {
}
@Test
- public void testInitDependency() {
+ public void testInitDependency() throws ExecutionException, InterruptedException {
Dependency.clearDependencies();
- Dependency dependency =
- SystemUIFactory.getInstance().getSysUIComponent().createDependency();
+ SystemUIInitializer initializer =
+ SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ initializer.init(true);
+ Dependency dependency = initializer.getSysUIComponent().createDependency();
dependency.start();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
index 8c20b248d02c..9179efc9f39f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiBaseFragmentTest.java
@@ -34,6 +34,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.mockito.Mockito;
+import java.util.concurrent.ExecutionException;
+
public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
public static final Class<?>[] ALL_SUPPORTED_CLASSES = LeakCheckedTest.ALL_SUPPORTED_CLASSES;
@@ -54,10 +56,11 @@ public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
}
@Before
- public void SysuiSetup() {
- SystemUIFactory.createFromConfig(mContext, true);
- mDependency = new TestableDependency(
- SystemUIFactory.getInstance().getSysUIComponent().createDependency());
+ public void sysuiSetup() throws ExecutionException, InterruptedException {
+ SystemUIInitializer initializer =
+ SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ initializer.init(true);
+ mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
// TODO: Figure out another way to give reference to a SysuiTestableContext.
@@ -77,7 +80,6 @@ public abstract class SysuiBaseFragmentTest extends BaseFragmentTest {
public void SysuiTeardown() {
InstrumentationRegistry.registerInstance(mRealInstrumentation,
InstrumentationRegistry.getArguments());
- SystemUIFactory.cleanup();
}
@AfterClass
diff --git a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
index 8c7927782d2d..c52ea60f0bfc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/SysuiTestCase.java
@@ -76,9 +76,10 @@ public abstract class SysuiTestCase {
@Before
public void SysuiSetup() throws Exception {
- SystemUIFactory.createFromConfig(mContext, true);
- mDependency = new TestableDependency(
- SystemUIFactory.getInstance().getSysUIComponent().createDependency());
+ SystemUIInitializer initializer =
+ SystemUIInitializerFactory.createFromConfigNoAssert(mContext);
+ initializer.init(true);
+ mDependency = new TestableDependency(initializer.getSysUIComponent().createDependency());
Dependency.setInstance(mDependency);
mFakeBroadcastDispatcher = new FakeBroadcastDispatcher(mContext, mock(Looper.class),
mock(Executor.class), mock(DumpManager.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
index 7e9f84c1ef8c..bebd8712a750 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java
@@ -191,6 +191,16 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
}
@Test
+ public void hideMenuViewWhenStartingAnimation_animatorNotRunning() {
+ mMenuView.show();
+
+ mMenuView.mDragAnimator.start();
+ mMenuView.hide();
+
+ assertThat(mMenuView.mDragAnimator.isRunning()).isFalse();
+ }
+
+ @Test
public void onTargetsChanged_singleTarget_expectedRadii() {
final Position alignRightPosition = new Position(1.0f, 0.0f);
final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
index fe2efa5ea30d..4218e0904c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/DialogLaunchAnimatorTest.kt
@@ -15,6 +15,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.WindowManager
import android.widget.LinearLayout
import androidx.test.filters.SmallTest
+import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.policy.DecorView
import com.android.systemui.SysuiTestCase
import junit.framework.Assert.assertEquals
@@ -29,6 +30,8 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.verify
import org.mockito.junit.MockitoJUnit
@SmallTest
@@ -40,12 +43,14 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
private val attachedViews = mutableSetOf<View>()
@Mock lateinit var dreamManager: IDreamManager
+ @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
@get:Rule val rule = MockitoJUnit.rule()
@Before
fun setUp() {
dialogLaunchAnimator = DialogLaunchAnimator(
- dreamManager, launchAnimator, isForTesting = true)
+ dreamManager, interactionJankMonitor, launchAnimator, isForTesting = true
+ )
}
@After
@@ -90,7 +95,8 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
// The dialog content is inside this fake window view.
assertNotNull(
- dialogContentWithBackground.findViewByPredicate { it === dialog.contentView })
+ dialogContentWithBackground.findViewByPredicate { it === dialog.contentView }
+ )
// Clicking the transparent background should dismiss the dialog.
runOnMainThreadAndWaitForIdleSync {
@@ -161,6 +167,32 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
assertNotEquals(0, dialog.window.attributes.windowAnimations)
}
+ @Test
+ fun testCujSpecificationLogsInteraction() {
+ val touchSurface = createTouchSurface()
+ runOnMainThreadAndWaitForIdleSync {
+ val dialog = TestDialog(context)
+ dialogLaunchAnimator.showFromView(
+ dialog, touchSurface, cuj = DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN))
+ }
+
+ verify(interactionJankMonitor).begin(any())
+ verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN)
+ }
+
+ @Test
+ fun testShowFromDialogCujSpecificationLogsInteraction() {
+ val firstDialog = createAndShowDialog()
+ runOnMainThreadAndWaitForIdleSync {
+ val dialog = TestDialog(context)
+ dialogLaunchAnimator.showFromDialog(
+ dialog, firstDialog, cuj = DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN))
+ dialog
+ }
+ verify(interactionJankMonitor).begin(any())
+ verify(interactionJankMonitor).end(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN)
+ }
+
private fun createAndShowDialog(): TestDialog {
val touchSurface = createTouchSurface()
return runOnMainThreadAndWaitForIdleSync {
@@ -224,4 +256,4 @@ class DialogLaunchAnimatorTest : SysuiTestCase() {
window.setBackgroundDrawable(windowBackground)
}
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
index 95fa3b9c594b..f01da2dd3a6b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/FontInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/FontInterpolatorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.Paint
import android.graphics.fonts.Font
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index 3322414e9e7a..ed0cd7ed9b24 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.animation.AnimatorListenerAdapter
import android.animation.ValueAnimator
@@ -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`
private val PAINT = TextPaint().apply {
textSize = 32f
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
index e42d537596c5..2a183bd43f83 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/TextInterpolatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextInterpolatorTest.kt
@@ -14,10 +14,11 @@
* limitations under the License.
*/
-package com.android.keyguard
+package com.android.systemui.animation
import android.graphics.Bitmap
import android.graphics.Canvas
+import android.graphics.Color
import android.graphics.Typeface
import android.graphics.fonts.Font
import android.graphics.fonts.FontFamily
@@ -30,10 +31,10 @@ import android.text.TextPaint
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
import java.io.File
import kotlin.math.ceil
+import org.junit.Test
+import org.junit.runner.RunWith
private const val TEXT = "Hello, World."
private const val BIDI_TEXT = "abc\u05D0\u05D1\u05D2"
@@ -194,10 +195,132 @@ class TextInterpolatorTest : SysuiTestCase() {
assertThat(expected.sameAs(actual)).isTrue()
}
+
+ @Test
+ fun testGlyphCallback_Empty() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ assertThat(expected.sameAs(actual)).isTrue()
+ }
+
+ @Test
+ fun testGlyphCallback_Xcoordinate() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.x += 30f
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
+
+ @Test
+ fun testGlyphCallback_Ycoordinate() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.y += 30f
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
+
+ @Test
+ fun testGlyphCallback_TextSize() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.textSize += 10f
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
+
+ @Test
+ fun testGlyphCallback_Color() {
+ val layout = makeLayout(BIDI_TEXT, PAINT, TextDirectionHeuristics.RTL)
+
+ val interp = TextInterpolator(layout).apply {
+ glyphFilter = { glyph, progress ->
+ glyph.color = Color.RED
+ }
+ }
+ interp.basePaint.set(START_PAINT)
+ interp.onBasePaintModified()
+
+ interp.targetPaint.set(END_PAINT)
+ interp.onTargetPaintModified()
+
+ // Just after created TextInterpolator, it should have 0 progress.
+ val actual = interp.toBitmap(BMP_WIDTH, BMP_HEIGHT)
+ val expected = makeLayout(BIDI_TEXT, START_PAINT, TextDirectionHeuristics.RTL)
+ .toBitmap(BMP_WIDTH, BMP_HEIGHT)
+
+ // The glyph position was modified by callback, so the bitmap should not be the same.
+ // We cannot modify the result of StaticLayout, so we cannot expect the exact bitmaps.
+ assertThat(expected.sameAs(actual)).isFalse()
+ }
}
private fun Layout.toBitmap(width: Int, height: Int) =
Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }!!
private fun TextInterpolator.toBitmap(width: Int, height: Int) =
- Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) } \ No newline at end of file
+ Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888).also { draw(Canvas(it)) }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
index b61fbbe1ea75..273786d74782 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/ViewHierarchyAnimatorTest.kt
@@ -207,25 +207,30 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
@Test
- fun animatesAppearingViewsFromStartToEnd() {
- // Starting GONE.
- rootView.visibility = View.GONE
- rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
- var success = ViewHierarchyAnimator.animateAddition(rootView)
- rootView.visibility = View.VISIBLE
- rootView.layout(0 /* l */, 100 /* t */, 100 /* r */, 200 /* b */)
+ fun animatesInvisibleViews() {
+ rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
+ rootView.visibility = View.INVISIBLE
+
+ val success = ViewHierarchyAnimator.animate(rootView)
+ // Change all bounds.
+ rootView.layout(0 /* l */, 15 /* t */, 70 /* r */, 80 /* b */)
assertTrue(success)
assertNotNull(rootView.getTag(R.id.tag_animator))
- checkBounds(rootView, l = 50, t = 150, r = 50, b = 150)
+ // The initial values should be those of the previous layout.
+ checkBounds(rootView, l = 10, t = 10, r = 50, b = 50)
endAnimation(rootView)
assertNull(rootView.getTag(R.id.tag_animator))
- checkBounds(rootView, l = 0, t = 100, r = 100, b = 200)
+ // The end values should be those of the latest layout.
+ checkBounds(rootView, l = 0, t = 15, r = 70, b = 80)
+ }
- // Starting INVISIBLE.
- rootView.visibility = View.INVISIBLE
+ @Test
+ fun animatesAppearingViewsFromStartToEnd() {
+ // Starting GONE.
+ rootView.visibility = View.GONE
rootView.layout(0 /* l */, 50 /* t */, 50 /* r */, 100 /* b */)
- success = ViewHierarchyAnimator.animateAddition(rootView)
+ var success = ViewHierarchyAnimator.animateAddition(rootView)
rootView.visibility = View.VISIBLE
rootView.layout(0 /* l */, 100 /* t */, 100 /* r */, 200 /* b */)
@@ -937,7 +942,7 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
}
@Test
- fun doesNotAnimateInvisibleViews() {
+ fun doesNotAnimateGoneViews() {
rootView.layout(10 /* l */, 10 /* t */, 50 /* r */, 50 /* b */)
// GONE
@@ -948,15 +953,6 @@ ViewHierarchyAnimatorTest : SysuiTestCase() {
assertFalse(success)
assertNull(rootView.getTag(R.id.tag_animator))
checkBounds(rootView, l = 0, t = 15, r = 55, b = 80)
-
- // INVISIBLE.
- rootView.visibility = View.INVISIBLE
- success = ViewHierarchyAnimator.animate(rootView)
- rootView.layout(0 /* l */, 20 /* t */, 10 /* r */, 50 /* b */)
-
- assertFalse(success)
- assertNull(rootView.getTag(R.id.tag_animator))
- checkBounds(rootView, l = 0, t = 20, r = 10, b = 50)
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index bc5a4d3d274e..bf3788e4c76a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -48,11 +48,12 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyLong
import org.mockito.Mockito.eq
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@RunWith(AndroidTestingRunner::class)
@RunWithLooper(setAsMainLooper = true)
@@ -87,7 +88,7 @@ class AuthContainerViewTest : SysuiTestCase() {
@Test
fun testNotifiesAnimatedIn() {
initializeFingerprintContainer()
- verify(callback).onDialogAnimatedIn()
+ verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
}
@Test
@@ -96,13 +97,13 @@ class AuthContainerViewTest : SysuiTestCase() {
container.dismissFromSystemServer()
waitForIdleSync()
- verify(callback, never()).onDialogAnimatedIn()
+ verify(callback, never()).onDialogAnimatedIn(anyLong())
container.addToView()
waitForIdleSync()
// attaching the view resets the state and allows this to happen again
- verify(callback).onDialogAnimatedIn()
+ verify(callback).onDialogAnimatedIn(authContainer?.requestId ?: 0L)
}
@Test
@@ -110,14 +111,17 @@ class AuthContainerViewTest : SysuiTestCase() {
val container = initializeFingerprintContainer()
waitForIdleSync()
- verify(callback).onDialogAnimatedIn()
+ val requestID = authContainer?.requestId ?: 0L
+
+ verify(callback).onDialogAnimatedIn(requestID)
container.onWindowFocusChanged(false)
waitForIdleSync()
verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq<ByteArray?>(null) /* credentialAttestation */
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(requestID)
)
assertThat(container.parent).isNull()
}
@@ -131,8 +135,9 @@ class AuthContainerViewTest : SysuiTestCase() {
waitForIdleSync()
verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
- eq<ByteArray?>(null) /* credentialAttestation */
+ eq(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(authContainer?.requestId ?: 0L)
)
assertThat(container.parent).isNull()
}
@@ -146,11 +151,13 @@ class AuthContainerViewTest : SysuiTestCase() {
waitForIdleSync()
verify(callback).onSystemEvent(
- eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL)
+ eq(BiometricConstants.BIOMETRIC_SYSTEM_EVENT_EARLY_USER_CANCEL),
+ eq(authContainer?.requestId ?: 0L)
)
verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
- eq<ByteArray?>(null) /* credentialAttestation */
+ eq(AuthDialogCallback.DISMISSED_USER_CANCELED),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(authContainer?.requestId ?: 0L)
)
assertThat(container.parent).isNull()
}
@@ -164,8 +171,9 @@ class AuthContainerViewTest : SysuiTestCase() {
waitForIdleSync()
verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
- eq<ByteArray?>(null) /* credentialAttestation */
+ eq(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(authContainer?.requestId ?: 0L)
)
assertThat(container.parent).isNull()
}
@@ -180,7 +188,7 @@ class AuthContainerViewTest : SysuiTestCase() {
)
waitForIdleSync()
- verify(callback).onTryAgainPressed()
+ verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
}
@Test
@@ -192,8 +200,9 @@ class AuthContainerViewTest : SysuiTestCase() {
waitForIdleSync()
verify(callback).onDismissed(
- eq(AuthDialogCallback.DISMISSED_ERROR),
- eq<ByteArray?>(null) /* credentialAttestation */
+ eq(AuthDialogCallback.DISMISSED_ERROR),
+ eq<ByteArray?>(null), /* credentialAttestation */
+ eq(authContainer?.requestId ?: 0L)
)
assertThat(authContainer!!.parent).isNull()
}
@@ -209,7 +218,7 @@ class AuthContainerViewTest : SysuiTestCase() {
)
waitForIdleSync()
- verify(callback).onDeviceCredentialPressed()
+ verify(callback).onDeviceCredentialPressed(authContainer?.requestId ?: 0L)
assertThat(container.hasCredentialView()).isTrue()
}
@@ -297,6 +306,16 @@ class AuthContainerViewTest : SysuiTestCase() {
}
@Test
+ fun testLayoutParams_hasDimbehindWindowFlag() {
+ val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
+ val lpFlags = layoutParams.flags
+ val lpDimAmount = layoutParams.dimAmount
+
+ assertThat((lpFlags and WindowManager.LayoutParams.FLAG_DIM_BEHIND) != 0).isTrue()
+ assertThat(lpDimAmount).isGreaterThan(0f)
+ }
+
+ @Test
fun testLayoutParams_excludesImeInsets() {
val layoutParams = AuthContainerView.getLayoutParams(windowToken, "")
assertThat((layoutParams.fitInsetsTypes and WindowInsets.Type.ime()) == 0).isTrue()
@@ -312,12 +331,12 @@ class AuthContainerViewTest : SysuiTestCase() {
container.onAuthenticationFailed(BiometricAuthenticator.TYPE_FACE, "failed")
waitForIdleSync()
- verify(callback, never()).onTryAgainPressed()
+ verify(callback, never()).onTryAgainPressed(anyLong())
container.onPointerDown()
waitForIdleSync()
- verify(callback).onTryAgainPressed()
+ verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
}
private fun initializeFingerprintContainer(
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 d948a99f8ad8..d158892e4ec5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -291,7 +291,8 @@ public class AuthControllerTest extends SysuiTestCase {
public void testSendsReasonUserCanceled_whenDismissedByUserCancel() throws Exception {
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
- null /* credentialAttestation */);
+ null, /* credentialAttestation */
+ mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_USER_CANCEL),
eq(null) /* credentialAttestation */);
@@ -301,7 +302,8 @@ public class AuthControllerTest extends SysuiTestCase {
public void testSendsReasonNegative_whenDismissedByButtonNegative() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE,
- null /* credentialAttestation */);
+ null, /* credentialAttestation */
+ mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_NEGATIVE),
eq(null) /* credentialAttestation */);
@@ -311,7 +313,8 @@ public class AuthControllerTest extends SysuiTestCase {
public void testSendsReasonConfirmed_whenDismissedByButtonPositive() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE,
- null /* credentialAttestation */);
+ null, /* credentialAttestation */
+ mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRMED),
eq(null) /* credentialAttestation */);
@@ -321,7 +324,8 @@ public class AuthControllerTest extends SysuiTestCase {
public void testSendsReasonConfirmNotRequired_whenDismissedByAuthenticated() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED,
- null /* credentialAttestation */);
+ null, /* credentialAttestation */
+ mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_BIOMETRIC_CONFIRM_NOT_REQUIRED),
eq(null) /* credentialAttestation */);
@@ -331,7 +335,8 @@ public class AuthControllerTest extends SysuiTestCase {
public void testSendsReasonError_whenDismissedByError() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_ERROR,
- null /* credentialAttestation */);
+ null, /* credentialAttestation */
+ mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_ERROR),
eq(null) /* credentialAttestation */);
@@ -341,7 +346,8 @@ public class AuthControllerTest extends SysuiTestCase {
public void testSendsReasonServerRequested_whenDismissedByServer() throws Exception {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_BY_SYSTEM_SERVER,
- null /* credentialAttestation */);
+ null, /* credentialAttestation */
+ mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_SERVER_REQUESTED),
eq(null) /* credentialAttestation */);
@@ -355,7 +361,7 @@ public class AuthControllerTest extends SysuiTestCase {
final byte[] credentialAttestation = generateRandomHAT();
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
- credentialAttestation);
+ credentialAttestation, mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
AdditionalMatchers.aryEq(credentialAttestation));
@@ -531,7 +537,7 @@ public class AuthControllerTest extends SysuiTestCase {
final byte[] credentialAttestation = generateRandomHAT();
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_CREDENTIAL_AUTHENTICATED,
- credentialAttestation);
+ credentialAttestation, mAuthController.mCurrentDialog.getRequestId());
verify(mReceiver).onDialogDismissed(
eq(BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
AdditionalMatchers.aryEq(credentialAttestation));
@@ -640,17 +646,19 @@ public class AuthControllerTest extends SysuiTestCase {
@Test
public void testDoesNotCrash_whenTryAgainPressedAfterDismissal() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+ final long requestID = mAuthController.mCurrentDialog.getRequestId();
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
- null /* credentialAttestation */);
- mAuthController.onTryAgainPressed();
+ null, /* credentialAttestation */requestID);
+ mAuthController.onTryAgainPressed(requestID);
}
@Test
public void testDoesNotCrash_whenDeviceCredentialPressedAfterDismissal() {
showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
+ final long requestID = mAuthController.mCurrentDialog.getRequestId();
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
- null /* credentialAttestation */);
- mAuthController.onDeviceCredentialPressed();
+ null /* credentialAttestation */, requestID);
+ mAuthController.onDeviceCredentialPressed(requestID);
}
@Test
@@ -708,7 +716,8 @@ public class AuthControllerTest extends SysuiTestCase {
// WHEN dialog is shown and then dismissed
showDialog(new int[]{1} /* sensorIds */, false /* credentialAllowed */);
mAuthController.onDismissed(AuthDialogCallback.DISMISSED_USER_CANCELED,
- null /* credentialAttestation */);
+ null /* credentialAttestation */,
+ mAuthController.mCurrentDialog.getRequestId());
// THEN callback should be received
verify(callback).onBiometricPromptDismissed();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
new file mode 100644
index 000000000000..adb10f01b5e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+include /services/core/java/com/android/server/biometrics/OWNERS
+beverlyt@google.com
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
index dec2b82ed88f..6157ccbd597a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt
@@ -62,7 +62,6 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers.eq
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.any
import org.mockito.Mockito.anyFloat
import org.mockito.Mockito.anyInt
@@ -72,6 +71,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
import org.mockito.junit.MockitoJUnit
private const val DISPLAY_ID = 2
@@ -126,15 +126,15 @@ class SidefpsControllerTest : SysuiTestCase() {
context.addMockSystemService(DisplayManager::class.java, displayManager)
context.addMockSystemService(WindowManager::class.java, windowManager)
- `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
- `when`(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+ whenEver(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sidefpsView)
+ whenEver(sidefpsView.findViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
.thenReturn(mock(LottieAnimationView::class.java))
with(mock(ViewPropertyAnimator::class.java)) {
- `when`(sidefpsView.animate()).thenReturn(this)
- `when`(alpha(anyFloat())).thenReturn(this)
- `when`(setStartDelay(anyLong())).thenReturn(this)
- `when`(setDuration(anyLong())).thenReturn(this)
- `when`(setListener(any())).thenAnswer {
+ whenEver(sidefpsView.animate()).thenReturn(this)
+ whenEver(alpha(anyFloat())).thenReturn(this)
+ whenEver(setStartDelay(anyLong())).thenReturn(this)
+ whenEver(setDuration(anyLong())).thenReturn(this)
+ whenEver(setListener(any())).thenAnswer {
(it.arguments[0] as Animator.AnimatorListener)
.onAnimationEnd(mock(Animator::class.java))
this
@@ -177,7 +177,7 @@ class SidefpsControllerTest : SysuiTestCase() {
displayBounds = Rect(0, 0, displayWidth, displayHeight)
var locations = listOf(sensorLocation)
- `when`(fingerprintManager.sensorPropertiesInternal).thenReturn(
+ whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(
listOf(
FingerprintSensorPropertiesInternal(
SENSOR_ID,
@@ -196,12 +196,12 @@ class SidefpsControllerTest : SysuiTestCase() {
displayInfo.initInfo()
val dmGlobal = mock(DisplayManagerGlobal::class.java)
val display = Display(dmGlobal, DISPLAY_ID, displayInfo, DEFAULT_DISPLAY_ADJUSTMENTS)
- `when`(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
- `when`(windowManager.defaultDisplay).thenReturn(display)
- `when`(windowManager.maximumWindowMetrics).thenReturn(
+ whenEver(dmGlobal.getDisplayInfo(eq(DISPLAY_ID))).thenReturn(displayInfo)
+ whenEver(windowManager.defaultDisplay).thenReturn(display)
+ whenEver(windowManager.maximumWindowMetrics).thenReturn(
WindowMetrics(displayBounds, WindowInsets.CONSUMED)
)
- `when`(windowManager.currentWindowMetrics).thenReturn(
+ whenEver(windowManager.currentWindowMetrics).thenReturn(
WindowMetrics(displayBounds, windowInsets)
)
@@ -277,13 +277,13 @@ class SidefpsControllerTest : SysuiTestCase() {
@Test
fun testShowsForMostSettings() = testWithDisplay {
- `when`(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
+ whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpEnrollTask()))
testIgnoredFor(REASON_AUTH_SETTINGS, ignored = false)
}
@Test
fun testIgnoredForVerySpecificSettings() = testWithDisplay {
- `when`(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
+ whenEver(activityTaskManager.getTasks(anyInt())).thenReturn(listOf(fpSettingsTask()))
testIgnoredFor(REASON_AUTH_SETTINGS)
}
@@ -424,6 +424,20 @@ class SidefpsControllerTest : SysuiTestCase() {
assertThat(overlayViewParamsCaptor.value.x).isEqualTo(displayWidth - boundsWidth)
assertThat(overlayViewParamsCaptor.value.y).isEqualTo(sensorLocation.sensorLocationY)
}
+
+ @Test
+ fun hasSideFpsSensor_withSensorProps_returnsTrue() = testWithDisplay {
+ // By default all those tests assume the side fps sensor is available.
+
+ assertThat(fingerprintManager.hasSideFpsSensor()).isTrue()
+ }
+
+ @Test
+ fun hasSideFpsSensor_withoutSensorProps_returnsFalse() {
+ whenEver(fingerprintManager.sensorPropertiesInternal).thenReturn(null)
+
+ assertThat(fingerprintManager.hasSideFpsSensor()).isFalse()
+ }
}
private fun insetsForSmallNavbar() = insetsWithBottom(60)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 98f9e17cc59c..d7a0a0ae974d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -746,12 +746,12 @@ public class UdfpsControllerTest extends SysuiTestCase {
anyString(),
any(),
eq("udfps-onStart-click"),
- eq(UdfpsController.VIBRATION_ATTRIBUTES));
+ eq(UdfpsController.UDFPS_VIBRATION_ATTRIBUTES));
// THEN make sure vibration attributes has so that it always will play the haptic,
// even in battery saver mode
assertEquals(VibrationAttributes.USAGE_COMMUNICATION_REQUEST,
- UdfpsController.VIBRATION_ATTRIBUTES.getUsage());
+ UdfpsController.UDFPS_VIBRATION_ATTRIBUTES.getUsage());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index 0fdd9054e4bc..b61bda8edd10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -21,6 +21,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
@@ -468,6 +469,40 @@ public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
verify(mView).setUnpausedAlpha(255);
}
+ @Test
+ public void testUpdatePanelExpansion_pauseAuth() {
+ // GIVEN view is attached + on the keyguard
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to hide
+ when(mView.getUnpausedAlpha()).thenReturn(0);
+ updateStatusBarExpansion(0f, false);
+
+ // THEN pause auth is updated to PAUSE
+ verify(mView, atLeastOnce()).setPauseAuth(true);
+ }
+
+ @Test
+ public void testUpdatePanelExpansion_unpauseAuth() {
+ // GIVEN view is attached + on the keyguard + panel expansion is 0f
+ mController.onViewAttached();
+ captureStatusBarStateListeners();
+ captureStatusBarExpansionListeners();
+ sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+ reset(mView);
+
+ // WHEN panelViewExpansion changes to expanded
+ when(mView.getUnpausedAlpha()).thenReturn(255);
+ updateStatusBarExpansion(1f, true);
+
+ // THEN pause auth is updated to NOT pause
+ verify(mView, atLeastOnce()).setPauseAuth(false);
+ }
+
private void sendStatusBarStateChanged(int statusBarState) {
mStatusBarStateListener.onStateChanged(statusBarState);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
new file mode 100644
index 000000000000..a61cecbabf30
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogTest.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+
+import androidx.test.filters.SmallTest;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.android.internal.logging.UiEventLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.media.dialog.MediaOutputDialogFactory;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+public class BroadcastDialogTest extends SysuiTestCase {
+
+ private static final String SWITCH_APP = "Music";
+ private static final String TEST_PACKAGE = "com.google.android.apps.nbu.files";
+ private BroadcastDialog mBroadcastDialog;
+ private View mDialogView;
+ private TextView mSubTitle;
+ private Button mChangeOutputButton;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mBroadcastDialog = new BroadcastDialog(mContext, mock(MediaOutputDialogFactory.class),
+ SWITCH_APP, TEST_PACKAGE, mock(UiEventLogger.class));
+ mBroadcastDialog.show();
+ mDialogView = mBroadcastDialog.mDialogView;
+ }
+
+ @After
+ public void tearDown() {
+ mBroadcastDialog.dismiss();
+ }
+
+ @Test
+ public void onCreate_withCurrentApp_checkSwitchAppContent() {
+ mSubTitle = mDialogView.requireViewById(R.id.dialog_subtitle);
+
+ assertThat(mSubTitle.getText()).isEqualTo(
+ mContext.getString(R.string.bt_le_audio_broadcast_dialog_sub_title, SWITCH_APP));
+ }
+
+ @Test
+ public void onClick_withChangeOutput_dismissBroadcastDialog() {
+ mChangeOutputButton = mDialogView.requireViewById(R.id.change_output);
+ mChangeOutputButton.performClick();
+
+ assertThat(mBroadcastDialog.isShowing()).isFalse();
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
new file mode 100644
index 000000000000..ca94ea826782
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/camera/CameraGestureHelperTest.kt
@@ -0,0 +1,311 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.camera
+
+import android.app.ActivityManager
+import android.app.IActivityTaskManager
+import android.content.ComponentName
+import android.content.ContentResolver
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.PackageManager
+import android.content.pm.ResolveInfo
+import androidx.test.filters.SmallTest
+import com.android.systemui.ActivityIntentHelper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.KotlinArgumentCaptor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class CameraGestureHelperTest : SysuiTestCase() {
+
+ @Mock
+ lateinit var centralSurfaces: CentralSurfaces
+ @Mock
+ lateinit var keyguardStateController: KeyguardStateController
+ @Mock
+ lateinit var packageManager: PackageManager
+ @Mock
+ lateinit var activityManager: ActivityManager
+ @Mock
+ lateinit var activityStarter: ActivityStarter
+ @Mock
+ lateinit var activityIntentHelper: ActivityIntentHelper
+ @Mock
+ lateinit var activityTaskManager: IActivityTaskManager
+ @Mock
+ lateinit var cameraIntents: CameraIntentsWrapper
+ @Mock
+ lateinit var contentResolver: ContentResolver
+
+ private lateinit var underTest: CameraGestureHelper
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(cameraIntents.getSecureCameraIntent()).thenReturn(
+ Intent(CameraIntents.DEFAULT_SECURE_CAMERA_INTENT_ACTION)
+ )
+ whenever(cameraIntents.getInsecureCameraIntent()).thenReturn(
+ Intent(CameraIntents.DEFAULT_INSECURE_CAMERA_INTENT_ACTION)
+ )
+
+ prepare()
+
+ underTest = CameraGestureHelper(
+ context = mock(),
+ centralSurfaces = centralSurfaces,
+ keyguardStateController = keyguardStateController,
+ packageManager = packageManager,
+ activityManager = activityManager,
+ activityStarter = activityStarter,
+ activityIntentHelper = activityIntentHelper,
+ activityTaskManager = activityTaskManager,
+ cameraIntents = cameraIntents,
+ contentResolver = contentResolver,
+ uiExecutor = MoreExecutors.directExecutor(),
+ )
+ }
+
+ /**
+ * Prepares for tests by setting up the various mocks to emulate a specific device state.
+ *
+ * <p>Safe to call multiple times in a single test (for example, once in [setUp] and once in the
+ * actual test case).
+ *
+ * @param isCameraAllowedByAdmin Whether the device administrator allows use of the camera app
+ * @param installedCameraAppCount The number of installed camera apps on the device
+ * @param isUsingSecureScreenLockOption Whether the user-controlled setting for Screen Lock is
+ * set with a "secure" option that requires the user to provide some secret/credentials to be
+ * able to unlock the device, for example "Face Unlock", "PIN", or "Password". Examples of
+ * non-secure options are "None" and "Swipe"
+ * @param isCameraActivityRunningOnTop Whether the camera activity is running at the top of the
+ * most recent/current task of activities
+ * @param isTaskListEmpty Whether there are no active activity tasks at all. Note that this is
+ * treated as `false` if [isCameraActivityRunningOnTop] is set to `true`
+ */
+ private fun prepare(
+ isCameraAllowedByAdmin: Boolean = true,
+ installedCameraAppCount: Int = 1,
+ isUsingSecureScreenLockOption: Boolean = true,
+ isCameraActivityRunningOnTop: Boolean = false,
+ isTaskListEmpty: Boolean = false,
+ ) {
+ whenever(centralSurfaces.isCameraAllowedByAdmin).thenReturn(isCameraAllowedByAdmin)
+
+ whenever(activityIntentHelper.wouldLaunchResolverActivity(any(), anyInt()))
+ .thenReturn(installedCameraAppCount > 1)
+
+ whenever(keyguardStateController.isMethodSecure).thenReturn(isUsingSecureScreenLockOption)
+ whenever(keyguardStateController.canDismissLockScreen())
+ .thenReturn(!isUsingSecureScreenLockOption)
+
+ if (installedCameraAppCount >= 1) {
+ val resolveInfo = ResolveInfo().apply {
+ this.activityInfo = ActivityInfo().apply {
+ packageName = CAMERA_APP_PACKAGE_NAME
+ }
+ }
+ whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
+ resolveInfo
+ )
+ } else {
+ whenever(packageManager.resolveActivityAsUser(any(), anyInt(), anyInt())).thenReturn(
+ null
+ )
+ }
+
+ when {
+ isCameraActivityRunningOnTop -> {
+ val runningTaskInfo = ActivityManager.RunningTaskInfo().apply {
+ topActivity = ComponentName(CAMERA_APP_PACKAGE_NAME, "cameraActivity")
+ }
+ whenever(activityManager.getRunningTasks(anyInt())).thenReturn(
+ listOf(
+ runningTaskInfo
+ )
+ )
+ }
+ isTaskListEmpty -> {
+ whenever(activityManager.getRunningTasks(anyInt())).thenReturn(emptyList())
+ }
+ else -> {
+ whenever(activityManager.getRunningTasks(anyInt())).thenReturn(listOf())
+ }
+ }
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - status bar state is keyguard - returns true`() {
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isTrue()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - state is shade-locked - returns true`() {
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE_LOCKED)).isTrue()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - state is keyguard - camera activity on top - returns true`() {
+ prepare(isCameraActivityRunningOnTop = true)
+
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isTrue()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - state is shade-locked - camera activity on top - true`() {
+ prepare(isCameraActivityRunningOnTop = true)
+
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE_LOCKED)).isTrue()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - not allowed by admin - returns false`() {
+ prepare(isCameraAllowedByAdmin = false)
+
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isFalse()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - intent does not resolve to any app - returns false`() {
+ prepare(installedCameraAppCount = 0)
+
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.KEYGUARD)).isFalse()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - state is shade - no running tasks - returns true`() {
+ prepare(isCameraActivityRunningOnTop = false, isTaskListEmpty = true)
+
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isTrue()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - state is shade - camera activity on top - returns false`() {
+ prepare(isCameraActivityRunningOnTop = true)
+
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isFalse()
+ }
+
+ @Test
+ fun `canCameraGestureBeLaunched - state is shade - camera activity not on top - true`() {
+ assertThat(underTest.canCameraGestureBeLaunched(StatusBarState.SHADE)).isTrue()
+ }
+
+ @Test
+ fun `launchCamera - only one camera app installed - using secure screen lock option`() {
+ val source = 1337
+
+ underTest.launchCamera(source)
+
+ assertActivityStarting(isSecure = true, source = source)
+ }
+
+ @Test
+ fun `launchCamera - only one camera app installed - using non-secure screen lock option`() {
+ prepare(isUsingSecureScreenLockOption = false)
+ val source = 1337
+
+ underTest.launchCamera(source)
+
+ assertActivityStarting(isSecure = false, source = source)
+ }
+
+ @Test
+ fun `launchCamera - multiple camera apps installed - using secure screen lock option`() {
+ prepare(installedCameraAppCount = 2)
+ val source = 1337
+
+ underTest.launchCamera(source)
+
+ assertActivityStarting(
+ isSecure = true,
+ source = source,
+ moreThanOneCameraAppInstalled = true
+ )
+ }
+
+ @Test
+ fun `launchCamera - multiple camera apps installed - using non-secure screen lock option`() {
+ prepare(
+ isUsingSecureScreenLockOption = false,
+ installedCameraAppCount = 2,
+ )
+ val source = 1337
+
+ underTest.launchCamera(source)
+
+ assertActivityStarting(
+ isSecure = false,
+ moreThanOneCameraAppInstalled = true,
+ source = source
+ )
+ }
+
+ private fun assertActivityStarting(
+ isSecure: Boolean,
+ source: Int,
+ moreThanOneCameraAppInstalled: Boolean = false,
+ ) {
+ val intentCaptor = KotlinArgumentCaptor(Intent::class.java)
+ if (isSecure && !moreThanOneCameraAppInstalled) {
+ verify(activityTaskManager).startActivityAsUser(
+ any(),
+ any(),
+ any(),
+ intentCaptor.capture(),
+ any(),
+ any(),
+ any(),
+ anyInt(),
+ anyInt(),
+ any(),
+ any(),
+ anyInt()
+ )
+ } else {
+ verify(activityStarter).startActivity(intentCaptor.capture(), eq(false))
+ }
+ val intent = intentCaptor.value
+
+ assertThat(CameraIntents.isSecureCameraIntent(intent)).isEqualTo(isSecure)
+ assertThat(intent.getIntExtra(CameraGestureHelper.EXTRA_CAMERA_LAUNCH_SOURCE, -1))
+ .isEqualTo(source)
+ }
+
+ companion object {
+ private const val CAMERA_APP_PACKAGE_NAME = "cameraAppPackageName"
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
index b4cae38d8b6e..6978490a1252 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/charging/WiredChargingRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/charging/WiredChargingRippleControllerTest.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.charging
+package com.android.systemui.charging
import android.testing.AndroidTestingRunner
import android.view.View
@@ -24,6 +24,7 @@ import com.android.internal.logging.UiEventLogger
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
+import com.android.systemui.ripple.RippleView
import com.android.systemui.statusbar.commandline.CommandRegistry
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.statusbar.policy.ConfigurationController
@@ -50,7 +51,7 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
@Mock private lateinit var batteryController: BatteryController
@Mock private lateinit var featureFlags: FeatureFlags
@Mock private lateinit var configurationController: ConfigurationController
- @Mock private lateinit var rippleView: ChargingRippleView
+ @Mock private lateinit var rippleView: RippleView
@Mock private lateinit var windowManager: WindowManager
@Mock private lateinit var uiEventLogger: UiEventLogger
private val systemClock = FakeSystemClock()
@@ -74,9 +75,9 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
// Verify ripple added to window manager.
captor.value.onBatteryLevelChanged(
- 0 /* unusedBatteryLevel */,
- true /* plugged in */,
- false /* charging */)
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
val attachListenerCaptor =
ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
verify(rippleView).addOnAttachStateChangeListener(attachListenerCaptor.capture())
@@ -144,4 +145,22 @@ class WiredChargingRippleControllerTest : SysuiTestCase() {
// Verify that ripple is triggered.
verify(rippleView).addOnAttachStateChangeListener(ArgumentMatchers.any())
}
+
+ @Test
+ fun testRipple_whenDocked_doesNotPlayRipple() {
+ `when`(batteryController.isChargingSourceDock).thenReturn(true)
+ val captor = ArgumentCaptor
+ .forClass(BatteryController.BatteryStateChangeCallback::class.java)
+ verify(batteryController).addCallback(captor.capture())
+
+ captor.value.onBatteryLevelChanged(
+ /* unusedBatteryLevel= */ 0,
+ /* plugged in= */ true,
+ /* charging= */ false)
+
+ val attachListenerCaptor =
+ ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java)
+ verify(rippleView, never()).addOnAttachStateChangeListener(attachListenerCaptor.capture())
+ verify(windowManager, never()).addView(eq(rippleView), any<WindowManager.LayoutParams>())
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index 3c7ea4fe6f35..c31fd828c730 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -37,9 +37,14 @@ import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.ui.ControlsUiController
import com.android.systemui.dump.DumpManager
+import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.Optional
+import java.util.function.Consumer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
@@ -50,20 +55,20 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
+import org.mockito.ArgumentMatchers.anyString
import org.mockito.Captor
import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.function.Consumer
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -85,6 +90,8 @@ class ControlsControllerImplTest : SysuiTestCase() {
private lateinit var listingController: ControlsListingController
@Mock(stubOnly = true)
private lateinit var userTracker: UserTracker
+ @Mock
+ private lateinit var userFileManager: UserFileManager
@Captor
private lateinit var structureInfoCaptor: ArgumentCaptor<StructureInfo>
@@ -153,6 +160,9 @@ class ControlsControllerImplTest : SysuiTestCase() {
canceller = DidRunRunnable()
`when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+ `when`(userFileManager.getFile(anyString(), anyInt())).thenReturn(mock(File::class.java))
+ `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+ .thenReturn(context.getSharedPreferences("test", Context.MODE_PRIVATE))
controller = ControlsControllerImpl(
wrapper,
@@ -161,6 +171,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
bindingController,
listingController,
broadcastDispatcher,
+ userFileManager,
Optional.of(persistenceWrapper),
mock(DumpManager::class.java),
userTracker
@@ -217,6 +228,7 @@ class ControlsControllerImplTest : SysuiTestCase() {
bindingController,
listingController,
broadcastDispatcher,
+ userFileManager,
Optional.of(persistenceWrapper),
mock(DumpManager::class.java),
userTracker
@@ -911,6 +923,14 @@ class ControlsControllerImplTest : SysuiTestCase() {
assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
}
+
+ @Test
+ fun testUserStructure() {
+ val userStructure = UserStructure(context, context.user, userFileManager)
+ verify(userFileManager, times(2))
+ .getFile(ControlsFavoritePersistenceWrapper.FILE_NAME, context.user.identifier)
+ assertThat(userStructure.file).isNotNull()
+ }
}
private class DidRunRunnable() : Runnable {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
new file mode 100644
index 000000000000..1e4a9e42cce1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/AppAdapterTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.controls.management
+
+import android.content.ComponentName
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.settingslib.core.lifecycle.Lifecycle
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsServiceInfo
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class AppAdapterTest : SysuiTestCase() {
+ private val fakeSystemClock = FakeSystemClock()
+ private val backgroundExecutor = FakeExecutor(fakeSystemClock)
+ private val uiExecutor = FakeExecutor(fakeSystemClock)
+ @Mock lateinit var lifecycle: Lifecycle
+ @Mock lateinit var controlsListingController: ControlsListingController
+ @Mock lateinit var layoutInflater: LayoutInflater
+ @Mock lateinit var onAppSelected: (ComponentName?) -> Unit
+ @Mock lateinit var favoritesRenderer: FavoritesRenderer
+ val resources: Resources = context.resources
+ lateinit var adapter: AppAdapter
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ adapter = AppAdapter(backgroundExecutor,
+ uiExecutor,
+ lifecycle,
+ controlsListingController,
+ layoutInflater,
+ onAppSelected,
+ favoritesRenderer,
+ resources)
+ }
+
+ @Test
+ fun testOnServicesUpdated_nullLoadLabel() {
+ val captor = ArgumentCaptor
+ .forClass(ControlsListingController.ControlsListingCallback::class.java)
+ val controlsServiceInfo = mock(ControlsServiceInfo::class.java)
+ val serviceInfo = listOf(controlsServiceInfo)
+ `when`(controlsServiceInfo.loadLabel()).thenReturn(null)
+ verify(controlsListingController).observe(any(Lifecycle::class.java), captor.capture())
+
+ captor.value.onServicesUpdated(serviceInfo)
+ backgroundExecutor.runAllReady()
+ uiExecutor.runAllReady()
+
+ assertThat(adapter.itemCount).isEqualTo(serviceInfo.size)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
index 3340f2fd5749..abe7ae1a0b1c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java
@@ -21,7 +21,9 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_DOCKED;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING_BRIGHT;
import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_SUSPEND_TRIGGERS;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
@@ -39,6 +41,8 @@ import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import android.app.UiModeManager;
+import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
import android.testing.UiThreadTest;
@@ -50,7 +54,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.util.wakelock.WakeLockFake;
import org.junit.Before;
@@ -70,9 +73,12 @@ public class DozeMachineTest extends SysuiTestCase {
private WakefulnessLifecycle mWakefulnessLifecycle;
@Mock
private DozeLog mDozeLog;
- @Mock private DockManager mDockManager;
+ @Mock
+ private DockManager mDockManager;
@Mock
private DozeHost mHost;
+ @Mock
+ private UiModeManager mUiModeManager;
private DozeServiceFake mServiceFake;
private WakeLockFake mWakeLockFake;
private AmbientDisplayConfiguration mConfigMock;
@@ -89,7 +95,7 @@ public class DozeMachineTest extends SysuiTestCase {
when(mDockManager.isHidden()).thenReturn(false);
mMachine = new DozeMachine(mServiceFake, mConfigMock, mWakeLockFake,
- mWakefulnessLifecycle, mock(BatteryController.class), mDozeLog, mDockManager,
+ mWakefulnessLifecycle, mUiModeManager, mDozeLog, mDockManager,
mHost, new DozeMachine.Part[]{mPartMock});
}
@@ -462,4 +468,64 @@ public class DozeMachineTest extends SysuiTestCase {
assertEquals(Display.STATE_ON, DOZE_REQUEST_PULSE.screenState(dozeParameters));
}
+
+ @Test
+ public void testTransitionToInitialized_carModeIsEnabled() {
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mMachine.requestState(INITIALIZED);
+
+ verify(mPartMock).transitionTo(UNINITIALIZED, INITIALIZED);
+ verify(mPartMock).transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
+ assertEquals(DOZE_SUSPEND_TRIGGERS, mMachine.getState());
+ }
+
+ @Test
+ public void testTransitionToFinish_carModeIsEnabled() {
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(FINISH);
+
+ assertEquals(FINISH, mMachine.getState());
+ }
+
+ @Test
+ public void testDozeToDozeSuspendTriggers_carModeIsEnabled() {
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(DOZE);
+
+ assertEquals(DOZE_SUSPEND_TRIGGERS, mMachine.getState());
+ }
+
+ @Test
+ public void testDozeAoDToDozeSuspendTriggers_carModeIsEnabled() {
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(DOZE_AOD);
+
+ assertEquals(DOZE_SUSPEND_TRIGGERS, mMachine.getState());
+ }
+
+ @Test
+ public void testDozePulsingBrightDozeSuspendTriggers_carModeIsEnabled() {
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(DOZE_PULSING_BRIGHT);
+
+ assertEquals(DOZE_SUSPEND_TRIGGERS, mMachine.getState());
+ }
+
+ @Test
+ public void testDozeAodDockedDozeSuspendTriggers_carModeIsEnabled() {
+ when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
+ mMachine.requestState(INITIALIZED);
+ mMachine.requestState(DOZE_AOD_DOCKED);
+
+ assertEquals(DOZE_SUSPEND_TRIGGERS, mMachine.getState());
+ }
+
+ @Test
+ public void testDozeSuppressTriggers_screenState() {
+ assertEquals(Display.STATE_OFF, DOZE_SUSPEND_TRIGGERS.screenState(null));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
index 2e7b88d5fd38..03827dab0c96 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeScreenBrightnessTest.java
@@ -24,6 +24,7 @@ import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD_PAUSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSE_DONE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_PULSING;
import static com.android.systemui.doze.DozeMachine.State.DOZE_REQUEST_PULSE;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_SUSPEND_TRIGGERS;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
@@ -182,6 +183,21 @@ public class DozeScreenBrightnessTest extends SysuiTestCase {
}
@Test
+ public void dozeSuspendTriggers_doesNotUseLightSensor() {
+ // GIVEN the device is DOZE and the display state changes to ON
+ mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
+ mScreen.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
+ waitForSensorManager();
+
+ // WHEN new sensor event sent
+ mSensor.sendSensorEvent(3);
+
+ // THEN brightness is NOT changed, it's set to the default brightness
+ assertNotSame(3, mServiceFake.screenBrightness);
+ assertEquals(DEFAULT_BRIGHTNESS, mServiceFake.screenBrightness);
+ }
+
+ @Test
public void aod_usesLightSensor() {
// GIVEN the device is DOZE_AOD and the display state changes to ON
mScreen.transitionTo(UNINITIALIZED, INITIALIZED);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
index aa0a909622bb..0f29dcd5a939 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeSuppressorTest.java
@@ -16,19 +16,26 @@
package com.android.systemui.doze;
+import static android.app.UiModeManager.ACTION_ENTER_CAR_MODE;
+import static android.app.UiModeManager.ACTION_EXIT_CAR_MODE;
+
import static com.android.systemui.doze.DozeMachine.State.DOZE;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.DOZE_SUSPEND_TRIGGERS;
import static com.android.systemui.doze.DozeMachine.State.FINISH;
import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
-import static org.mockito.Matchers.anyObject;
+import static org.hamcrest.Matchers.containsInAnyOrder;
+import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.UiModeManager;
import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.hardware.display.AmbientDisplayConfiguration;
import android.testing.AndroidTestingRunner;
@@ -77,7 +84,10 @@ public class DozeSuppressorTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor;
+ @Captor
+ private ArgumentCaptor<IntentFilter> mIntentFilterCaptor;
private BroadcastReceiver mBroadcastReceiver;
+ private IntentFilter mIntentFilter;
@Captor
private ArgumentCaptor<DozeHost.Callback> mDozeHostCaptor;
@@ -122,15 +132,59 @@ public class DozeSuppressorTest extends SysuiTestCase {
}
@Test
- public void testEndDoze_carMode() {
+ public void testSuspendTriggersDoze_carMode() {
// GIVEN car mode
when(mUiModeManager.getCurrentModeType()).thenReturn(Configuration.UI_MODE_TYPE_CAR);
// WHEN dozing begins
mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
- // THEN doze immediately ends
- verify(mDozeMachine).requestState(FINISH);
+ // THEN doze continues with all doze triggers disabled.
+ verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
+ }
+
+ @Test
+ public void testSuspendTriggersDoze_enterCarMode() {
+ // GIVEN currently dozing
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+ captureBroadcastReceiver();
+ mDozeSuppressor.transitionTo(INITIALIZED, DOZE);
+
+ // WHEN car mode entered
+ mBroadcastReceiver.onReceive(null, new Intent(ACTION_ENTER_CAR_MODE));
+
+ // THEN doze continues with all doze triggers disabled.
+ verify(mDozeMachine).requestState(DOZE_SUSPEND_TRIGGERS);
+ }
+
+ @Test
+ public void testDozeResume_exitCarMode() {
+ // GIVEN currently suspended, with AOD not enabled
+ when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(false);
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+ captureBroadcastReceiver();
+ mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
+
+ // WHEN exiting car mode
+ mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+
+ // THEN doze is resumed
+ verify(mDozeMachine).requestState(DOZE);
+ }
+
+ @Test
+ public void testDozeAoDResume_exitCarMode() {
+ // GIVEN currently suspended, with AOD not enabled
+ when(mConfig.alwaysOnEnabled(anyInt())).thenReturn(true);
+ mDozeSuppressor.transitionTo(UNINITIALIZED, INITIALIZED);
+ captureBroadcastReceiver();
+ mDozeSuppressor.transitionTo(INITIALIZED, DOZE_SUSPEND_TRIGGERS);
+
+ // WHEN exiting car mode
+ mBroadcastReceiver.onReceive(null, new Intent(ACTION_EXIT_CAR_MODE));
+
+ // THEN doze AOD is resumed
+ verify(mDozeMachine).requestState(DOZE_AOD);
}
@Test
@@ -225,7 +279,11 @@ public class DozeSuppressorTest extends SysuiTestCase {
private void captureBroadcastReceiver() {
verify(mBroadcastDispatcher).registerReceiver(mBroadcastReceiverCaptor.capture(),
- anyObject());
+ mIntentFilterCaptor.capture());
mBroadcastReceiver = mBroadcastReceiverCaptor.getValue();
+ mIntentFilter = mIntentFilterCaptor.getValue();
+ assertEquals(2, mIntentFilter.countActions());
+ org.hamcrest.MatcherAssert.assertThat(() -> mIntentFilter.actionsIterator(),
+ containsInAnyOrder(ACTION_ENTER_CAR_MODE, ACTION_EXIT_CAR_MODE));
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
index 4eeb4acf18b8..01a1a3718765 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java
@@ -17,6 +17,8 @@
package com.android.systemui.doze;
import static com.android.systemui.doze.DozeMachine.State.DOZE_AOD;
+import static com.android.systemui.doze.DozeMachine.State.INITIALIZED;
+import static com.android.systemui.doze.DozeMachine.State.UNINITIALIZED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -133,7 +135,7 @@ public class DozeTriggersTest extends SysuiTestCase {
ArgumentCaptor<DozeHost.Callback> captor = ArgumentCaptor.forClass(DozeHost.Callback.class);
doAnswer(invocation -> null).when(mHost).addCallback(captor.capture());
- mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ mTriggers.transitionTo(UNINITIALIZED, DozeMachine.State.INITIALIZED);
mTriggers.transitionTo(DozeMachine.State.INITIALIZED, DozeMachine.State.DOZE);
clearInvocations(mMachine);
@@ -192,8 +194,21 @@ public class DozeTriggersTest extends SysuiTestCase {
}
@Test
+ public void transitionToDozeSuspendTriggers_disablesAllCallbacks() {
+ mTriggers.transitionTo(UNINITIALIZED, INITIALIZED);
+ when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+
+ mTriggers.transitionTo(DozeMachine.State.INITIALIZED,
+ DozeMachine.State.DOZE_SUSPEND_TRIGGERS);
+
+ verify(mDockManager).removeListener(any());
+ verify(mBroadcastDispatcher).unregisterReceiver(any());
+ verify(mHost).removeCallback(any());
+ }
+
+ @Test
public void testDockEventListener_registerAndUnregister() {
- mTriggers.transitionTo(DozeMachine.State.UNINITIALIZED, DozeMachine.State.INITIALIZED);
+ mTriggers.transitionTo(UNINITIALIZED, DozeMachine.State.INITIALIZED);
verify(mDockManager).addListener(any());
mTriggers.transitionTo(DozeMachine.State.DOZE, DozeMachine.State.FINISH);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
index e5a75e231f8d..49cdfa72f344 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeUiTest.java
@@ -86,7 +86,7 @@ public class DozeUiTest extends SysuiTestCase {
mHandler = mHandlerThread.getThreadHandler();
mDozeUi = new DozeUi(mContext, mAlarmManager, mWakeLock, mHost, mHandler,
- mDozeParameters, mKeyguardUpdateMonitor, mStatusBarStateController, mDozeLog);
+ mDozeParameters, mStatusBarStateController, mDozeLog);
mDozeUi.setDozeMachine(mMachine);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
index b7de6c15459c..25d4c472dc63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayNotificationCountProviderTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.dreams;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -48,6 +49,8 @@ public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
@Mock
StatusBarNotification mNotification2;
@Mock
+ StatusBarNotification mNotification3;
+ @Mock
NotificationListenerService.RankingMap mRankingMap;
private DreamOverlayNotificationCountProvider mProvider;
@@ -58,6 +61,8 @@ public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
when(mNotification1.getKey()).thenReturn("key1");
when(mNotification2.getKey()).thenReturn("key2");
+ when(mNotification3.getKey()).thenReturn("key3");
+ when(mNotification3.isOngoing()).thenReturn(true);
final StatusBarNotification[] notifications = {mNotification1};
when(mNotificationListener.getActiveNotifications()).thenReturn(notifications);
@@ -83,4 +88,13 @@ public class DreamOverlayNotificationCountProviderTest extends SysuiTestCase {
handlerArgumentCaptor.getValue().onNotificationRemoved(mNotification1, mRankingMap);
verify(mCallback).onNotificationCountChanged(0);
}
+
+ @Test
+ public void testPostingOngoingNotificationDoesNotCallCallbackWithNotificationCount() {
+ final ArgumentCaptor<NotificationHandler> handlerArgumentCaptor =
+ ArgumentCaptor.forClass(NotificationHandler.class);
+ verify(mNotificationListener).addNotificationHandler(handlerArgumentCaptor.capture());
+ handlerArgumentCaptor.getValue().onNotificationPosted(mNotification3, mRankingMap);
+ verify(mCallback, never()).onNotificationCountChanged(2);
+ }
}
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 fb64c7b58aac..2adf2857a385 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -207,4 +207,15 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
assertThat(complications.contains(weatherComplication)).isFalse();
}
}
+
+ @Test
+ public void testComplicationWithNoTypeNotFiltered() {
+ final Complication complication = Mockito.mock(Complication.class);
+ final DreamOverlayStateController stateController =
+ new DreamOverlayStateController(mExecutor);
+ stateController.addComplication(complication);
+ mExecutor.runAllReady();
+ assertThat(stateController.getComplications(true).contains(complication))
+ .isTrue();
+ }
}
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 3c28d48c2eb5..60e5a9423c61 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -57,6 +57,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import java.util.Optional;
import java.util.concurrent.Executor;
@SmallTest
@@ -115,7 +116,7 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
mNextAlarmController,
mDateFormatUtil,
mSensorPrivacyController,
- mDreamOverlayNotificationCountProvider,
+ Optional.of(mDreamOverlayNotificationCountProvider),
mZenModeController,
mStatusBarWindowStateController);
}
@@ -183,25 +184,36 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
}
@Test
- public void testOnViewAttachedShowsMicCameraIconWhenDisabled() {
+ public void testOnViewAttachedShowsMicIconWhenDisabled() {
when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE))
.thenReturn(true);
when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA))
- .thenReturn(true);
+ .thenReturn(false);
mController.onViewAttached();
verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null);
+ DreamOverlayStatusBarView.STATUS_ICON_MIC_DISABLED, true, null);
}
@Test
- public void testOnViewAttachedHidesMicCameraIconWhenEnabled() {
+ public void testOnViewAttachedShowsCameraIconWhenDisabled() {
when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE))
.thenReturn(false);
when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA))
- .thenReturn(false);
+ .thenReturn(true);
+ mController.onViewAttached();
+ verify(mView).showIcon(
+ DreamOverlayStatusBarView.STATUS_ICON_CAMERA_DISABLED, true, null);
+ }
+
+ @Test
+ public void testOnViewAttachedShowsMicCameraIconWhenDisabled() {
+ when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE))
+ .thenReturn(true);
+ when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA))
+ .thenReturn(true);
mController.onViewAttached();
verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, false, null);
+ DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, true, null);
}
@Test
@@ -231,6 +243,26 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testNotificationsIconNotShownWhenCountProviderAbsent() {
+ DreamOverlayStatusBarViewController controller = new DreamOverlayStatusBarViewController(
+ mView,
+ mResources,
+ mMainExecutor,
+ mConnectivityManager,
+ mTouchSession,
+ mAlarmManager,
+ mNextAlarmController,
+ mDateFormatUtil,
+ mSensorPrivacyController,
+ Optional.empty(),
+ mZenModeController,
+ mStatusBarWindowStateController);
+ controller.onViewAttached();
+ verify(mView, never()).showIcon(
+ eq(DreamOverlayStatusBarView.STATUS_ICON_NOTIFICATIONS), eq(true), any());
+ }
+
+ @Test
public void testOnViewAttachedShowsPriorityModeIconWhenEnabled() {
when(mZenModeController.getZen()).thenReturn(
Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS);
@@ -365,24 +397,6 @@ public class DreamOverlayStatusBarViewControllerTest extends SysuiTestCase {
}
@Test
- public void testMicCameraIconHiddenWhenSensorsNotBlocked() {
- when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.MICROPHONE))
- .thenReturn(true).thenReturn(false);
- when(mSensorPrivacyController.isSensorBlocked(SensorPrivacyManager.Sensors.CAMERA))
- .thenReturn(true).thenReturn(false);
- mController.onViewAttached();
-
- final ArgumentCaptor<IndividualSensorPrivacyController.Callback> callbackCapture =
- ArgumentCaptor.forClass(IndividualSensorPrivacyController.Callback.class);
- verify(mSensorPrivacyController).addCallback(callbackCapture.capture());
- callbackCapture.getValue().onSensorBlockedChanged(
- SensorPrivacyManager.Sensors.MICROPHONE, false);
-
- verify(mView).showIcon(
- DreamOverlayStatusBarView.STATUS_ICON_MIC_CAMERA_DISABLED, false, null);
- }
-
- @Test
public void testPriorityModeIconShownWhenZenModeEnabled() {
mController.onViewAttached();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
index ed1cf69ad8c0..964e6d79f0bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/SmartSpaceComplicationTest.java
@@ -15,6 +15,8 @@
*/
package com.android.systemui.dreams;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -23,11 +25,14 @@ import static org.mockito.Mockito.when;
import android.app.smartspace.SmartspaceTarget;
import android.content.Context;
import android.testing.AndroidTestingRunner;
+import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.smartspace.DreamsSmartspaceController;
+import com.android.systemui.dreams.complication.Complication;
+import com.android.systemui.dreams.complication.ComplicationViewModel;
+import com.android.systemui.dreams.smartspace.DreamSmartspaceController;
import com.android.systemui.plugins.BcSmartspaceDataPlugin;
import org.junit.Before;
@@ -47,7 +52,7 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
private Context mContext;
@Mock
- private DreamsSmartspaceController mSmartspaceController;
+ private DreamSmartspaceController mSmartspaceController;
@Mock
private DreamOverlayStateController mDreamOverlayStateController;
@@ -55,25 +60,79 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
@Mock
private SmartSpaceComplication mComplication;
+ @Mock
+ private ComplicationViewModel mComplicationViewModel;
+
+ @Mock
+ private View mBcSmartspaceView;
+
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
/**
- * Ensures {@link SmartSpaceComplication} is only registered when it is available.
+ * Ensures {@link SmartSpaceComplication} isn't registered right away on start.
*/
@Test
- public void testAvailability() {
+ public void testRegistrantStart_doesNotAddComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+ verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
+ }
- final SmartSpaceComplication.Registrant registrant = new SmartSpaceComplication.Registrant(
+ private SmartSpaceComplication.Registrant getRegistrant() {
+ return new SmartSpaceComplication.Registrant(
mContext,
mDreamOverlayStateController,
mComplication,
mSmartspaceController);
+ }
+
+ @Test
+ public void testOverlayActive_addsTargetListener() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
registrant.start();
- verify(mDreamOverlayStateController, never()).addComplication(eq(mComplication));
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ // Test
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+ }
+
+ @Test
+ public void testOverlayActive_targetsNonEmpty_addsComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+
+ // Test
+ final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testOverlayActive_targetsEmpty_removesComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
@@ -89,5 +148,45 @@ public class SmartSpaceComplicationTest extends SysuiTestCase {
final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ // Test
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList());
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testOverlayInActive_removesTargetListener_removesComplication() {
+ final SmartSpaceComplication.Registrant registrant = getRegistrant();
+ registrant.start();
+
+ final ArgumentCaptor<DreamOverlayStateController.Callback> dreamCallbackCaptor =
+ ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+ verify(mDreamOverlayStateController).addCallback(dreamCallbackCaptor.capture());
+
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ dreamCallbackCaptor.getValue().onStateChanged();
+
+ final ArgumentCaptor<BcSmartspaceDataPlugin.SmartspaceTargetListener> listenerCaptor =
+ ArgumentCaptor.forClass(BcSmartspaceDataPlugin.SmartspaceTargetListener.class);
+ verify(mSmartspaceController).addListener(listenerCaptor.capture());
+
+ final SmartspaceTarget target = Mockito.mock(SmartspaceTarget.class);
+ listenerCaptor.getValue().onSmartspaceTargetsUpdated(Arrays.asList(target));
+ verify(mDreamOverlayStateController).addComplication(eq(mComplication));
+
+ // Test
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(false);
+ dreamCallbackCaptor.getValue().onStateChanged();
+ verify(mSmartspaceController).removeListener(listenerCaptor.getValue());
+ verify(mDreamOverlayStateController).removeComplication(eq(mComplication));
+ }
+
+ @Test
+ public void testGetView_reusesSameView() {
+ final SmartSpaceComplication complication = new SmartSpaceComplication(getContext(),
+ mSmartspaceController);
+ final Complication.ViewHolder viewHolder = complication.createView(mComplicationViewModel);
+ when(mSmartspaceController.buildAndConnectView(any())).thenReturn(mBcSmartspaceView);
+ assertEquals(viewHolder.getView(), viewHolder.getView());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
deleted file mode 100644
index 151742af7e1a..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamWeatherComplicationTest.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.dreams.complication;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class DreamWeatherComplicationTest extends SysuiTestCase {
- @SuppressWarnings("HidingField")
- @Mock
- private Context mContext;
-
- @Mock
- private LockscreenSmartspaceController mSmartspaceController;
-
- @Mock
- private DreamOverlayStateController mDreamOverlayStateController;
-
- @Mock
- private DreamWeatherComplication mComplication;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- }
-
- /**
- * Ensures {@link DreamWeatherComplication} is only registered when it is available.
- */
- @Test
- public void testComplicationAvailability() {
- when(mSmartspaceController.isEnabled()).thenReturn(false);
- final DreamWeatherComplication.Registrant registrant =
- new DreamWeatherComplication.Registrant(
- mContext,
- mSmartspaceController,
- mDreamOverlayStateController,
- mComplication);
- registrant.start();
- verify(mDreamOverlayStateController, never()).addComplication(any());
-
- when(mSmartspaceController.isEnabled()).thenReturn(true);
- registrant.start();
- verify(mDreamOverlayStateController).addComplication(eq(mComplication));
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 89c82fbff901..c3fca29a9883 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -422,7 +422,29 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase {
verify(mUiEventLogger).log(BouncerSwipeTouchHandler.DreamEvent.DREAM_BOUNCER_FULLY_VISIBLE);
}
+ /**
+ * Ensures {@link CentralSurfaces}
+ */
+ @Test
+ public void testInformBouncerShowingOnExpand() {
+ swipeToPosition(1f, Direction.UP, 0);
+ }
+
+ /**
+ * Ensures {@link CentralSurfaces}
+ */
+ @Test
+ public void testInformBouncerHidingOnCollapse() {
+ // Must swipe up to set initial state.
+ swipeToPosition(1f, Direction.UP, 0);
+ Mockito.clearInvocations(mCentralSurfaces);
+
+ swipeToPosition(0f, Direction.DOWN, 0);
+ }
+
+
private void swipeToPosition(float percent, Direction direction, float velocityY) {
+ Mockito.clearInvocations(mTouchSession);
mTouchHandler.onSessionStart(mTouchSession);
ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor =
ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
index 0a021331d9d1..14a5702c8e5b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/HideComplicationTouchHandlerTest.java
@@ -35,6 +35,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dreams.complication.Complication;
import com.android.systemui.shared.system.InputChannelCompat;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.touch.TouchInsetManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -59,6 +60,9 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
TouchInsetManager mTouchInsetManager;
@Mock
+ StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+
+ @Mock
Handler mHandler;
@Mock
@@ -83,12 +87,45 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
mVisibilityController,
RESTORE_TIMEOUT,
mTouchInsetManager,
+ mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
// Report multiple active sessions.
when(mSession.getActiveSessionCount()).thenReturn(2);
+ // Bouncer hidden.
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+
+ // Start session.
+ touchHandler.onSessionStart(mSession);
+
+ // Verify session end.
+ verify(mSession).pop();
+
+ // Verify no interaction with visibility controller.
+ verify(mVisibilityController, never()).setVisibility(anyInt(), anyBoolean());
+ }
+
+ /**
+ * Ensures no actions are taken when the bouncer is showing.
+ */
+ @Test
+ public void testSessionEndWhenBouncerShowing() {
+ final HideComplicationTouchHandler touchHandler = new HideComplicationTouchHandler(
+ mVisibilityController,
+ RESTORE_TIMEOUT,
+ mTouchInsetManager,
+ mStatusBarKeyguardViewManager,
+ mFakeExecutor,
+ mHandler);
+
+ // Report one session.
+ when(mSession.getActiveSessionCount()).thenReturn(1);
+
+ // Bouncer is showing.
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+
// Start session.
touchHandler.onSessionStart(mSession);
@@ -108,12 +145,16 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
mVisibilityController,
RESTORE_TIMEOUT,
mTouchInsetManager,
+ mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
+ // Bouncer hidden.
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+
// Start session
touchHandler.onSessionStart(mSession);
@@ -149,12 +190,16 @@ public class HideComplicationTouchHandlerTest extends SysuiTestCase {
mVisibilityController,
RESTORE_TIMEOUT,
mTouchInsetManager,
+ mStatusBarKeyguardViewManager,
mFakeExecutor,
mHandler);
// Report one session
when(mSession.getActiveSessionCount()).thenReturn(1);
+ // Bouncer hidden.
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+
// Start session
touchHandler.onSessionStart(mSession);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index b43856aae4cf..51f3404a9bca 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -26,6 +26,7 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
import com.android.systemui.statusbar.commandline.Command
import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.capture
@@ -75,6 +76,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
private lateinit var broadcastReceiver: BroadcastReceiver
private lateinit var clearCacheAction: Consumer<Int>
+ private val deviceConfig = DeviceConfigProxyFake()
private val teamfoodableFlagA = BooleanFlag(500, false, true)
private val teamfoodableFlagB = BooleanFlag(501, true, true)
@@ -90,6 +92,7 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
systemProperties,
resources,
dumpManager,
+ deviceConfig,
flagMap,
commandRegistry,
barService
@@ -195,6 +198,21 @@ class FeatureFlagsDebugTest : SysuiTestCase() {
}
@Test
+ fun testReadDeviceConfigBooleanFlag() {
+ val namespace = "test_namespace"
+ deviceConfig.setProperty(namespace, "a", "true", false)
+ deviceConfig.setProperty(namespace, "b", "false", false)
+ deviceConfig.setProperty(namespace, "c", null, false)
+
+ assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
+ .isTrue()
+ assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
+ .isFalse()
+ assertThat(mFeatureFlagsDebug.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
+ .isFalse()
+ }
+
+ @Test
fun testReadStringFlag() {
whenever(flagManager.readFlagValue<String>(eq(3), any())).thenReturn("foo")
whenever(flagManager.readFlagValue<String>(eq(4), any())).thenReturn("bar")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index b5deb0b55e11..6b683f456ea7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -20,6 +20,7 @@ import android.content.res.Resources
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.DeviceConfigProxyFake
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.After
@@ -44,10 +45,13 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
@Mock private lateinit var mSystemProperties: SystemPropertiesHelper
@Mock private lateinit var mDumpManager: DumpManager
+ private val deviceConfig = DeviceConfigProxyFake()
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
- mFeatureFlagsRelease = FeatureFlagsRelease(mResources, mSystemProperties, mDumpManager)
+ mFeatureFlagsRelease = FeatureFlagsRelease(mResources, mSystemProperties, deviceConfig,
+ mDumpManager)
}
@After
@@ -85,6 +89,21 @@ class FeatureFlagsReleaseTest : SysuiTestCase() {
}
@Test
+ fun testReadDeviceConfigBooleanFlag() {
+ val namespace = "test_namespace"
+ deviceConfig.setProperty(namespace, "a", "true", false)
+ deviceConfig.setProperty(namespace, "b", "false", false)
+ deviceConfig.setProperty(namespace, "c", null, false)
+
+ assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(1, "a", namespace)))
+ .isTrue()
+ assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(2, "b", namespace)))
+ .isFalse()
+ assertThat(mFeatureFlagsRelease.isEnabled(DeviceConfigBooleanFlag(3, "c", namespace)))
+ .isFalse()
+ }
+
+ @Test
fun testSysPropBooleanFlag() {
val flagId = 213
val flagName = "sys_prop_flag"
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 f00fbe6ac4d7..141a213a5b6a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/globalactions/GlobalActionsDialogLiteTest.java
@@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
@@ -480,4 +481,13 @@ public class GlobalActionsDialogLiteTest extends SysuiTestCase {
// hide dialog again
mGlobalActionsDialogLite.showOrHideDialog(true, true, null /* view */);
}
+
+ @Test
+ public void testBugreportAction_whenDebugMode_shouldOfferBugreportButtonBeforeProvisioning() {
+ doReturn(1).when(mGlobalSettings).getInt(anyString(), anyInt());
+
+ GlobalActionsDialogLite.BugReportAction bugReportAction =
+ mGlobalActionsDialogLite.makeBugReportActionForTesting();
+ assertThat(bugReportAction.showBeforeProvisioning()).isTrue();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
index 5a4bb86dba45..df506b479b88 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/AnimatableClockControllerTest.java
@@ -34,12 +34,12 @@ import android.view.View;
import androidx.test.filters.SmallTest;
import com.android.keyguard.AnimatableClockController;
-import com.android.keyguard.AnimatableClockView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.settingslib.Utils;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shared.clocks.AnimatableClockView;
import com.android.systemui.statusbar.policy.BatteryController;
import org.junit.After;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index 51c258055465..23516c94d851 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -45,6 +45,7 @@ import androidx.slice.core.SliceQuery;
import androidx.test.filters.SmallTest;
import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SystemUIInitializerImpl;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationMediaManager;
@@ -100,7 +101,7 @@ public class KeyguardSliceProviderTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
mIsZenMode = false;
mProvider = new TestableKeyguardSliceProvider();
- mProvider.setContextAvailableCallback(context -> { });
+ mProvider.setContextAvailableCallback(context -> new SystemUIInitializerImpl(mContext));
mProvider.attachInfo(getContext(), null);
reset(mContentResolver);
SliceProvider.setSpecs(new HashSet<>(Arrays.asList(SliceSpecs.LIST)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index a80aed7a6d18..9b665555562a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -203,6 +203,13 @@ public class KeyguardViewMediatorTest extends SysuiTestCase {
mViewMediator.mViewMediatorCallback.getBouncerPromptReason());
}
+ @Test
+ public void testHideSurfaceBehindKeyguardMarksKeyguardNotGoingAway() {
+ mViewMediator.hideSurfaceBehindKeyguard();
+
+ verify(mKeyguardStateController).notifyKeyguardGoingAway(false);
+ }
+
private void createAndStartViewMediator() {
mViewMediator = new KeyguardViewMediator(
mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
new file mode 100644
index 000000000000..4f5c570ee812
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/lifecycle/WindowAddedViewLifecycleOwnerTest.kt
@@ -0,0 +1,150 @@
+package com.android.systemui.lifecycle
+
+import android.view.View
+import android.view.ViewTreeObserver
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleRegistry
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.capture
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(JUnit4::class)
+class WindowAddedViewLifecycleOwnerTest : SysuiTestCase() {
+
+ @Mock lateinit var view: View
+ @Mock lateinit var viewTreeObserver: ViewTreeObserver
+
+ private lateinit var underTest: WindowAddedViewLifecycleOwner
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(view.viewTreeObserver).thenReturn(viewTreeObserver)
+ whenever(view.isAttachedToWindow).thenReturn(false)
+ whenever(view.windowVisibility).thenReturn(View.INVISIBLE)
+ whenever(view.hasWindowFocus()).thenReturn(false)
+
+ underTest = WindowAddedViewLifecycleOwner(view) { LifecycleRegistry.createUnsafe(it) }
+ }
+
+ @Test
+ fun `detached - invisible - does not have focus -- INITIALIZED`() {
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+ }
+
+ @Test
+ fun `detached - invisible - has focus -- INITIALIZED`() {
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ val captor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(captor))
+ captor.value.onWindowFocusChanged(true)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+ }
+
+ @Test
+ fun `detached - visible - does not have focus -- INITIALIZED`() {
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ val captor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(captor))
+ captor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+ }
+
+ @Test
+ fun `detached - visible - has focus -- INITIALIZED`() {
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
+ focusCaptor.value.onWindowFocusChanged(true)
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
+ visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.INITIALIZED)
+ }
+
+ @Test
+ fun `attached - invisible - does not have focus -- CREATED`() {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val captor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
+ verify(viewTreeObserver).addOnWindowAttachListener(capture(captor))
+ captor.value.onWindowAttached()
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `attached - invisible - has focus -- CREATED`() {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
+ verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
+ attachCaptor.value.onWindowAttached()
+
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
+ focusCaptor.value.onWindowFocusChanged(true)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.CREATED)
+ }
+
+ @Test
+ fun `attached - visible - does not have focus -- STARTED`() {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
+ verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
+ attachCaptor.value.onWindowAttached()
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
+ visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.STARTED)
+ }
+
+ @Test
+ fun `attached - visible - has focus -- RESUMED`() {
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ val attachCaptor = argumentCaptor<ViewTreeObserver.OnWindowAttachListener>()
+ verify(viewTreeObserver).addOnWindowAttachListener(capture(attachCaptor))
+ attachCaptor.value.onWindowAttached()
+
+ whenever(view.hasWindowFocus()).thenReturn(true)
+ val focusCaptor = argumentCaptor<ViewTreeObserver.OnWindowFocusChangeListener>()
+ verify(viewTreeObserver).addOnWindowFocusChangeListener(capture(focusCaptor))
+ focusCaptor.value.onWindowFocusChanged(true)
+
+ whenever(view.windowVisibility).thenReturn(View.VISIBLE)
+ val visibilityCaptor = argumentCaptor<ViewTreeObserver.OnWindowVisibilityChangeListener>()
+ verify(viewTreeObserver).addOnWindowVisibilityChangeListener(capture(visibilityCaptor))
+ visibilityCaptor.value.onWindowVisibilityChanged(View.VISIBLE)
+
+ assertThat(underTest.lifecycle.currentState).isEqualTo(Lifecycle.State.RESUMED)
+ }
+
+ @Test
+ fun dispose() {
+ underTest.dispose()
+
+ verify(viewTreeObserver).removeOnWindowAttachListener(any())
+ verify(viewTreeObserver).removeOnWindowVisibilityChangeListener(any())
+ verify(viewTreeObserver).removeOnWindowFocusChangeListener(any())
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
index b97924107322..f56d42ec3fb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/ColorSchemeTransitionTest.kt
@@ -16,7 +16,6 @@
package com.android.systemui.media
-import org.mockito.Mockito.`when` as whenever
import android.animation.ValueAnimator
import android.graphics.Color
import android.testing.AndroidTestingRunner
@@ -34,6 +33,7 @@ import org.mockito.Mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private const val DEFAULT_COLOR = Color.RED
@@ -147,8 +147,8 @@ class ColorSchemeTransitionTest : SysuiTestCase() {
@Test
fun testColorSchemeTransition_update() {
- colorSchemeTransition.updateColorScheme(colorScheme, true)
- verify(mockAnimatingTransition, times(10)).updateColorScheme(colorScheme)
+ colorSchemeTransition.updateColorScheme(colorScheme)
+ verify(mockAnimatingTransition, times(8)).updateColorScheme(colorScheme)
verify(gutsViewHolder).colorScheme = colorScheme
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
index 6f4579bb14b5..178502269e73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt
@@ -21,7 +21,6 @@ import android.animation.AnimatorSet
import android.app.PendingIntent
import android.app.smartspace.SmartspaceAction
import android.content.Context
-import org.mockito.Mockito.`when` as whenever
import android.content.Intent
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
@@ -58,6 +57,7 @@ import com.android.internal.logging.InstanceId
import com.android.systemui.ActivityIntentHelper
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.BroadcastDialogController
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.media.MediaControlPanel.KEY_SMARTSPACE_APP_NAME
import com.android.systemui.media.dialog.MediaOutputDialogFactory
@@ -68,8 +68,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.animation.TransitionLayout
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.KotlinArgumentCaptor
-import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.argumentCaptor
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.mockito.withArgCaptor
@@ -92,6 +92,7 @@ import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private const val KEY = "TEST_KEY"
@@ -104,6 +105,7 @@ private const val SESSION_ARTIST = "SESSION_ARTIST"
private const val SESSION_TITLE = "SESSION_TITLE"
private const val DISABLED_DEVICE_NAME = "DISABLED_DEVICE_NAME"
private const val REC_APP_NAME = "REC APP NAME"
+private const val APP_NAME = "APP_NAME"
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -130,6 +132,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
@Mock private lateinit var mediaCarouselController: MediaCarouselController
@Mock private lateinit var falsingManager: FalsingManager
@Mock private lateinit var transitionParent: ViewGroup
+ @Mock private lateinit var broadcastDialogController: BroadcastDialogController
private lateinit var appIcon: ImageView
@Mock private lateinit var albumView: ImageView
private lateinit var titleText: TextView
@@ -160,8 +163,9 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var dismissText: TextView
private lateinit var session: MediaSession
- private val device = MediaDeviceData(true, null, DEVICE_NAME)
- private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME)
+ private lateinit var device: MediaDeviceData
+ private val disabledDevice = MediaDeviceData(false, null, DISABLED_DEVICE_NAME, null,
+ showBroadcastButton = false)
private lateinit var mediaData: MediaData
private val clock = FakeSystemClock()
@Mock private lateinit var logger: MediaUiEventLogger
@@ -187,6 +191,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
private lateinit var recSubtitle1: TextView
private lateinit var recSubtitle2: TextView
private lateinit var recSubtitle3: TextView
+ private var shouldShowBroadcastButton: Boolean = false
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -223,7 +228,8 @@ public class MediaControlPanelTest : SysuiTestCase() {
logger,
keyguardStateController,
activityIntentHelper,
- lockscreenUserManager) {
+ lockscreenUserManager,
+ broadcastDialogController) {
override fun loadAnimator(
animId: Int,
otionInterpolator: Interpolator,
@@ -236,28 +242,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
initGutsViewHolderMocks()
initMediaViewHolderMocks()
- // Create media session
- val metadataBuilder = MediaMetadata.Builder().apply {
- putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
- putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
- }
- val playbackBuilder = PlaybackState.Builder().apply {
- setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
- setActions(PlaybackState.ACTION_PLAY)
- }
- session = MediaSession(context, SESSION_KEY).apply {
- setMetadata(metadataBuilder.build())
- setPlaybackState(playbackBuilder.build())
- }
- session.setActive(true)
-
- mediaData = MediaTestUtils.emptyMediaData.copy(
- artist = ARTIST,
- song = TITLE,
- packageName = PACKAGE,
- token = session.sessionToken,
- device = device,
- instanceId = instanceId)
+ initDeviceMediaData(false, DEVICE_NAME)
// Set up recommendation view
initRecommendationViewHolderMocks()
@@ -293,6 +278,34 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(gutsViewHolder.dismissText).thenReturn(dismissText)
}
+ private fun initDeviceMediaData(shouldShowBroadcastButton: Boolean, name: String) {
+ device = MediaDeviceData(true, null, name, null,
+ showBroadcastButton = shouldShowBroadcastButton)
+
+ // Create media session
+ val metadataBuilder = MediaMetadata.Builder().apply {
+ putString(MediaMetadata.METADATA_KEY_ARTIST, SESSION_ARTIST)
+ putString(MediaMetadata.METADATA_KEY_TITLE, SESSION_TITLE)
+ }
+ val playbackBuilder = PlaybackState.Builder().apply {
+ setState(PlaybackState.STATE_PAUSED, 6000L, 1f)
+ setActions(PlaybackState.ACTION_PLAY)
+ }
+ session = MediaSession(context, SESSION_KEY).apply {
+ setMetadata(metadataBuilder.build())
+ setPlaybackState(playbackBuilder.build())
+ }
+ session.setActive(true)
+
+ mediaData = MediaTestUtils.emptyMediaData.copy(
+ artist = ARTIST,
+ song = TITLE,
+ packageName = PACKAGE,
+ token = session.sessionToken,
+ device = device,
+ instanceId = instanceId)
+ }
+
/**
* Initialize elements in media view holder
*/
@@ -342,6 +355,7 @@ public class MediaControlPanelTest : SysuiTestCase() {
whenever(viewHolder.player).thenReturn(view)
whenever(viewHolder.appIcon).thenReturn(appIcon)
whenever(viewHolder.albumView).thenReturn(albumView)
+ whenever(albumView.foreground).thenReturn(mock(Drawable::class.java))
whenever(viewHolder.titleText).thenReturn(titleText)
whenever(viewHolder.artistText).thenReturn(artistText)
whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java))
@@ -1045,6 +1059,28 @@ public class MediaControlPanelTest : SysuiTestCase() {
assertThat(seamless.isEnabled()).isFalse()
}
+ @Test
+ fun bindBroadcastButton() {
+ initMediaViewHolderMocks()
+ initDeviceMediaData(true, APP_NAME)
+
+ val mockAvd0 = mock(AnimatedVectorDrawable::class.java)
+ whenever(mockAvd0.mutate()).thenReturn(mockAvd0)
+ val semanticActions0 = MediaButton(
+ playOrPause = MediaAction(mockAvd0, Runnable {}, "play", null)
+ )
+ val state = mediaData.copy(resumption = true, semanticActions = semanticActions0,
+ isPlaying = false)
+ player.attachPlayer(viewHolder)
+ player.bindPlayer(state, PACKAGE)
+ assertThat(seamlessText.getText()).isEqualTo(APP_NAME)
+ assertThat(seamless.isEnabled()).isTrue()
+
+ seamless.callOnClick()
+
+ verify(logger).logOpenBroadcastDialog(anyInt(), eq(PACKAGE), eq(instanceId))
+ }
+
/* ***** Guts tests for the player ***** */
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
index 3e335c5163a9..04b93d79f83b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDataCombineLatestTest.java
@@ -79,7 +79,7 @@ public class MediaDataCombineLatestTest extends SysuiTestCase {
new ArrayList<>(), new ArrayList<>(), null, PACKAGE, null, null, null, true, null,
MediaData.PLAYBACK_LOCAL, false, KEY, false, false, false, 0L,
InstanceId.fakeInstanceId(-1), -1);
- mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME);
+ mDeviceData = new MediaDeviceData(true, null, DEVICE_NAME, null, false);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
index 187210a40b6d..ee104262dc29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaDeviceManagerTest.kt
@@ -16,6 +16,10 @@
package com.android.systemui.media
+import android.bluetooth.BluetoothLeBroadcast
+import android.bluetooth.BluetoothLeBroadcastMetadata
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
import android.graphics.drawable.Drawable
import android.media.MediaRouter2Manager
import android.media.RoutingSessionInfo
@@ -25,8 +29,12 @@ import android.media.session.MediaSession
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast
+import com.android.settingslib.bluetooth.LocalBluetoothManager
+import com.android.settingslib.bluetooth.LocalBluetoothProfileManager
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.dump.DumpManager
import com.android.systemui.media.muteawait.MediaMuteAwaitConnectionManager
@@ -42,6 +50,7 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
import org.mockito.Mockito.any
import org.mockito.Mockito.mock
@@ -60,6 +69,8 @@ private const val SESSION_KEY = "SESSION_KEY"
private const val DEVICE_ID = "DEVICE_ID"
private const val DEVICE_NAME = "DEVICE_NAME"
private const val REMOTE_DEVICE_NAME = "REMOTE_DEVICE_NAME"
+private const val BROADCAST_APP_NAME = "BROADCAST_APP_NAME"
+private const val NORMAL_APP_NAME = "NORMAL_APP_NAME"
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -83,6 +94,12 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Mock private lateinit var controller: MediaController
@Mock private lateinit var playbackInfo: PlaybackInfo
@Mock private lateinit var configurationController: ConfigurationController
+ @Mock private lateinit var bluetoothLeBroadcast: BluetoothLeBroadcast
+ @Mock private lateinit var localBluetoothProfileManager: LocalBluetoothProfileManager
+ @Mock private lateinit var localBluetoothLeBroadcast: LocalBluetoothLeBroadcast
+ @Mock private lateinit var packageManager: PackageManager
+ @Mock private lateinit var applicationInfo: ApplicationInfo
+ private lateinit var localBluetoothManager: LocalBluetoothManager
private lateinit var session: MediaSession
private lateinit var mediaData: MediaData
@JvmField @Rule val mockito = MockitoJUnit.rule()
@@ -91,12 +108,15 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
fun setUp() {
fakeFgExecutor = FakeExecutor(FakeSystemClock())
fakeBgExecutor = FakeExecutor(FakeSystemClock())
+ localBluetoothManager = mDependency.injectMockDependency(LocalBluetoothManager::class.java)
manager = MediaDeviceManager(
+ context,
controllerFactory,
lmmFactory,
mr2,
muteAwaitFactory,
configurationController,
+ localBluetoothManager,
fakeFgExecutor,
fakeBgExecutor,
dumpster
@@ -119,6 +139,7 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
token = session.sessionToken)
whenever(controllerFactory.create(session.sessionToken))
.thenReturn(controller)
+ setupLeAudioConfiguration(false)
}
@After
@@ -545,7 +566,8 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
@Test
fun testRemotePlaybackDeviceOverride() {
whenever(route.name).thenReturn(DEVICE_NAME)
- val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null)
+ val deviceData = MediaDeviceData(false, null, REMOTE_DEVICE_NAME, null,
+ showBroadcastButton = false)
val mediaDataWithDevice = mediaData.copy(device = deviceData)
// GIVEN media data that already has a device set
@@ -560,12 +582,95 @@ public class MediaDeviceManagerTest : SysuiTestCase() {
verify(lmm, never()).registerCallback(any())
}
+ @Test
+ fun onBroadcastStarted_currentMediaDeviceDataIsBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(BROADCAST_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isTrue()
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(context.getString(
+ R.string.broadcasting_description_is_broadcasting))
+ }
+
+ @Test
+ fun onBroadcastStarted_currentMediaDeviceDataIsNotBroadcasting() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(true)
+ setupBroadcastPackage(NORMAL_APP_NAME)
+ broadcastCallback.onBroadcastStarted(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isTrue()
+ assertThat(data.enabled).isTrue()
+ assertThat(data.name).isEqualTo(BROADCAST_APP_NAME)
+ }
+
+ @Test
+ fun onBroadcastStopped_bluetoothLeBroadcastIsDisabledAndBroadcastingButtonIsGone() {
+ val broadcastCallback = setupBroadcastCallback()
+ setupLeAudioConfiguration(false)
+ broadcastCallback.onBroadcastStopped(1, 1)
+
+ manager.onMediaDataLoaded(KEY, null, mediaData)
+ fakeBgExecutor.runAllReady()
+ fakeFgExecutor.runAllReady()
+
+ val data = captureDeviceData(KEY)
+ assertThat(data.showBroadcastButton).isFalse()
+ }
+
fun captureCallback(): LocalMediaManager.DeviceCallback {
val captor = ArgumentCaptor.forClass(LocalMediaManager.DeviceCallback::class.java)
verify(lmm).registerCallback(captor.capture())
return captor.getValue()
}
+ fun setupBroadcastCallback(): BluetoothLeBroadcast.Callback {
+ val callback: BluetoothLeBroadcast.Callback = object : BluetoothLeBroadcast.Callback {
+ override fun onBroadcastStarted(reason: Int, broadcastId: Int) {}
+ override fun onBroadcastStartFailed(reason: Int) {}
+ override fun onBroadcastStopped(reason: Int, broadcastId: Int) {}
+ override fun onBroadcastStopFailed(reason: Int) {}
+ override fun onPlaybackStarted(reason: Int, broadcastId: Int) {}
+ override fun onPlaybackStopped(reason: Int, broadcastId: Int) {}
+ override fun onBroadcastUpdated(reason: Int, broadcastId: Int) {}
+ override fun onBroadcastUpdateFailed(reason: Int, broadcastId: Int) {}
+ override fun onBroadcastMetadataChanged(broadcastId: Int,
+ metadata: BluetoothLeBroadcastMetadata) {}
+ }
+
+ bluetoothLeBroadcast.registerCallback(fakeFgExecutor, callback)
+ return callback;
+ }
+
+ fun setupLeAudioConfiguration(isLeAudio: Boolean) {
+ whenever(localBluetoothManager.profileManager).thenReturn(localBluetoothProfileManager)
+ whenever(localBluetoothProfileManager.leAudioBroadcastProfile)
+ .thenReturn(localBluetoothLeBroadcast)
+ whenever(localBluetoothLeBroadcast.isEnabled(any())).thenReturn(isLeAudio)
+ whenever(localBluetoothLeBroadcast.appSourceName).thenReturn(BROADCAST_APP_NAME)
+ }
+
+ fun setupBroadcastPackage(currentName: String) {
+ whenever(lmm.packageName).thenReturn(PACKAGE)
+ whenever(packageManager.getApplicationInfo(eq(PACKAGE), anyInt()))
+ .thenReturn(applicationInfo)
+ whenever(packageManager.getApplicationLabel(applicationInfo)).thenReturn(currentName)
+ context.setMockPackageManager(packageManager)
+ }
+
fun captureDeviceData(key: String, oldKey: String? = null): MediaDeviceData {
val captor = ArgumentCaptor.forClass(MediaDeviceData::class.java)
verify(listener).onMediaDeviceChanged(eq(key), eq(oldKey), captor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
index ae58fe67da23..3d9ed5fe7f7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaTestUtils.kt
@@ -20,7 +20,8 @@ class MediaTestUtils {
device = null,
active = true,
resumeAction = null,
+ isPlaying = false,
instanceId = InstanceId.fakeInstanceId(-1),
appUid = -1)
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
index 59475cf0cb90..568e0cb22f18 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputAdapterTest.java
@@ -71,14 +71,13 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Before
public void setUp() {
- mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController, mMediaOutputDialog);
+ mMediaOutputAdapter = new MediaOutputAdapter(mMediaOutputController);
mViewHolder = (MediaOutputAdapter.MediaDeviceViewHolder) mMediaOutputAdapter
.onCreateViewHolder(new LinearLayout(mContext), 0);
mSpyMediaOutputSeekbar = spy(mViewHolder.mSeekBar);
when(mMediaOutputController.getMediaDevices()).thenReturn(mMediaDevices);
when(mMediaOutputController.hasAdjustVolumeUserRestriction()).thenReturn(false);
- when(mMediaOutputController.isZeroMode()).thenReturn(false);
when(mMediaOutputController.isTransferring()).thenReturn(false);
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
@@ -98,28 +97,12 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
}
@Test
- public void getItemCount_nonZeroMode_isDeviceSize() {
- assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size());
- }
-
- @Test
- public void getItemCount_zeroMode_containExtraOneForPairNew() {
- when(mMediaOutputController.isZeroMode()).thenReturn(true);
-
+ public void getItemCount_containExtraOneForPairNew() {
assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size() + 1);
}
@Test
- public void getItemCount_withDynamicGroup_containExtraOneForGroup() {
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- when(mMediaOutputController.isZeroMode()).thenReturn(false);
-
- assertThat(mMediaOutputAdapter.getItemCount()).isEqualTo(mMediaDevices.size());
- }
-
- @Test
- public void onBindViewHolder_zeroMode_bindPairNew_verifyView() {
- when(mMediaOutputController.isZeroMode()).thenReturn(true);
+ public void onBindViewHolder_bindPairNew_verifyView() {
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
assertThat(mViewHolder.mTitleText.getVisibility()).isEqualTo(View.VISIBLE);
@@ -133,7 +116,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindGroup_withSessionName_verifyView() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- when(mMediaOutputController.isZeroMode()).thenReturn(false);
when(mMediaOutputController.getSessionName()).thenReturn(TEST_SESSION_NAME);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -148,7 +130,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onBindViewHolder_bindGroup_noSessionName_verifyView() {
when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- when(mMediaOutputController.isZeroMode()).thenReturn(false);
when(mMediaOutputController.getSessionName()).thenReturn(null);
mMediaOutputAdapter.getItemCount();
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 0);
@@ -257,7 +238,6 @@ public class MediaOutputAdapterTest extends SysuiTestCase {
@Test
public void onItemClick_clickPairNew_verifyLaunchBluetoothPairing() {
- when(mMediaOutputController.isZeroMode()).thenReturn(true);
mMediaOutputAdapter.onBindViewHolder(mViewHolder, 2);
mViewHolder.mContainerLayout.performClick();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
index 9eaa20c2afed..d414660018a8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputBaseDialogTest.java
@@ -43,6 +43,8 @@ import android.widget.TextView;
import androidx.core.graphics.drawable.IconCompat;
import androidx.test.filters.SmallTest;
+import com.android.settingslib.bluetooth.CachedBluetoothDevice;
+import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager;
import com.android.settingslib.bluetooth.LocalBluetoothLeBroadcast;
import com.android.settingslib.bluetooth.LocalBluetoothManager;
import com.android.settingslib.bluetooth.LocalBluetoothProfileManager;
@@ -98,10 +100,17 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
private CharSequence mHeaderSubtitle;
private String mStopText;
private boolean mIsBroadcasting;
+ private boolean mIsBroadcastIconVisibility;
+
@Before
public void setUp() {
when(mLocalBluetoothManager.getProfileManager()).thenReturn(mLocalBluetoothProfileManager);
+ final CachedBluetoothDeviceManager cachedBluetoothDeviceManager = mock(
+ CachedBluetoothDeviceManager.class);
+ when(mLocalBluetoothManager.getCachedDeviceManager()).thenReturn(
+ cachedBluetoothDeviceManager);
+ when(cachedBluetoothDeviceManager.findDevice(any())).thenReturn(null);
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(null);
when(mMediaController.getPlaybackState()).thenReturn(mPlaybackState);
when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_NONE);
@@ -153,6 +162,27 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
}
@Test
+ public void refresh_broadcastIconVisibilityOff_broadcastIconLayoutNotVisible() {
+ mIsBroadcastIconVisibility = false;
+
+ mMediaOutputBaseDialogImpl.refresh();
+ final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
+ R.id.broadcast_icon);
+
+ assertThat(view.getVisibility()).isEqualTo(View.GONE);
+ }
+ @Test
+ public void refresh_broadcastIconVisibilityOn_broadcastIconLayoutVisible() {
+ mIsBroadcastIconVisibility = true;
+
+ mMediaOutputBaseDialogImpl.refresh();
+ final ImageView view = mMediaOutputBaseDialogImpl.mDialogView.requireViewById(
+ R.id.broadcast_icon);
+
+ assertThat(view.getVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
public void refresh_checkTitle() {
mHeaderTitle = "test_string";
@@ -308,5 +338,10 @@ public class MediaOutputBaseDialogTest extends SysuiTestCase {
public CharSequence getStopButtonText() {
return mStopText;
}
+
+ @Override
+ public int getBroadcastIconVisibility() {
+ return mIsBroadcastIconVisibility ? View.VISIBLE : View.GONE;
+ }
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
index 2bf5f0fcbfcb..751c8951859c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputControllerTest.java
@@ -383,62 +383,6 @@ public class MediaOutputControllerTest extends SysuiTestCase {
}
@Test
- public void isZeroMode_onlyFromPhoneOutput_returnTrue() {
- // Multiple available devices
- assertThat(mMediaOutputController.isZeroMode()).isFalse();
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_PHONE_DEVICE);
- mMediaDevices.clear();
- mMediaDevices.add(mMediaDevice1);
- mMediaOutputController.start(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
-
- assertThat(mMediaOutputController.isZeroMode()).isTrue();
-
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_3POINT5_MM_AUDIO_DEVICE);
-
- assertThat(mMediaOutputController.isZeroMode()).isTrue();
-
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_USB_C_AUDIO_DEVICE);
-
- assertThat(mMediaOutputController.isZeroMode()).isTrue();
- }
-
- @Test
- public void isZeroMode_notFromPhoneOutput_returnFalse() {
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_UNKNOWN);
- mMediaDevices.clear();
- mMediaDevices.add(mMediaDevice1);
- mMediaOutputController.start(mCb);
- mMediaOutputController.onDeviceListUpdate(mMediaDevices);
-
- assertThat(mMediaOutputController.isZeroMode()).isFalse();
-
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_FAST_PAIR_BLUETOOTH_DEVICE);
-
- assertThat(mMediaOutputController.isZeroMode()).isFalse();
-
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_BLUETOOTH_DEVICE);
-
- assertThat(mMediaOutputController.isZeroMode()).isFalse();
-
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_CAST_DEVICE);
-
- assertThat(mMediaOutputController.isZeroMode()).isFalse();
-
- when(mMediaDevice1.getDeviceType()).thenReturn(
- MediaDevice.MediaDeviceType.TYPE_CAST_GROUP_DEVICE);
-
- assertThat(mMediaOutputController.isZeroMode()).isFalse();
- }
-
- @Test
public void getGroupMediaDevices_differentDeviceOrder_showingSameOrder() {
final MediaDevice selectedMediaDevice1 = mock(MediaDevice.class);
final MediaDevice selectedMediaDevice2 = mock(MediaDevice.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
index ef0fc95f95b2..6afed1a846b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputDialogTest.java
@@ -140,16 +140,31 @@ public class MediaOutputDialogTest extends SysuiTestCase {
mFeatures.add(MediaRoute2Info.FEATURE_REMOTE_GROUP_PLAYBACK);
assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
+ }
- mFeatures.clear();
+ @Test
+ public void getStopButtonVisibility_remoteBLEDevice_returnVisible() {
when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
mLocalBluetoothLeBroadcast);
when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
+ when(mMediaDevice.isBLEDevice()).thenReturn(true);
+
assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
}
@Test
+ public void getStopButtonVisibility_remoteNonBLEDevice_returnGone() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
+ when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+ assertThat(mMediaOutputDialog.getStopButtonVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
public void getStopButtonVisibility_localDevice_returnGone() {
mFeatures.add(MediaRoute2Info.FEATURE_LOCAL_PLAYBACK);
@@ -157,6 +172,39 @@ public class MediaOutputDialogTest extends SysuiTestCase {
}
@Test
+ public void getBroadcastIconVisibility_isBroadcasting_returnVisible() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(true);
+ when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
+ when(mMediaDevice.isBLEDevice()).thenReturn(true);
+
+ assertThat(mMediaOutputDialog.getBroadcastIconVisibility()).isEqualTo(View.VISIBLE);
+ }
+
+ @Test
+ public void getBroadcastIconVisibility_noBroadcasting_returnGone() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
+ when(mMediaDevice.isBLEDevice()).thenReturn(true);
+
+ assertThat(mMediaOutputDialog.getBroadcastIconVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
+ public void getBroadcastIconVisibility_remoteNonLeDevice_returnGone() {
+ when(mLocalBluetoothProfileManager.getLeAudioBroadcastProfile()).thenReturn(
+ mLocalBluetoothLeBroadcast);
+ when(mLocalBluetoothLeBroadcast.isEnabled(any())).thenReturn(false);
+ when(mPlaybackState.getState()).thenReturn(PlaybackState.STATE_PLAYING);
+ when(mMediaDevice.isBLEDevice()).thenReturn(false);
+
+ assertThat(mMediaOutputDialog.getBroadcastIconVisibility()).isEqualTo(View.GONE);
+ }
+
+ @Test
// Check the visibility metric logging by creating a new MediaOutput dialog,
// and verify if the calling times increases.
public void onCreate_ShouldLogVisibility() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
deleted file mode 100644
index 9256cd32291f..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupAdapterTest.java
+++ /dev/null
@@ -1,222 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dialog;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.graphics.drawable.Icon;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.widget.LinearLayout;
-
-import androidx.core.graphics.drawable.IconCompat;
-import androidx.test.filters.SmallTest;
-
-import com.android.settingslib.media.MediaDevice;
-import com.android.systemui.R;
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class MediaOutputGroupAdapterTest extends SysuiTestCase {
-
- private static final String TEST_DEVICE_NAME_1 = "test_device_name_1";
- private static final String TEST_DEVICE_NAME_2 = "test_device_name_2";
- private static final String TEST_DEVICE_ID_1 = "test_device_id_1";
- private static final String TEST_DEVICE_ID_2 = "test_device_id_2";
- private static final int TEST_VOLUME = 10;
- private static final int TEST_MAX_VOLUME = 50;
-
- // Mock
- private MediaOutputController mMediaOutputController = mock(MediaOutputController.class);
- private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
- private MediaDevice mMediaDevice2 = mock(MediaDevice.class);
- private Icon mIcon = mock(Icon.class);
- private IconCompat mIconCompat = mock(IconCompat.class);
-
- private MediaOutputGroupAdapter mGroupAdapter;
- private MediaOutputGroupAdapter.GroupViewHolder mGroupViewHolder;
- private List<MediaDevice> mGroupMediaDevices = new ArrayList<>();
- private List<MediaDevice> mSelectableMediaDevices = new ArrayList<>();
- private List<MediaDevice> mSelectedMediaDevices = new ArrayList<>();
- private List<MediaDevice> mDeselectableMediaDevices = new ArrayList<>();
-
- @Before
- public void setUp() {
- when(mMediaOutputController.getGroupMediaDevices()).thenReturn(mGroupMediaDevices);
- when(mMediaOutputController.getDeviceIconCompat(mMediaDevice1)).thenReturn(mIconCompat);
- when(mMediaOutputController.getDeviceIconCompat(mMediaDevice2)).thenReturn(mIconCompat);
- when(mMediaOutputController.getSelectableMediaDevice()).thenReturn(mSelectableMediaDevices);
- when(mMediaOutputController.getSelectedMediaDevice()).thenReturn(mSelectedMediaDevices);
- when(mMediaOutputController.getDeselectableMediaDevice()).thenReturn(
- mDeselectableMediaDevices);
- when(mIconCompat.toIcon(mContext)).thenReturn(mIcon);
- when(mMediaDevice1.getName()).thenReturn(TEST_DEVICE_NAME_1);
- when(mMediaDevice1.getId()).thenReturn(TEST_DEVICE_ID_1);
- when(mMediaDevice2.getName()).thenReturn(TEST_DEVICE_NAME_2);
- when(mMediaDevice2.getId()).thenReturn(TEST_DEVICE_ID_2);
- mGroupMediaDevices.add(mMediaDevice1);
- mGroupMediaDevices.add(mMediaDevice2);
- mSelectedMediaDevices.add(mMediaDevice1);
- mSelectableMediaDevices.add(mMediaDevice2);
- mDeselectableMediaDevices.add(mMediaDevice1);
-
- mGroupAdapter = new MediaOutputGroupAdapter(mMediaOutputController);
- mGroupViewHolder = (MediaOutputGroupAdapter.GroupViewHolder) mGroupAdapter
- .onCreateViewHolder(new LinearLayout(mContext), 0);
- }
-
- @Test
- public void onBindViewHolder_verifyGroupItem() {
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 0);
-
- assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getText()).isEqualTo(mContext.getText(
- R.string.media_output_dialog_group));
- }
-
- @Test
- public void onBindViewHolder_singleSelectedDevice_verifyView() {
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 1);
-
- assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_1);
- assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mCheckBox.isChecked()).isTrue();
- // Disabled checkBox
- assertThat(mGroupViewHolder.mCheckBox.isEnabled()).isFalse();
- }
-
- @Test
- public void onBindViewHolder_multipleSelectedDevice_verifyView() {
- mSelectedMediaDevices.clear();
- mSelectedMediaDevices.add(mMediaDevice1);
- mSelectedMediaDevices.add(mMediaDevice2);
- mDeselectableMediaDevices.clear();
- mDeselectableMediaDevices.add(mMediaDevice1);
- mDeselectableMediaDevices.add(mMediaDevice2);
- mSelectableMediaDevices.clear();
-
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 1);
-
- assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_1);
- assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mCheckBox.isChecked()).isTrue();
- // Enabled checkBox
- assertThat(mGroupViewHolder.mCheckBox.isEnabled()).isTrue();
- }
-
- @Test
- public void onBindViewHolder_notDeselectedDevice_verifyView() {
- mSelectedMediaDevices.clear();
- mSelectedMediaDevices.add(mMediaDevice1);
- mSelectedMediaDevices.add(mMediaDevice2);
- mDeselectableMediaDevices.clear();
- mDeselectableMediaDevices.add(mMediaDevice1);
- mSelectableMediaDevices.clear();
-
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 2);
-
- assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
- assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mCheckBox.isChecked()).isTrue();
- // Disabled checkBox
- assertThat(mGroupViewHolder.mCheckBox.isEnabled()).isFalse();
- }
-
- @Test
- public void onBindViewHolder_selectableDevice_verifyCheckBox() {
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 2);
-
- assertThat(mGroupViewHolder.mTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mProgressBar.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSubTitleText.getVisibility()).isEqualTo(View.GONE);
- assertThat(mGroupViewHolder.mSeekBar.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mTwoLineTitleText.getText()).isEqualTo(TEST_DEVICE_NAME_2);
- assertThat(mGroupViewHolder.mCheckBox.getVisibility()).isEqualTo(View.VISIBLE);
- assertThat(mGroupViewHolder.mCheckBox.isChecked()).isFalse();
- // Enabled checkBox
- assertThat(mGroupViewHolder.mCheckBox.isEnabled()).isTrue();
- }
-
- @Test
- public void onBindViewHolder_verifyDeviceVolume() {
- when(mMediaDevice1.getCurrentVolume()).thenReturn(TEST_VOLUME);
- when(mMediaDevice1.getMaxVolume()).thenReturn(TEST_MAX_VOLUME);
- mGroupViewHolder.mSeekBar.setVisibility(View.VISIBLE);
-
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 1);
-
- assertThat(mGroupViewHolder.mSeekBar.getVolume()).isEqualTo(TEST_VOLUME);
- }
-
- @Test
- public void clickSelectedDevice_verifyRemoveDeviceFromPlayMedia() {
- mSelectedMediaDevices.clear();
- mSelectedMediaDevices.add(mMediaDevice1);
- mSelectedMediaDevices.add(mMediaDevice2);
- mDeselectableMediaDevices.clear();
- mDeselectableMediaDevices.add(mMediaDevice1);
- mDeselectableMediaDevices.add(mMediaDevice2);
- mSelectableMediaDevices.clear();
-
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 1);
- mGroupViewHolder.mCheckBox.performClick();
-
- verify(mMediaOutputController).removeDeviceFromPlayMedia(mMediaDevice1);
- }
-
- @Test
- public void clickSelectabelDevice_verifyAddDeviceToPlayMedia() {
- mGroupAdapter.onBindViewHolder(mGroupViewHolder, 2);
-
- mGroupViewHolder.mCheckBox.performClick();
-
- verify(mMediaOutputController).addDeviceToPlayMedia(mMediaDevice2);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
deleted file mode 100644
index 4534ae6448ec..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/media/dialog/MediaOutputGroupDialogTest.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.media.dialog;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import android.media.AudioManager;
-import android.media.session.MediaSessionManager;
-import android.os.PowerExemptionManager;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.view.View;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.settingslib.bluetooth.LocalBluetoothManager;
-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;
-import com.android.systemui.media.nearby.NearbyMediaDevicesManager;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class MediaOutputGroupDialogTest extends SysuiTestCase {
-
- private static final String TEST_PACKAGE = "test_package";
-
- // Mock
- private MediaSessionManager mMediaSessionManager = mock(MediaSessionManager.class);
- private LocalBluetoothManager mLocalBluetoothManager = mock(LocalBluetoothManager.class);
- private ActivityStarter mStarter = mock(ActivityStarter.class);
- private BroadcastSender mBroadcastSender = mock(BroadcastSender.class);
- private LocalMediaManager mLocalMediaManager = mock(LocalMediaManager.class);
- private MediaDevice mMediaDevice = mock(MediaDevice.class);
- private MediaDevice mMediaDevice1 = mock(MediaDevice.class);
- private NotificationEntryManager mNotificationEntryManager =
- mock(NotificationEntryManager.class);
- private final DialogLaunchAnimator mDialogLaunchAnimator = mock(DialogLaunchAnimator.class);
- private NearbyMediaDevicesManager mNearbyMediaDevicesManager = mock(
- NearbyMediaDevicesManager.class);
- private final AudioManager mAudioManager = mock(AudioManager.class);
- private PowerExemptionManager mPowerExemptionManager = mock(PowerExemptionManager.class);
-
- private MediaOutputGroupDialog mMediaOutputGroupDialog;
- private MediaOutputController mMediaOutputController;
- private List<MediaDevice> mMediaDevices = new ArrayList<>();
-
- @Before
- public void setUp() {
- mMediaOutputController = new MediaOutputController(mContext, TEST_PACKAGE,
- mMediaSessionManager, mLocalBluetoothManager, mStarter,
- mNotificationEntryManager, mDialogLaunchAnimator,
- Optional.of(mNearbyMediaDevicesManager), mAudioManager, mPowerExemptionManager);
- mMediaOutputController.mLocalMediaManager = mLocalMediaManager;
- mMediaOutputGroupDialog = new MediaOutputGroupDialog(mContext, false, mBroadcastSender,
- mMediaOutputController);
- mMediaOutputGroupDialog.show();
- when(mLocalMediaManager.getSelectedMediaDevice()).thenReturn(mMediaDevices);
- }
-
- @After
- public void tearDown() {
- mMediaOutputGroupDialog.dismissDialog();
- }
-
- @Test
- public void getStopButtonVisibility_returnVisible() {
- assertThat(mMediaOutputGroupDialog.getStopButtonVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void getHeaderSubtitle_singleDevice_verifyTitle() {
- mMediaDevices.add(mMediaDevice);
-
- assertThat(mMediaOutputGroupDialog.getHeaderSubtitle()).isEqualTo(
- mContext.getText(R.string.media_output_dialog_single_device));
- }
-
- @Test
- public void getHeaderSubtitle_multipleDevices_verifyTitle() {
- mMediaDevices.add(mMediaDevice);
- mMediaDevices.add(mMediaDevice1);
-
- assertThat(mMediaOutputGroupDialog.getHeaderSubtitle()).isEqualTo(mContext.getString(
- R.string.media_output_dialog_multiple_devices, mMediaDevices.size()));
- }
-
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
index b9a69bb8641a..2eb478303cb2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/common/MediaTttChipControllerCommonTest.kt
@@ -25,6 +25,7 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.systemui.R
@@ -65,6 +66,8 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
@Mock
private lateinit var logger: MediaTttLogger
@Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
private lateinit var windowManager: WindowManager
@Mock
private lateinit var viewUtil: ViewUtil
@@ -88,11 +91,21 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
)).thenReturn(applicationInfo)
context.setMockPackageManager(packageManager)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any()))
+ .thenReturn(TIMEOUT_MS.toInt())
+
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
controllerCommon = TestControllerCommon(
- context, logger, windowManager, viewUtil, fakeExecutor, tapGestureDetector, powerManager
+ context,
+ logger,
+ windowManager,
+ viewUtil,
+ fakeExecutor,
+ accessibilityManager,
+ tapGestureDetector,
+ powerManager
)
}
@@ -344,6 +357,7 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
windowManager: WindowManager,
viewUtil: ViewUtil,
@Main mainExecutor: DelayableExecutor,
+ accessibilityManager: AccessibilityManager,
tapGestureDetector: TapGestureDetector,
powerManager: PowerManager
) : MediaTttChipControllerCommon<ChipInfo>(
@@ -352,23 +366,22 @@ class MediaTttChipControllerCommonTest : SysuiTestCase() {
windowManager,
viewUtil,
mainExecutor,
+ accessibilityManager,
tapGestureDetector,
powerManager,
R.layout.media_ttt_chip
) {
- override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {
-
- }
-
- override fun getIconSize(isAppIcon: Boolean): Int? = ICON_SIZE
+ override val windowLayoutParams = commonWindowLayoutParams
+ override fun updateChipView(chipInfo: ChipInfo, currentChipView: ViewGroup) {}
+ override fun getIconSize(isAppIcon: Boolean): Int = ICON_SIZE
}
inner class ChipInfo : ChipInfoCommon {
- override fun getTimeoutMs() = TIMEOUT_MS
+ override fun getTimeoutMs() = 1L
}
}
private const val PACKAGE_NAME = "com.android.systemui"
private const val APP_NAME = "Fake App Name"
private const val TIMEOUT_MS = 10000L
-private const val ICON_SIZE = 47 \ No newline at end of file
+private const val ICON_SIZE = 47
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
index 9edc4f4c71c3..bbc564193080 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiverTest.kt
@@ -28,6 +28,7 @@ import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import androidx.test.filters.SmallTest
import com.android.internal.logging.testing.UiEventLoggerFake
@@ -65,6 +66,8 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
@Mock
private lateinit var logger: MediaTttLogger
@Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -99,6 +102,7 @@ class MediaTttChipControllerReceiverTest : SysuiTestCase() {
windowManager,
viewUtil,
FakeExecutor(FakeSystemClock()),
+ accessibilityManager,
TapGestureDetector(context),
powerManager,
Handler.getMain(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
index a8c72ddfd5d7..7ca0cd34ab26 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttChipControllerSenderTest.kt
@@ -27,6 +27,7 @@ import android.testing.TestableLooper
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
+import android.view.accessibility.AccessibilityManager
import android.widget.ImageView
import android.widget.TextView
import androidx.test.filters.SmallTest
@@ -67,6 +68,8 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
@Mock
private lateinit var logger: MediaTttLogger
@Mock
+ private lateinit var accessibilityManager: AccessibilityManager
+ @Mock
private lateinit var powerManager: PowerManager
@Mock
private lateinit var windowManager: WindowManager
@@ -95,9 +98,12 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
fakeClock = FakeSystemClock()
fakeExecutor = FakeExecutor(fakeClock)
+
uiEventLoggerFake = UiEventLoggerFake()
senderUiEventLogger = MediaTttSenderUiEventLogger(uiEventLoggerFake)
+ whenever(accessibilityManager.getRecommendedTimeoutMillis(any(), any())).thenReturn(TIMEOUT)
+
controllerSender = MediaTttChipControllerSender(
commandQueue,
context,
@@ -105,6 +111,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
windowManager,
viewUtil,
fakeExecutor,
+ accessibilityManager,
TapGestureDetector(context),
powerManager,
senderUiEventLogger
@@ -592,7 +599,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
fakeClock.advanceTime(1000L)
controllerSender.removeChip("fakeRemovalReason")
- fakeClock.advanceTime(state.state.timeout + 1)
+ fakeClock.advanceTime(TIMEOUT + 1L)
verify(windowManager).removeView(any())
}
@@ -615,7 +622,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
fakeClock.advanceTime(1000L)
controllerSender.removeChip("fakeRemovalReason")
- fakeClock.advanceTime(state.state.timeout + 1)
+ fakeClock.advanceTime(TIMEOUT + 1L)
verify(windowManager).removeView(any())
}
@@ -674,6 +681,7 @@ class MediaTttChipControllerSenderTest : SysuiTestCase() {
private const val APP_NAME = "Fake app name"
private const val OTHER_DEVICE_NAME = "My Tablet"
private const val PACKAGE_NAME = "com.android.systemui"
+private const val TIMEOUT = 10000
private val routeInfo = MediaRoute2Info.Builder("id", OTHER_DEVICE_NAME)
.addFeature("feature")
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 1cfa3b2a08ff..8fa5c9324416 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -89,6 +89,7 @@ import com.android.systemui.recents.OverviewProxyService;
import com.android.systemui.recents.Recents;
import com.android.systemui.settings.UserContextProvider;
import com.android.systemui.settings.UserTracker;
+import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shared.rotation.RotationButtonController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -97,7 +98,6 @@ import com.android.systemui.statusbar.phone.AutoHideController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.LightBarController;
import com.android.systemui.statusbar.phone.LightBarTransitionsController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
index 99f21ad4d508..c8ebd1240149 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/LaunchConversationActivityTest.java
@@ -48,6 +48,8 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
+import com.android.systemui.util.concurrency.FakeExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubble;
@@ -106,6 +108,9 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
private Intent mIntent;
+ private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -114,7 +119,8 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
mNotifCollection,
Optional.of(mBubblesManager),
mUserManager,
- mCommandQueue
+ mCommandQueue,
+ mBgExecutor
);
verify(mCommandQueue, times(1)).addCallback(mCallbacksCaptor.capture());
@@ -193,6 +199,7 @@ public class LaunchConversationActivityTest extends SysuiTestCase {
// Ensure callback removed
verify(mCommandQueue).removeCallback(any());
// Clear the notification for bubbles.
+ FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mIStatusBarService, times(1)).onNotificationClear(any(),
anyInt(), any(), anyInt(), anyInt(), mNotificationVisibilityCaptor.capture());
// Do not select the bubble.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 26e4d9dfb003..a56990f40b90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -222,7 +222,7 @@ public class PowerNotificationWarningsTest extends SysuiTestCase {
mReceiver.onReceive(mContext, intent);
- verify(mDialogLaunchAnimator).showFromView(any(), eq(mView));
+ verify(mDialogLaunchAnimator).showFromView(any(), eq(mView), any());
mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
index 664af75a6529..32c66d25d4aa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSFragmentTest.java
@@ -32,8 +32,6 @@ import android.app.Fragment;
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.LayoutInflater;
@@ -42,36 +40,23 @@ import android.view.ViewGroup;
import androidx.test.filters.SmallTest;
-import com.android.internal.logging.UiEventLogger;
import com.android.keyguard.BouncerPanelExpansionCalculator;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
import com.android.systemui.animation.ShadeInterpolation;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.MediaHost;
import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.customize.QSCustomizerController;
import com.android.systemui.qs.dagger.QSFragmentComponent;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
import com.android.systemui.qs.external.TileServiceRequestController;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.tileimpl.QSFactoryImpl;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.RemoteInputQuickSettingsDisabler;
-import com.android.systemui.tuner.TunerService;
import com.android.systemui.util.animation.UniqueObjectHostView;
-import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -79,8 +64,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@RunWithLooper(setAsMainLooper = true)
@SmallTest
@@ -125,34 +108,11 @@ public class QSFragmentTest extends SysuiBaseFragmentTest {
mFragments.dispatchResume();
processAllMessages();
- QSTileHost host =
- new QSTileHost(
- mContext,
- mock(StatusBarIconController.class),
- mock(QSFactoryImpl.class),
- new Handler(),
- Looper.myLooper(),
- mock(PluginManager.class),
- mock(TunerService.class),
- () -> mock(AutoTileManager.class),
- mock(DumpManager.class),
- mock(BroadcastDispatcher.class),
- Optional.of(mock(CentralSurfaces.class)),
- mock(QSLogger.class),
- mock(UiEventLogger.class),
- mock(UserTracker.class),
- mock(SecureSettings.class),
- mock(CustomTileStatePersister.class),
- mTileServiceRequestControllerBuilder,
- mock(TileLifecycleManager.Factory.class));
-
qs.setListening(true);
processAllMessages();
qs.setListening(false);
processAllMessages();
- host.destroy();
- processAllMessages();
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
index a3c353b5a666..c1c0f78ecd6b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSSecurityFooterTest.java
@@ -731,7 +731,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
mTestableLooper.processAllMessages();
- verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView));
+ verify(mDialogLaunchAnimator).showFromView(any(), eq(mRootView), any());
}
@Test
@@ -768,7 +768,7 @@ public class QSSecurityFooterTest extends SysuiTestCase {
ArgumentCaptor<AlertDialog> dialogCaptor = ArgumentCaptor.forClass(AlertDialog.class);
mTestableLooper.processAllMessages();
- verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any());
+ verify(mDialogLaunchAnimator).showFromView(dialogCaptor.capture(), any(), any());
AlertDialog dialog = dialogCaptor.getValue();
dialog.create();
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 8cf3fe274848..7dbc561fbfc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
@@ -32,12 +32,11 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import androidx.annotation.Nullable;
@@ -48,7 +47,6 @@ import com.android.internal.logging.UiEventLogger;
import com.android.internal.util.CollectionUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.ActivityStarter;
@@ -68,8 +66,10 @@ import com.android.systemui.statusbar.phone.AutoTileManager;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.tuner.TunerService;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.settings.FakeSettings;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -81,18 +81,19 @@ 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;
@RunWith(AndroidTestingRunner.class)
@SmallTest
-@RunWithLooper(setAsMainLooper = true)
public class QSTileHostTest extends SysuiTestCase {
private static String MOCK_STATE_STRING = "MockState";
private static ComponentName CUSTOM_TILE =
ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
+ private static final String SETTING = QSTileHost.TILES_SETTING;
@Mock
private StatusBarIconController mIconController;
@@ -107,8 +108,6 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private DumpManager mDumpManager;
@Mock
- private BroadcastDispatcher mBroadcastDispatcher;
- @Mock
private QSTile.State mMockState;
@Mock
private CentralSurfaces mCentralSurfaces;
@@ -132,31 +131,47 @@ public class QSTileHostTest extends SysuiTestCase {
@Mock
private TileLifecycleManager mTileLifecycleManager;
- private Handler mHandler;
- private TestableLooper mLooper;
+ private FakeExecutor mMainExecutor;
+
private QSTileHost mQSTileHost;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mLooper = TestableLooper.get(this);
- mHandler = new Handler(mLooper.getLooper());
+ mMainExecutor = new FakeExecutor(new FakeSystemClock());
+
when(mTileServiceRequestControllerBuilder.create(any()))
.thenReturn(mTileServiceRequestController);
when(mTileLifecycleManagerFactory.create(any(Intent.class), any(UserHandle.class)))
.thenReturn(mTileLifecycleManager);
mSecureSettings = new FakeSettings();
- mSecureSettings.putStringForUser(
- QSTileHost.TILES_SETTING, "", "", false, mUserTracker.getUserId(), false);
- mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mHandler,
- mLooper.getLooper(), mPluginManager, mTunerService, mAutoTiles, mDumpManager,
- mBroadcastDispatcher, mCentralSurfaces, mQSLogger, mUiEventLogger, mUserTracker,
- mSecureSettings, mCustomTileStatePersister, mTileServiceRequestControllerBuilder,
- mTileLifecycleManagerFactory);
+ saveSetting("");
+ mQSTileHost = new TestQSTileHost(mContext, mIconController, mDefaultFactory, mMainExecutor,
+ mPluginManager, mTunerService, mAutoTiles, mDumpManager, mCentralSurfaces,
+ mQSLogger, mUiEventLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
+ mTileServiceRequestControllerBuilder, mTileLifecycleManagerFactory);
+
+ mSecureSettings.registerContentObserverForUser(SETTING, new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ super.onChange(selfChange);
+ mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting()));
+ mMainExecutor.runAllReady();
+ }
+ }, mUserTracker.getUserId());
setUpTileFactory();
}
+ private void saveSetting(String value) {
+ mSecureSettings.putStringForUser(
+ SETTING, value, "", false, mUserTracker.getUserId(), false);
+ }
+
+ private String getSetting() {
+ return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId());
+ }
+
private void setUpTileFactory() {
when(mMockState.toString()).thenReturn(MOCK_STATE_STRING);
// Only create this kind of tiles
@@ -173,6 +188,10 @@ public class QSTileHostTest extends SysuiTestCase {
return new NotAvailableTile(mQSTileHost);
} else if (CUSTOM_TILE_SPEC.equals(spec)) {
return mCustomTile;
+ } else if ("internet".equals(spec)
+ || "wifi".equals(spec)
+ || "cell".equals(spec)) {
+ return new TestTile1(mQSTileHost);
} else {
return null;
}
@@ -196,14 +215,14 @@ public class QSTileHostTest extends SysuiTestCase {
public void testInvalidSpecUsesDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "not-valid");
+ saveSetting("not-valid");
assertEquals(2, mQSTileHost.getTiles().size());
}
@Test
public void testRemoveWifiAndCellularWithoutInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2");
+ saveSetting("wifi, spec1, cell, spec2");
assertEquals("internet", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec1", mQSTileHost.mTileSpecs.get(1));
@@ -212,7 +231,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testRemoveWifiAndCellularWithInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "wifi, spec1, cell, spec2, internet");
+ saveSetting("wifi, spec1, cell, spec2, internet");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -221,7 +240,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testRemoveWifiWithoutInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, wifi, spec2");
+ saveSetting("spec1, wifi, spec2");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("internet", mQSTileHost.mTileSpecs.get(1));
@@ -230,7 +249,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testRemoveCellWithInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1, spec2, cell, internet");
+ saveSetting("spec1, spec2, cell, internet");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -239,7 +258,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testNoWifiNoCellularNoInternet() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
assertEquals("spec2", mQSTileHost.mTileSpecs.get(1));
@@ -249,7 +268,7 @@ public class QSTileHostTest extends SysuiTestCase {
public void testSpecWithInvalidDoesNotUseDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec2,not-valid");
+ saveSetting("spec2,not-valid");
assertEquals(1, mQSTileHost.getTiles().size());
QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
@@ -258,7 +277,7 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testDump() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
StringWriter w = new StringWriter();
PrintWriter pw = new PrintWriter(w);
mQSTileHost.dump(pw, new String[]{});
@@ -274,7 +293,7 @@ public class QSTileHostTest extends SysuiTestCase {
public void testDefault() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles_default, "spec1");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "default");
+ saveSetting("default");
assertEquals(1, mQSTileHost.getTiles().size());
QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
assertTrue(element instanceof TestTile1);
@@ -285,7 +304,7 @@ public class QSTileHostTest extends SysuiTestCase {
public void testNoRepeatedSpecs_addTile() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec2");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec2");
+ saveSetting("spec1,spec2");
mQSTileHost.addTile("spec1");
@@ -298,9 +317,10 @@ public class QSTileHostTest extends SysuiTestCase {
public void testAddTileAtValidPosition() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", 1);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -312,9 +332,10 @@ public class QSTileHostTest extends SysuiTestCase {
public void testAddTileAtInvalidPositionAddsToEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", 100);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -326,9 +347,10 @@ public class QSTileHostTest extends SysuiTestCase {
public void testAddTileAtEnd() {
mContext.getOrCreateTestableResources()
.addOverride(R.string.quick_settings_tiles, "spec1,spec3");
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1,spec3");
+ saveSetting("spec1,spec3");
mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
+ mMainExecutor.runAllReady();
assertEquals(3, mQSTileHost.mTileSpecs.size());
assertEquals("spec1", mQSTileHost.mTileSpecs.get(0));
@@ -338,9 +360,10 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testNoRepeatedSpecs_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, CUSTOM_TILE_SPEC);
+ saveSetting(CUSTOM_TILE_SPEC);
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+ mMainExecutor.runAllReady();
assertEquals(1, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -348,9 +371,10 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testAddedAtBeginningOnDefault_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -358,9 +382,10 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testAddedAtBeginning_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(0));
@@ -368,9 +393,10 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testAddedAtEnd_customTile() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "spec1"); // seed
+ saveSetting("spec1"); // seed
mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
+ mMainExecutor.runAllReady();
assertEquals(2, mQSTileHost.mTileSpecs.size());
assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.mTileSpecs.get(1));
@@ -409,13 +435,13 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testNotAvailableTile_specNotNull() {
- mQSTileHost.onTuningChanged(QSTileHost.TILES_SETTING, "na");
+ saveSetting("na");
verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
}
@Test
public void testCustomTileRemoved_stateDeleted() {
- mQSTileHost.changeTiles(List.of(CUSTOM_TILE_SPEC), List.of());
+ mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of());
verify(mCustomTileStatePersister)
.removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
@@ -423,29 +449,99 @@ public class QSTileHostTest extends SysuiTestCase {
@Test
public void testRemoveTiles() {
- List<String> tiles = List.of("spec1", "spec2", "spec3");
- mQSTileHost.saveTilesToSettings(tiles);
+ saveSetting("spec1,spec2,spec3");
mQSTileHost.removeTiles(List.of("spec1", "spec2"));
+ mMainExecutor.runAllReady();
assertEquals(List.of("spec3"), mQSTileHost.mTileSpecs);
}
+ @Test
+ public void testTilesRemovedInQuickSuccession() {
+ saveSetting("spec1,spec2,spec3");
+ mQSTileHost.removeTile("spec1");
+ mQSTileHost.removeTile("spec3");
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ assertEquals("spec2", getSetting());
+ }
+
+ @Test
+ public void testAddTileInMainThread() {
+ saveSetting("spec1,spec2");
+
+ mQSTileHost.addTile("spec3");
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTileInMainThread() {
+ saveSetting("spec1,spec2");
+
+ mQSTileHost.removeTile("spec1");
+ assertEquals(List.of("spec1", "spec2"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTilesInMainThread() {
+ saveSetting("spec1,spec2,spec3");
+
+ mQSTileHost.removeTiles(List.of("spec3", "spec1"));
+ assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec2"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testRemoveTileByUserInMainThread() {
+ saveSetting("spec1," + CUSTOM_TILE_SPEC);
+
+ mQSTileHost.removeTileByUser(CUSTOM_TILE);
+ assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.mTileSpecs);
+
+ mMainExecutor.runAllReady();
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ }
+
+ @Test
+ public void testNonValidTileNotStoredInSettings() {
+ saveSetting("spec1,not-valid");
+
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals("spec1", getSetting());
+ }
+
+ @Test
+ public void testNotAvailableTileNotStoredInSettings() {
+ saveSetting("spec1,na");
+
+ assertEquals(List.of("spec1"), mQSTileHost.mTileSpecs);
+ assertEquals("spec1", getSetting());
+ }
+
private class TestQSTileHost extends QSTileHost {
TestQSTileHost(Context context, StatusBarIconController iconController,
- QSFactory defaultFactory, Handler mainHandler, Looper bgLooper,
+ QSFactory defaultFactory, Executor mainExecutor,
PluginManager pluginManager, TunerService tunerService,
Provider<AutoTileManager> autoTiles, DumpManager dumpManager,
- BroadcastDispatcher broadcastDispatcher, CentralSurfaces centralSurfaces,
- QSLogger qsLogger, UiEventLogger uiEventLogger, UserTracker userTracker,
- SecureSettings secureSettings, CustomTileStatePersister customTileStatePersister,
+ CentralSurfaces centralSurfaces, QSLogger qsLogger, UiEventLogger uiEventLogger,
+ UserTracker userTracker, SecureSettings secureSettings,
+ CustomTileStatePersister customTileStatePersister,
TileServiceRequestController.Builder tileServiceRequestControllerBuilder,
TileLifecycleManager.Factory tileLifecycleManagerFactory) {
- super(context, iconController, defaultFactory, mainHandler, bgLooper, pluginManager,
- tunerService, autoTiles, dumpManager, broadcastDispatcher,
- Optional.of(centralSurfaces), qsLogger, uiEventLogger, userTracker,
- secureSettings, customTileStatePersister, tileServiceRequestControllerBuilder,
- tileLifecycleManagerFactory);
+ super(context, iconController, defaultFactory, mainExecutor, pluginManager,
+ tunerService, autoTiles, dumpManager, Optional.of(centralSurfaces), qsLogger,
+ uiEventLogger, userTracker, secureSettings, customTileStatePersister,
+ tileServiceRequestControllerBuilder, tileLifecycleManagerFactory);
}
@Override
@@ -455,25 +551,16 @@ public class QSTileHostTest extends SysuiTestCase {
@Override
public void onPluginDisconnected(QSFactory plugin) {
}
-
- @Override
- void saveTilesToSettings(List<String> tileSpecs) {
- super.saveTilesToSettings(tileSpecs);
- // After tiles are changed, make sure to call onTuningChanged with the new setting if it
- // changed
- String specs = mSecureSettings.getStringForUser(
- QSTileHost.TILES_SETTING, mUserTracker.getUserId());
- onTuningChanged(TILES_SETTING, specs);
- }
}
+
private class TestTile extends QSTileImpl<QSTile.State> {
protected TestTile(QSHost host) {
super(
host,
- mLooper.getLooper(),
- new Handler(mLooper.getLooper()),
+ mock(Looper.class),
+ mock(Handler.class),
new FalsingManagerFake(),
mock(MetricsLogger.class),
mock(StatusBarStateController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
index 3d53062d7d02..d42cbe3b698a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/customize/TileAdapterTest.java
@@ -55,6 +55,6 @@ public class TileAdapterTest extends SysuiTestCase {
@Test
public void testResetNotifiesHost() {
mTileAdapter.resetTileSpecs(Collections.emptyList());
- verify(mQSTileHost).changeTiles(any(), any());
+ verify(mQSTileHost).changeTilesByUser(any(), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
index 2ad9c9400f65..fb17fc0a0595 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileRequestDialogTest.kt
@@ -163,4 +163,20 @@ class TileRequestDialogTest : SysuiTestCase() {
assertThat(tile.isClickable).isFalse()
assertThat(tile.isLongClickable).isFalse()
}
-} \ No newline at end of file
+
+ @Test
+ fun setTileData_tileHasCorrectContentDescription() {
+ val icon = Icon.createWithResource(mContext, R.drawable.cloud)
+ val tileData = TileRequestDialog.TileData(APP_NAME, LABEL, icon)
+
+ dialog.setTileData(tileData)
+ dialog.show()
+
+ TestableLooper.get(this).processAllMessages()
+
+ val content = dialog.requireViewById<ViewGroup>(TileRequestDialog.CONTENT_ID)
+ val tile = content.getChildAt(1) as QSTileView
+
+ assertThat(tile.contentDescription).isEqualTo(LABEL)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
index 6b7e5b9335f2..471ddfd3f224 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java
@@ -29,6 +29,7 @@ import static org.mockito.Mockito.when;
import android.content.ComponentName;
import android.content.Intent;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.quicksettings.IQSTileService;
@@ -65,6 +66,7 @@ import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Optional;
+import java.util.concurrent.Executor;
import javax.inject.Provider;
@@ -130,17 +132,16 @@ public class TileServicesTest extends SysuiTestCase {
.thenReturn(mTileLifecycleManager);
Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper());
+ Executor executor = new HandlerExecutor(provider.get());
QSTileHost host = new QSTileHost(mContext,
mStatusBarIconController,
mQSFactory,
- provider.get(),
- mTestableLooper.getLooper(),
+ executor,
mPluginManager,
mTunerService,
() -> mAutoTileManager,
mDumpManager,
- mock(BroadcastDispatcher.class),
Optional.of(mCentralSurfaces),
mQSLogger,
mUiEventLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
index 0f2c2647f20f..ca3182affcc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSFactoryImplTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.qs.tiles.ColorInversionTile
import com.android.systemui.qs.tiles.DataSaverTile
import com.android.systemui.qs.tiles.DeviceControlsTile
import com.android.systemui.qs.tiles.DndTile
+import com.android.systemui.qs.tiles.DreamTile
import com.android.systemui.qs.tiles.FlashlightTile
import com.android.systemui.qs.tiles.HotspotTile
import com.android.systemui.qs.tiles.InternetTile
@@ -89,7 +90,8 @@ private val specMap = mapOf(
"wallet" to QuickAccessWalletTile::class.java,
"qr_code_scanner" to QRCodeScannerTile::class.java,
"onehanded" to OneHandedModeTile::class.java,
- "color_correction" to ColorCorrectionTile::class.java
+ "color_correction" to ColorCorrectionTile::class.java,
+ "dream" to DreamTile::class.java
)
@RunWith(AndroidTestingRunner::class)
@@ -129,6 +131,7 @@ class QSFactoryImplTest : SysuiTestCase() {
@Mock private lateinit var qrCodeScannerTile: QRCodeScannerTile
@Mock private lateinit var oneHandedModeTile: OneHandedModeTile
@Mock private lateinit var colorCorrectionTile: ColorCorrectionTile
+ @Mock private lateinit var dreamTile: DreamTile
private lateinit var factory: QSFactoryImpl
@@ -171,7 +174,8 @@ class QSFactoryImplTest : SysuiTestCase() {
{ quickAccessWalletTile },
{ qrCodeScannerTile },
{ oneHandedModeTile },
- { colorCorrectionTile }
+ { colorCorrectionTile },
+ { dreamTile }
)
// When adding/removing tiles, fix also [specMap]
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
new file mode 100644
index 000000000000..73a0cbc5054d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/AirplaneModeTileTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.net.ConnectivityManager
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.util.settings.GlobalSettings
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class AirplaneModeTileTest : SysuiTestCase() {
+ @Mock
+ private lateinit var mHost: QSHost
+ @Mock
+ private lateinit var mMetricsLogger: MetricsLogger
+ @Mock
+ private lateinit var mStatusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var mActivityStarter: ActivityStarter
+ @Mock
+ private lateinit var mQsLogger: QSLogger
+ @Mock
+ private lateinit var mBroadcastDispatcher: BroadcastDispatcher
+ @Mock
+ private lateinit var mConnectivityManager: Lazy<ConnectivityManager>
+ @Mock
+ private lateinit var mGlobalSettings: GlobalSettings
+ private lateinit var mTestableLooper: TestableLooper
+ private lateinit var mTile: AirplaneModeTile
+
+ private val mUiEventLogger: UiEventLogger = UiEventLoggerFake()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mTestableLooper = TestableLooper.get(this)
+ Mockito.`when`(mHost.context).thenReturn(mContext)
+ Mockito.`when`(mHost.uiEventLogger).thenReturn(mUiEventLogger)
+ Mockito.`when`(mHost.userContext).thenReturn(mContext)
+
+ mTile = AirplaneModeTile(mHost,
+ mTestableLooper.looper,
+ Handler(mTestableLooper.looper),
+ FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQsLogger,
+ mBroadcastDispatcher,
+ mConnectivityManager,
+ mGlobalSettings)
+ }
+
+ @Test
+ fun testIcon_whenDisabled_showsOffState() {
+ val state = QSTile.BooleanState()
+
+ mTile.handleUpdateState(state, 0)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_off))
+ }
+
+ @Test
+ fun testIcon_whenEnabled_showsOnState() {
+ val state = QSTile.BooleanState()
+
+ mTile.handleUpdateState(state, 1)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_airplane_icon_on))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
index 3d9205ee0354..95e7ad9fad4d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BatterySaverTileTest.kt
@@ -24,15 +24,19 @@ import android.testing.TestableLooper.RunWithLooper
import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BatteryController
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
@@ -77,6 +81,7 @@ class BatterySaverTileTest : SysuiTestCase() {
fun setUp() {
MockitoAnnotations.initMocks(this)
testableLooper = TestableLooper.get(this)
+ `when`(qsHost.context).thenReturn(mContext)
`when`(qsHost.userContext).thenReturn(userContext)
`when`(userContext.userId).thenReturn(USER)
@@ -133,4 +138,26 @@ class BatterySaverTileTest : SysuiTestCase() {
tile.handleSetListening(false)
verify(batteryController).clearLastPowerSaverStartView()
}
+
+ @Test
+ fun testIcon_whenBatterySaverDisabled_isOffState() {
+ val state = QSTile.BooleanState()
+ tile.onPowerSaveChanged(false)
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_off))
+ }
+
+ @Test
+ fun testIcon_whenBatterySaverEnabled_isOnState() {
+ val state = QSTile.BooleanState()
+ tile.onPowerSaveChanged(true)
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_battery_saver_icon_on))
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index cc472481ad8a..d65901777a73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -10,6 +10,7 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
@@ -18,6 +19,7 @@ import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSTileHost
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.BluetoothController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
@@ -25,6 +27,7 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -84,6 +87,53 @@ class BluetoothTileTest : SysuiTestCase() {
assertThat(tile.restrictionChecked).isEqualTo(UserManager.DISALLOW_BLUETOOTH)
}
+ @Test
+ fun testIcon_whenDisabled_isOffState() {
+ val state = QSTile.BooleanState()
+ disableBluetooth()
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
+ }
+
+ @Test
+ fun testIcon_whenDisconnected_isOffState() {
+ val state = QSTile.BooleanState()
+ enableBluetooth()
+ setBluetoothDisconnected()
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_off))
+ }
+
+ @Test
+ fun testIcon_whenConnected_isOnState() {
+ val state = QSTile.BooleanState()
+ enableBluetooth()
+ setBluetoothConnected()
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_on))
+ }
+
+ @Test
+ fun testIcon_whenConnecting_isSearchState() {
+ val state = QSTile.BooleanState()
+ enableBluetooth()
+ setBluetoothConnecting()
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_bluetooth_icon_search))
+ }
+
private class FakeBluetoothTile(
qsTileHost: QSTileHost,
backgroundLooper: Looper,
@@ -114,4 +164,27 @@ class BluetoothTileTest : SysuiTestCase() {
restrictionChecked = userRestriction
}
}
+
+ fun enableBluetooth() {
+ `when`(bluetoothController.isBluetoothEnabled).thenReturn(true)
+ }
+
+ fun disableBluetooth() {
+ `when`(bluetoothController.isBluetoothEnabled).thenReturn(false)
+ }
+
+ fun setBluetoothDisconnected() {
+ `when`(bluetoothController.isBluetoothConnecting).thenReturn(false)
+ `when`(bluetoothController.isBluetoothConnected).thenReturn(false)
+ }
+
+ fun setBluetoothConnected() {
+ `when`(bluetoothController.isBluetoothConnecting).thenReturn(false)
+ `when`(bluetoothController.isBluetoothConnected).thenReturn(true)
+ }
+
+ fun setBluetoothConnecting() {
+ `when`(bluetoothController.isBluetoothConnected).thenReturn(false)
+ `when`(bluetoothController.isBluetoothConnecting).thenReturn(true)
+ }
} \ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
new file mode 100644
index 000000000000..cfbb82f5f338
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/CameraToggleTileTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class CameraToggleTileTest : SysuiTestCase() {
+ companion object {
+ /* isBlocked */
+ const val CAMERA_TOGGLE_ENABLED: Boolean = false
+ const val CAMERA_TOGGLE_DISABLED: Boolean = true
+ }
+
+ @Mock
+ private lateinit var host: QSHost
+ @Mock
+ private lateinit var metricsLogger: MetricsLogger
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var qsLogger: QSLogger
+ @Mock
+ private lateinit var privacyController: IndividualSensorPrivacyController
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var tile: CameraToggleTile
+ private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ whenever(host.context).thenReturn(mContext)
+ whenever(host.uiEventLogger).thenReturn(uiEventLogger)
+
+ tile = CameraToggleTile(host,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController)
+ }
+
+ @Test
+ fun testIcon_whenCameraAccessEnabled_isOnState() {
+ val state = QSTile.BooleanState()
+
+ tile.handleUpdateState(state, CAMERA_TOGGLE_ENABLED)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_on))
+ }
+
+ @Test
+ fun testIcon_whenCameraAccessDisabled_isOffState() {
+ val state = QSTile.BooleanState()
+
+ tile.handleUpdateState(state, CAMERA_TOGGLE_DISABLED)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_camera_access_icon_off))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
index 9936d4951d00..ce5edb147d87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DndTileTest.kt
@@ -21,6 +21,7 @@ import android.content.ContextWrapper
import android.content.SharedPreferences
import android.os.Handler
import android.provider.Settings
+import android.provider.Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
import android.provider.Settings.Global.ZEN_MODE_OFF
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -28,19 +29,24 @@ import android.view.View
import androidx.test.filters.SmallTest
import com.android.internal.logging.MetricsLogger
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.classifier.FalsingManagerFake
import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.qs.QSHost
import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
import com.android.systemui.statusbar.policy.ZenModeController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.android.systemui.util.settings.SecureSettings
import com.google.common.truth.Truth.assertThat
+import java.io.File
import org.junit.After
import org.junit.Before
import org.junit.Test
@@ -49,9 +55,8 @@ import org.mockito.Mock
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations
-import java.io.File
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -65,22 +70,31 @@ class DndTileTest : SysuiTestCase() {
@Mock
private lateinit var qsHost: QSHost
+
@Mock
private lateinit var metricsLogger: MetricsLogger
+
@Mock
private lateinit var statusBarStateController: StatusBarStateController
+
@Mock
private lateinit var activityStarter: ActivityStarter
+
@Mock
private lateinit var qsLogger: QSLogger
+
@Mock
private lateinit var uiEventLogger: UiEventLogger
+
@Mock
private lateinit var zenModeController: ZenModeController
+
@Mock
private lateinit var sharedPreferences: SharedPreferences
+
@Mock
private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
@Mock
private lateinit var hostDialog: Dialog
@@ -173,7 +187,7 @@ class DndTileTest : SysuiTestCase() {
tile.handleClick(view)
testableLooper.processAllMessages()
- verify(dialogLaunchAnimator).showFromView(any(), eq(view), anyBoolean())
+ verify(dialogLaunchAnimator).showFromView(any(), eq(view), nullable(), anyBoolean())
}
@Test
@@ -187,6 +201,26 @@ class DndTileTest : SysuiTestCase() {
tile.handleClick(view)
testableLooper.processAllMessages()
- verify(dialogLaunchAnimator, never()).showFromView(any(), any(), anyBoolean())
+ verify(dialogLaunchAnimator, never()).showFromView(any(), any(), nullable(), anyBoolean())
+ }
+
+ @Test
+ fun testIcon_whenDndModeOff_isOffState() {
+ whenever(zenModeController.zen).thenReturn(ZEN_MODE_OFF)
+ val state = QSTile.BooleanState()
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_dnd_icon_off))
+ }
+
+ @Test
+ fun testIcon_whenDndModeOn_isOnState() {
+ whenever(zenModeController.zen).thenReturn(ZEN_MODE_NO_INTERRUPTIONS)
+ val state = QSTile.BooleanState()
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_dnd_icon_on))
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
new file mode 100644
index 000000000000..a13bece4b489
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DreamTileTest.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertFalse;
+import static junit.framework.TestCase.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.service.dreams.IDreamManager;
+import android.service.quicksettings.Tile;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.logging.MetricsLogger;
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.classifier.FalsingManagerFake;
+import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.qs.QSTileHost;
+import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
+import com.android.systemui.settings.UserTracker;
+import com.android.systemui.util.settings.FakeSettings;
+import com.android.systemui.util.settings.SecureSettings;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+public class DreamTileTest extends SysuiTestCase {
+
+ @Mock
+ private ActivityStarter mActivityStarter;
+ @Mock
+ private QSTileHost mHost;
+ @Mock
+ private MetricsLogger mMetricsLogger;
+ @Mock
+ private QSLogger mQSLogger;
+ @Mock
+ private StatusBarStateController mStatusBarStateController;
+ @Mock
+ private IDreamManager mDreamManager;
+ @Mock
+ private BroadcastDispatcher mBroadcastDispatcher;
+ @Mock
+ private UserTracker mUserTracker;
+
+ private TestableLooper mTestableLooper;
+
+ private DreamTile mTile;
+
+ private SecureSettings mSecureSettings;
+
+ private static final ComponentName COLORS_DREAM_COMPONENT_NAME = new ComponentName(
+ "com.android.dreams", ".Colors");
+
+ private static final int DEFAULT_USER = 0;
+
+ private final String mExpectedTileLabel = mContext.getResources().getString(
+ R.string.quick_settings_screensaver_label);
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+ mSecureSettings = new FakeSettings();
+ mTestableLooper = TestableLooper.get(this);
+
+ when(mHost.getUserId()).thenReturn(DEFAULT_USER);
+ when(mHost.getContext()).thenReturn(mContext);
+
+ mTile = spy(constructTileForTest(true, false));
+
+ mTestableLooper.processAllMessages();
+ mTile.initialize();
+ }
+
+ @Test
+ public void testNotAvailable() throws RemoteException {
+ // Should not be available if screensaver is disabled
+ setScreensaverEnabled(false);
+
+ mTile.refreshState();
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_UNAVAILABLE, mTile.getState().state);
+
+ // Should not be available if component is not set
+ mSecureSettings.putInt(Settings.Secure.SCREENSAVER_ENABLED, 1);
+ when(mDreamManager.getDreamComponents()).thenReturn(null);
+
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_UNAVAILABLE, mTile.getState().state);
+ assertEquals(mExpectedTileLabel, mTile.getState().contentDescription);
+ }
+
+ @Test
+ public void testInactiveWhenDreaming() throws RemoteException {
+ setScreensaverEnabled(true);
+
+ when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
+ COLORS_DREAM_COMPONENT_NAME
+ });
+ when(mDreamManager.isDreaming()).thenReturn(false);
+
+ mTile.refreshState();
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_INACTIVE, mTile.getState().state);
+ }
+
+ @Test
+ public void testActive() throws RemoteException {
+ setScreensaverEnabled(true);
+
+ when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
+ COLORS_DREAM_COMPONENT_NAME
+ });
+ when(mDreamManager.isDreaming()).thenReturn(true);
+
+ mTile.refreshState();
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_ACTIVE, mTile.getState().state);
+ }
+
+ @Test
+ public void testClick() throws RemoteException {
+ // Set the AOSP dream enabled as the base setup.
+ setScreensaverEnabled(true);
+ when(mDreamManager.getDreamComponents()).thenReturn(new ComponentName[]{
+ COLORS_DREAM_COMPONENT_NAME
+ });
+ when(mDreamManager.isDreaming()).thenReturn(false);
+
+ mTile.refreshState();
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_INACTIVE, mTile.getState().state);
+
+ // Now click
+ mTile.handleClick(null /* view */);
+
+ verify(mDreamManager).dream();
+
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ mTile.refreshState();
+ mTestableLooper.processAllMessages();
+ assertEquals(Tile.STATE_ACTIVE, mTile.getState().state);
+
+ // Click again to see that other method is called
+ mTile.handleClick(null /* view */);
+
+ verify(mDreamManager).awaken();
+ }
+
+ @Test
+ public void testContentDescription() {
+ assertEquals(mExpectedTileLabel, mTile.getContentDescription(null));
+
+ final String testDreamName = "MyDream";
+ assertEquals(mExpectedTileLabel + ", " + testDreamName,
+ mTile.getContentDescription(testDreamName));
+ }
+
+ @Test
+ public void testUserAvailability() {
+ DreamTile unsupportedTile = constructTileForTest(false, true);
+ assertFalse(unsupportedTile.isAvailable());
+
+ DreamTile supportedTileAllUsers = constructTileForTest(true, false);
+
+ UserHandle systemUserHandle = mock(UserHandle.class);
+ when(systemUserHandle.isSystem()).thenReturn(true);
+
+ UserHandle nonSystemUserHandle = mock(UserHandle.class);
+ when(nonSystemUserHandle.isSystem()).thenReturn(false);
+
+ when(mUserTracker.getUserHandle()).thenReturn(systemUserHandle);
+ assertTrue(supportedTileAllUsers.isAvailable());
+ when(mUserTracker.getUserHandle()).thenReturn(nonSystemUserHandle);
+ assertTrue(supportedTileAllUsers.isAvailable());
+
+ DreamTile supportedTileOnlySystemUser = constructTileForTest(true, true);
+ when(mUserTracker.getUserHandle()).thenReturn(systemUserHandle);
+ assertTrue(supportedTileOnlySystemUser.isAvailable());
+ when(mUserTracker.getUserHandle()).thenReturn(nonSystemUserHandle);
+ assertFalse(supportedTileOnlySystemUser.isAvailable());
+ }
+
+ @Test
+ public void testIconDockState() {
+ final DreamTile dockedTile = constructTileForTest(true, false);
+
+ final ArgumentCaptor<BroadcastReceiver> receiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ dockedTile.handleSetListening(true);
+ verify(mBroadcastDispatcher).registerReceiver(receiverCaptor.capture(), any());
+ final BroadcastReceiver receiver = receiverCaptor.getValue();
+
+ Intent dockIntent = new Intent(Intent.ACTION_DOCK_EVENT);
+ dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_DESK);
+ receiver.onReceive(mContext, dockIntent);
+ mTestableLooper.processAllMessages();
+ assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver),
+ dockedTile.getState().icon);
+
+ dockIntent.putExtra(Intent.EXTRA_DOCK_STATE, Intent.EXTRA_DOCK_STATE_UNDOCKED);
+ receiver.onReceive(mContext, dockIntent);
+ mTestableLooper.processAllMessages();
+ assertEquals(QSTileImpl.ResourceIcon.get(R.drawable.ic_qs_screen_saver_undocked),
+ dockedTile.getState().icon);
+ }
+
+ private void setScreensaverEnabled(boolean enabled) {
+ mSecureSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, enabled ? 1 : 0,
+ DEFAULT_USER);
+ }
+
+ private DreamTile constructTileForTest(boolean dreamSupported,
+ boolean dreamOnlyEnabledForSystemUser) {
+ return new DreamTile(
+ mHost,
+ mTestableLooper.getLooper(),
+ new Handler(mTestableLooper.getLooper()),
+ new FalsingManagerFake(),
+ mMetricsLogger,
+ mStatusBarStateController,
+ mActivityStarter,
+ mQSLogger,
+ mDreamManager,
+ mSecureSettings,
+ mBroadcastDispatcher,
+ mUserTracker,
+ dreamSupported, dreamOnlyEnabledForSystemUser);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
new file mode 100644
index 000000000000..d2bbc8cfac39
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.content.Context
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSTileHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.statusbar.policy.LocationController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class LocationTileTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var mockContext: Context
+ @Mock
+ private lateinit var qsLogger: QSLogger
+ @Mock
+ private lateinit var qsHost: QSTileHost
+ @Mock
+ private lateinit var metricsLogger: MetricsLogger
+ private val falsingManager = FalsingManagerFake()
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var locationController: LocationController
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+
+ private val uiEventLogger = UiEventLoggerFake()
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var tile: LocationTile
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ `when`(qsHost.uiEventLogger).thenReturn(uiEventLogger)
+ `when`(qsHost.context).thenReturn(mockContext)
+
+ tile = LocationTile(qsHost,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ falsingManager,
+ metricsLogger,
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ locationController,
+ keyguardStateController)
+ }
+
+ @Test
+ fun testIcon_whenDisabled_isOffState() {
+ val state = QSTile.BooleanState()
+ `when`(locationController.isLocationEnabled).thenReturn(false)
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_off))
+ }
+
+ @Test
+ fun testIcon_whenEnabled_isOnState() {
+ val state = QSTile.BooleanState()
+ `when`(locationController.isLocationEnabled).thenReturn(true)
+
+ tile.handleUpdateState(state, /* arg= */ null)
+
+ assertThat(state.icon)
+ .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
new file mode 100644
index 000000000000..1ab601ce3ebe
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/MicrophoneToggleTileTest.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.MetricsLogger
+import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingManagerFake
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.qs.QSHost
+import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class MicrophoneToggleTileTest : SysuiTestCase() {
+ companion object {
+ /* isBlocked */
+ const val MICROPHONE_TOGGLE_ENABLED: Boolean = false
+ const val MICROPHONE_TOGGLE_DISABLED: Boolean = true
+ }
+
+ @Mock
+ private lateinit var host: QSHost
+ @Mock
+ private lateinit var metricsLogger: MetricsLogger
+ @Mock
+ private lateinit var statusBarStateController: StatusBarStateController
+ @Mock
+ private lateinit var activityStarter: ActivityStarter
+ @Mock
+ private lateinit var qsLogger: QSLogger
+ @Mock
+ private lateinit var privacyController: IndividualSensorPrivacyController
+ @Mock
+ private lateinit var keyguardStateController: KeyguardStateController
+
+ private lateinit var testableLooper: TestableLooper
+ private lateinit var tile: MicrophoneToggleTile
+ private val uiEventLogger: UiEventLogger = UiEventLoggerFake()
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ testableLooper = TestableLooper.get(this)
+ whenever(host.context).thenReturn(mContext)
+ whenever(host.uiEventLogger).thenReturn(uiEventLogger)
+
+ tile = MicrophoneToggleTile(host,
+ testableLooper.looper,
+ Handler(testableLooper.looper),
+ metricsLogger,
+ FalsingManagerFake(),
+ statusBarStateController,
+ activityStarter,
+ qsLogger,
+ privacyController,
+ keyguardStateController)
+ }
+
+ @Test
+ fun testIcon_whenMicrophoneAccessEnabled_isOnState() {
+ val state = QSTile.BooleanState()
+
+ tile.handleUpdateState(state, MICROPHONE_TOGGLE_ENABLED)
+
+ assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_on))
+ }
+
+ @Test
+ fun testIcon_whenMicrophoneAccessDisabled_isOffState() {
+ val state = QSTile.BooleanState()
+
+ tile.handleUpdateState(state, MICROPHONE_TOGGLE_DISABLED)
+
+ assertThat(state.icon).isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_mic_access_off))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
index 55c51b255ac7..e9dfd3ed182b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RotationLockTileTest.java
@@ -36,9 +36,11 @@ import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.plugins.ActivityStarter;
+import com.android.systemui.plugins.qs.QSTile;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.qs.QSTileHost;
import com.android.systemui.qs.logging.QSLogger;
+import com.android.systemui.qs.tileimpl.QSTileImpl;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.DeviceStateRotationLockSettingController;
import com.android.systemui.statusbar.policy.RotationLockController;
@@ -194,6 +196,26 @@ public class RotationLockTileTest extends SysuiTestCase {
assertEquals("", mLockTile.getState().secondaryLabel.toString());
}
+ @Test
+ public void testIcon_whenDisabled_isOffState() {
+ QSTile.BooleanState state = new QSTile.BooleanState();
+ disableAutoRotation();
+
+ mLockTile.handleUpdateState(state, /* arg= */ null);
+
+ assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_off));
+ }
+
+ @Test
+ public void testIcon_whenEnabled_isOnState() {
+ QSTile.BooleanState state = new QSTile.BooleanState();
+ enableAutoRotation();
+
+ mLockTile.handleUpdateState(state, /* arg= */ null);
+
+ assertEquals(state.icon, QSTileImpl.ResourceIcon.get(R.drawable.qs_auto_rotate_icon_on));
+ }
+
private void enableAutoRotation() {
when(mRotationPolicyWrapper.isRotationLocked()).thenReturn(false);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 030c65a0576a..9d908fdfb976 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -42,11 +42,12 @@ import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatcher
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
+import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.argThat
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -82,19 +83,19 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
`when`(dialog.context).thenReturn(mContext)
controller = UserSwitchDialogController(
- { userDetailViewAdapter },
- activityStarter,
- falsingManager,
- dialogLaunchAnimator,
- uiEventLogger,
- { dialog }
+ { userDetailViewAdapter },
+ activityStarter,
+ falsingManager,
+ dialogLaunchAnimator,
+ uiEventLogger,
+ { dialog }
)
}
@Test
fun showDialog_callsDialogShow() {
controller.showDialog(launchView)
- verify(dialogLaunchAnimator).showFromView(dialog, launchView)
+ verify(dialogLaunchAnimator).showFromView(eq(dialog), eq(launchView), any(), anyBoolean())
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_DETAIL_OPEN)
}
@@ -140,11 +141,11 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
clickCaptor.value.onClick(dialog, DialogInterface.BUTTON_NEUTRAL)
verify(activityStarter)
- .postStartActivityDismissingKeyguard(
- argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
- eq(0),
- eq(null)
- )
+ .postStartActivityDismissingKeyguard(
+ argThat(IntentMatcher(Settings.ACTION_USER_SETTINGS)),
+ eq(0),
+ eq(null)
+ )
verify(uiEventLogger).log(QSUserSwitcherEvent.QS_USER_MORE_SETTINGS)
}
@@ -167,4 +168,4 @@ class UserSwitchDialogControllerTest : SysuiTestCase() {
return argument?.action == action
}
}
-} \ No newline at end of file
+}
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 91cafead596c..b05d9a31b475 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingServiceTest.java
@@ -16,18 +16,21 @@
package com.android.systemui.screenrecord;
+import static org.junit.Assert.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.app.Notification;
import android.app.NotificationManager;
import android.content.Intent;
+import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
@@ -66,6 +69,8 @@ public class RecordingServiceTest extends SysuiTestCase {
@Mock
private Executor mExecutor;
@Mock
+ private Handler mHandler;
+ @Mock
private UserContextProvider mUserContextTracker;
private KeyguardDismissUtil mKeyguardDismissUtil = new KeyguardDismissUtil() {
public void executeWhenUnlocked(ActivityStarter.OnDismissAction action,
@@ -79,8 +84,8 @@ public class RecordingServiceTest extends SysuiTestCase {
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
- mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mUiEventLogger,
- mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
+ mRecordingService = Mockito.spy(new RecordingService(mController, mExecutor, mHandler,
+ mUiEventLogger, mNotificationManager, mUserContextTracker, mKeyguardDismissUtil));
// Return actual context info
doReturn(mContext).when(mRecordingService).getApplicationContext();
@@ -143,4 +148,54 @@ public class RecordingServiceTest extends SysuiTestCase {
// Then the state is set to not recording
verify(mController).updateState(false);
}
+
+ @Test
+ public void testOnSystemRequestedStop_recordingInProgress_endsRecording() throws IOException {
+ doReturn(true).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ verify(mScreenMediaRecorder).end();
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recordingInProgress_updatesState() {
+ doReturn(true).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ verify(mController).updateState(false);
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recordingIsNotInProgress_doesNotEndRecording()
+ throws IOException {
+ doReturn(false).when(mController).isRecording();
+
+ mRecordingService.onStopped();
+
+ verify(mScreenMediaRecorder, never()).end();
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recorderEndThrowsRuntimeException_releasesRecording()
+ throws IOException {
+ doReturn(true).when(mController).isRecording();
+ doThrow(new RuntimeException()).when(mScreenMediaRecorder).end();
+
+ mRecordingService.onStopped();
+
+ verify(mScreenMediaRecorder).release();
+ }
+
+ @Test
+ public void testOnSystemRequestedStop_recorderEndThrowsOOMError_releasesRecording()
+ throws IOException {
+ doReturn(true).when(mController).isRecording();
+ doThrow(new OutOfMemoryError()).when(mScreenMediaRecorder).end();
+
+ assertThrows(Throwable.class, () -> mRecordingService.onStopped());
+
+ verify(mScreenMediaRecorder).release();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
index 7ab49584f0e3..e1eda117177d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ActionProxyReceiverTest.java
@@ -32,7 +32,6 @@ import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.app.PendingIntent;
-import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.testing.AndroidTestingRunner;
@@ -115,7 +114,7 @@ public class ActionProxyReceiverTest extends SysuiTestCase {
actionProxyReceiver.onReceive(mContext, mIntent);
verify(mMockScreenshotSmartActions, never())
- .notifyScreenshotAction(any(Context.class), anyString(), anyString(), anyBoolean(),
+ .notifyScreenshotAction(anyString(), anyString(), anyBoolean(),
any(Intent.class));
}
@@ -129,7 +128,7 @@ public class ActionProxyReceiverTest extends SysuiTestCase {
actionProxyReceiver.onReceive(mContext, mIntent);
verify(mMockScreenshotSmartActions).notifyScreenshotAction(
- mContext, testId, ACTION_TYPE_SHARE, false, null);
+ testId, ACTION_TYPE_SHARE, false, null);
}
private ActionProxyReceiver constructActionProxyReceiver(boolean withStatusBar) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
index 664c125533d1..d58f47a0469a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DeleteScreenshotReceiverTest.java
@@ -31,7 +31,6 @@ import static org.mockito.Mockito.verify;
import android.content.ContentResolver;
import android.content.ContentValues;
-import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
@@ -81,7 +80,7 @@ public class DeleteScreenshotReceiverTest extends SysuiTestCase {
verify(mMockExecutor, never()).execute(any(Runnable.class));
verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
- any(Context.class), any(String.class), any(String.class), anyBoolean(),
+ any(String.class), any(String.class), anyBoolean(),
any(Intent.class));
}
@@ -113,7 +112,7 @@ public class DeleteScreenshotReceiverTest extends SysuiTestCase {
}
// ensure smart actions not called by default
- verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(any(Context.class),
+ verify(mMockScreenshotSmartActions, never()).notifyScreenshotAction(
any(String.class), any(String.class), anyBoolean(), any(Intent.class));
}
@@ -129,7 +128,7 @@ public class DeleteScreenshotReceiverTest extends SysuiTestCase {
mDeleteScreenshotReceiver.onReceive(mContext, intent);
verify(mMockExecutor).execute(any(Runnable.class));
- verify(mMockScreenshotSmartActions).notifyScreenshotAction(mContext, testId,
+ verify(mMockScreenshotSmartActions).notifyScreenshotAction(testId,
ACTION_TYPE_DELETE, false, null);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
index 3d658ec8e811..69b7b88b0524 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ScreenshotNotificationSmartActionsTest.java
@@ -43,7 +43,6 @@ import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
-import com.android.systemui.SystemUIFactory;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.screenshot.ScreenshotController.SavedImageData.ActionTransition;
@@ -71,7 +70,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
public void setup() {
mSmartActionsProvider = mock(
ScreenshotNotificationSmartActionsProvider.class);
- mScreenshotSmartActions = new ScreenshotSmartActions();
+ mScreenshotSmartActions = new ScreenshotSmartActions(() -> mSmartActionsProvider);
mHandler = mock(Handler.class);
}
@@ -158,8 +157,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
Bitmap bitmap = mock(Bitmap.class);
when(bitmap.getConfig()).thenReturn(Bitmap.Config.HARDWARE);
ScreenshotNotificationSmartActionsProvider actionsProvider =
- SystemUIFactory.getInstance().createScreenshotNotificationSmartActionsProvider(
- mContext, null, mHandler);
+ new ScreenshotNotificationSmartActionsProvider();
CompletableFuture<List<Notification.Action>> smartActionsFuture =
mScreenshotSmartActions.getSmartActionsFuture("", null, bitmap,
actionsProvider, REGULAR_SMART_ACTIONS,
@@ -183,7 +181,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
- ActionTransition::new);
+ ActionTransition::new, mSmartActionsProvider);
Notification.Action shareAction = task.createShareAction(mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png")).get().action;
@@ -211,7 +209,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
- ActionTransition::new);
+ ActionTransition::new, mSmartActionsProvider);
Notification.Action editAction = task.createEditAction(mContext, mContext.getResources(),
Uri.parse("Screenshot_123.png")).get().action;
@@ -239,7 +237,7 @@ public class ScreenshotNotificationSmartActionsTest extends SysuiTestCase {
data.mActionsReadyListener = null;
SaveImageInBackgroundTask task =
new SaveImageInBackgroundTask(mContext, null, mScreenshotSmartActions, data,
- ActionTransition::new);
+ ActionTransition::new, mSmartActionsProvider);
Notification.Action deleteAction = task.createDeleteAction(mContext,
mContext.getResources(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
index 011e6b7b1071..83c949793447 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/SmartActionsReceiverTest.java
@@ -74,6 +74,6 @@ public class SmartActionsReceiverTest extends SysuiTestCase {
verify(mMockPendingIntent).send(
eq(mContext), eq(0), isNull(), isNull(), isNull(), isNull(), any(Bundle.class));
verify(mMockScreenshotSmartActions).notifyScreenshotAction(
- mContext, testId, testActionType, true, intent);
+ testId, testActionType, true, intent);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
new file mode 100644
index 000000000000..6d9b01e28aa4
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/settings/UserFileManagerImplTest.kt
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.settings
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.IntentFilter
+import android.os.Environment
+import android.os.UserManager
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executor
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.isNull
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class UserFileManagerImplTest : SysuiTestCase() {
+ companion object {
+ const val TEST_FILE_NAME = "abc.txt"
+ }
+
+ lateinit var userFileManager: UserFileManagerImpl
+ lateinit var backgroundExecutor: FakeExecutor
+ @Mock
+ lateinit var userManager: UserManager
+ @Mock
+ lateinit var broadcastDispatcher: BroadcastDispatcher
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ backgroundExecutor = FakeExecutor(FakeSystemClock())
+ userFileManager = UserFileManagerImpl(context, userManager,
+ broadcastDispatcher, backgroundExecutor)
+ }
+
+ @After
+ fun end() {
+ val dir = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID)
+ dir.deleteRecursively()
+ }
+
+ @Test
+ fun testGetFile() {
+ assertThat(userFileManager.getFile(TEST_FILE_NAME, 0).path)
+ .isEqualTo("${context.filesDir}/$TEST_FILE_NAME")
+ assertThat(userFileManager.getFile(TEST_FILE_NAME, 11).path)
+ .isEqualTo("${context.filesDir}/${UserFileManagerImpl.ID}/11/files/$TEST_FILE_NAME")
+ }
+
+ @Test
+ fun testGetSharedPreferences() {
+ val secondarySharedPref = userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 11)
+ val secondaryUserDir = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ UserFileManagerImpl.SHARED_PREFS,
+ TEST_FILE_NAME
+ )
+
+ assertThat(secondarySharedPref).isNotNull()
+ assertThat(secondaryUserDir.exists())
+ assertThat(userFileManager.getSharedPreferences(TEST_FILE_NAME, 0, 0))
+ .isNotEqualTo(secondarySharedPref)
+ }
+
+ @Test
+ fun testUserFileManagerStart() {
+ val userFileManager = spy(userFileManager)
+ userFileManager.start()
+ verify(userFileManager).clearDeletedUserData()
+ verify(broadcastDispatcher).registerReceiver(any(BroadcastReceiver::class.java),
+ any(IntentFilter::class.java),
+ any(Executor::class.java), isNull(), eq(Context.RECEIVER_EXPORTED), isNull())
+ }
+
+ @Test
+ fun testClearDeletedUserData() {
+ val dir = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files"
+ )
+ dir.mkdirs()
+ val file = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ val secondaryUserDir = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ )
+ file.createNewFile()
+ assertThat(secondaryUserDir.exists()).isTrue()
+ assertThat(file.exists()).isTrue()
+ userFileManager.clearDeletedUserData()
+ assertThat(backgroundExecutor.runAllReady()).isGreaterThan(0)
+ verify(userManager).aliveUsers
+ assertThat(secondaryUserDir.exists()).isFalse()
+ assertThat(file.exists()).isFalse()
+ }
+
+ @Test
+ fun testEnsureParentDirExists() {
+ val file = Environment.buildPath(
+ context.filesDir,
+ UserFileManagerImpl.ID,
+ "11",
+ "files",
+ TEST_FILE_NAME
+ )
+ assertThat(file.parentFile.exists()).isFalse()
+ userFileManager.ensureParentDirExists(file)
+ assertThat(file.parentFile.exists()).isTrue()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
new file mode 100644
index 000000000000..0ce9056dc1d1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/CombinedShadeHeaderConstraintsTest.kt
@@ -0,0 +1,328 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
+import androidx.constraintlayout.widget.ConstraintSet.START
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class CombinedShadeHeaderConstraintsTest : SysuiTestCase() {
+
+ private lateinit var qqsConstraint: ConstraintSet
+ private lateinit var qsConstraint: ConstraintSet
+ private lateinit var largeScreenConstraint: ConstraintSet
+
+ @Before
+ fun setUp() {
+ qqsConstraint = ConstraintSet().apply {
+ load(context, context.resources.getXml(R.xml.qqs_header))
+ }
+ qsConstraint = ConstraintSet().apply {
+ load(context, context.resources.getXml(R.xml.qs_header_new))
+ }
+ largeScreenConstraint = ConstraintSet().apply {
+ load(context, context.resources.getXml(R.xml.large_screen_shade_header))
+ }
+ }
+
+ @Test
+ fun testEdgeElementsAlignedWithGuide_qqs() {
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(R.id.begin_guide)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+ .isEqualTo(1f)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias)
+ .isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testClockScale() {
+ with(qqsConstraint.getConstraint(R.id.clock)) {
+ assertThat(transform.scaleX).isEqualTo(1f)
+ assertThat(transform.scaleY).isEqualTo(1f)
+ }
+ with(qsConstraint.getConstraint(R.id.clock)) {
+ assertThat(transform.scaleX).isGreaterThan(1f)
+ assertThat(transform.scaleY).isGreaterThan(1f)
+ }
+ }
+
+ @Test
+ fun testEdgeElementsAlignedWithEdgeOrGuide_qs() {
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.date).layout.startToStart).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.date).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.endToEnd)
+ .isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).layout.horizontalBias)
+ .isEqualTo(1f)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.endToEnd)
+ .isEqualTo(R.id.end_guide)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testEdgeElementsAlignedWithEdge_largeScreen() {
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.clock).layout.startToStart).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.clock).layout.horizontalBias).isEqualTo(0f)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.endToEnd).isEqualTo(PARENT_ID)
+ assertThat(getConstraint(R.id.privacy_container).layout.horizontalBias).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testCarrierAlpha() {
+ assertThat(qqsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(0f)
+ assertThat(qsConstraint.getConstraint(R.id.carrier_group).propertySet.alpha).isEqualTo(1f)
+ assertThat(largeScreenConstraint.getConstraint(R.id.carrier_group).propertySet.alpha)
+ .isEqualTo(1f)
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityConstraints_notVisible() {
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .privacyChipVisibilityConstraints(false)
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityConstraints_visible() {
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .privacyChipVisibilityConstraints(true)
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(0f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(0f)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+
+ with(largeScreenConstraint) {
+ assertThat(getConstraint(R.id.statusIcons).propertySet.alpha).isEqualTo(1f)
+ assertThat(getConstraint(R.id.batteryRemainingIcon).propertySet.alpha).isEqualTo(1f)
+ }
+ }
+
+ @Test
+ fun testEmptyCutoutConstraints() {
+ val changes = CombinedShadeHeadersConstraintManagerImpl.emptyCutoutConstraints()
+ changes()
+
+ // QS and Large Screen don't change with cutouts.
+ assertThat(changes.qsConstraintsChanges).isNull()
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+
+ with(qqsConstraint) {
+ // In this case, the date is constrained on the end by a Barrier determined by either
+ // privacy or statusIcons
+ assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.barrier)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd).isEqualTo(R.id.date)
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd).isEqualTo(R.id.date)
+ assertThat(getConstraint(R.id.barrier).layout.mReferenceIds).asList().containsExactly(
+ R.id.statusIcons,
+ R.id.privacy_container
+ )
+ assertThat(getConstraint(R.id.barrier).layout.mBarrierDirection).isEqualTo(START)
+ }
+ }
+
+ @Test
+ fun testGuidesAreSetInCorrectPosition_largeCutoutSmallerPadding() {
+ val cutoutStart = 100
+ val padding = 10
+ val cutoutEnd = 30
+ val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
+ cutoutStart,
+ padding,
+ cutoutEnd,
+ padding
+ )
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+ .isEqualTo(cutoutStart - padding)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+ .isEqualTo(cutoutEnd - padding)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin)
+ .isEqualTo(cutoutStart - padding)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd)
+ .isEqualTo(cutoutEnd - padding)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ @Test
+ fun testGuidesAreSetInCorrectPosition_smallCutoutLargerPadding() {
+ val cutoutStart = 5
+ val padding = 10
+ val cutoutEnd = 10
+
+ val changes = CombinedShadeHeadersConstraintManagerImpl.edgesGuidelinesConstraints(
+ cutoutStart,
+ padding,
+ cutoutEnd,
+ padding
+ )
+ changes()
+
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.begin_guide).layout.guideBegin).isEqualTo(0)
+ assertThat(getConstraint(R.id.end_guide).layout.guideEnd).isEqualTo(0)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ @Test
+ fun testCenterCutoutConstraints_ltr() {
+ val offsetFromEdge = 400
+ val rtl = false
+
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .centerCutoutConstraints(rtl, offsetFromEdge)
+ changes()
+
+ // In LTR, center_left is towards the start and center_right is towards the end
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isEqualTo(R.id.center_right)
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_right)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideBegin).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideEnd).isEqualTo(offsetFromEdge)
+
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_right)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ @Test
+ fun testCenterCutoutConstraints_rtl() {
+ val offsetFromEdge = 400
+ val rtl = true
+
+ val changes = CombinedShadeHeadersConstraintManagerImpl
+ .centerCutoutConstraints(rtl, offsetFromEdge)
+ changes()
+
+ // In RTL, center_left is towards the end and center_right is towards the start
+ with(qqsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isEqualTo(R.id.center_right)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_left)
+ }
+
+ with(qsConstraint) {
+ assertThat(getConstraint(R.id.center_left).layout.guideEnd).isEqualTo(offsetFromEdge)
+ assertThat(getConstraint(R.id.center_right).layout.guideBegin).isEqualTo(offsetFromEdge)
+
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.date).layout.endToStart).isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_left)
+ assertThat(getConstraint(R.id.statusIcons).layout.startToEnd)
+ .isNotEqualTo(R.id.center_right)
+
+ assertThat(getConstraint(R.id.privacy_container).layout.startToEnd)
+ .isEqualTo(R.id.center_left)
+ }
+
+ assertThat(changes.largeScreenConstraintsChanges).isNull()
+ }
+
+ private operator fun ConstraintsChanges.invoke() {
+ qqsConstraintsChanges?.invoke(qqsConstraint)
+ qsConstraintsChanges?.invoke(qsConstraint)
+ largeScreenConstraintsChanges?.invoke(largeScreenConstraint)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
new file mode 100644
index 000000000000..9b2e085560a1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangeTest.kt
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConstraintChangeTest : SysuiTestCase() {
+
+ @Test
+ fun testSumNonNull() {
+ val mock1: ConstraintChange = mock()
+ val mock2: ConstraintChange = mock()
+
+ val constraintSet = ConstraintSet()
+
+ val sum = mock1 + mock2
+ sum?.invoke(constraintSet)
+
+ val inOrder = inOrder(mock1, mock2)
+ inOrder.verify(mock1).invoke(constraintSet)
+ inOrder.verify(mock2).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumThisNull() {
+ val mock: ConstraintChange = mock()
+ val constraintSet = ConstraintSet()
+
+ val sum = (null as? ConstraintChange?) + mock
+ sum?.invoke(constraintSet)
+
+ verify(mock).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumThisNull_notWrapped() {
+ val change: ConstraintChange = {}
+
+ val sum = (null as? ConstraintChange?) + change
+ assertThat(sum).isSameInstanceAs(change)
+ }
+
+ @Test
+ fun testSumOtherNull() {
+ val mock: ConstraintChange = mock()
+ val constraintSet = ConstraintSet()
+
+ val sum = mock + (null as? ConstraintChange?)
+ sum?.invoke(constraintSet)
+
+ verify(mock).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumOtherNull_notWrapped() {
+ val change: ConstraintChange = {}
+
+ val sum = change + (null as? ConstraintChange?)
+ assertThat(sum).isSameInstanceAs(change)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
new file mode 100644
index 000000000000..0abb08427478
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/ConstraintChangesTest.kt
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.inOrder
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ConstraintChangesTest : SysuiTestCase() {
+
+ @Test
+ fun testSumWithoutNulls() {
+ val mockQQS1: ConstraintChange = mock()
+ val mockQS1: ConstraintChange = mock()
+ val mockLS1: ConstraintChange = mock()
+ val mockQQS2: ConstraintChange = mock()
+ val mockQS2: ConstraintChange = mock()
+ val mockLS2: ConstraintChange = mock()
+
+ val changes1 = ConstraintsChanges(mockQQS1, mockQS1, mockLS1)
+ val changes2 = ConstraintsChanges(mockQQS2, mockQS2, mockLS2)
+
+ val sum = changes1 + changes2
+
+ val constraintSet = ConstraintSet()
+ sum.qqsConstraintsChanges?.invoke(constraintSet)
+ sum.qsConstraintsChanges?.invoke(constraintSet)
+ sum.largeScreenConstraintsChanges?.invoke(constraintSet)
+
+ val inOrder = inOrder(mockQQS1, mockQS1, mockLS1, mockQQS2, mockQS2, mockLS2)
+
+ inOrder.verify(mockQQS1).invoke(constraintSet)
+ inOrder.verify(mockQQS2).invoke(constraintSet)
+ inOrder.verify(mockQS1).invoke(constraintSet)
+ inOrder.verify(mockQS2).invoke(constraintSet)
+ inOrder.verify(mockLS1).invoke(constraintSet)
+ inOrder.verify(mockLS2).invoke(constraintSet)
+ }
+
+ @Test
+ fun testSumWithSomeNulls() {
+ val mockQQS: ConstraintChange = mock()
+ val mockQS: ConstraintChange = mock()
+
+ val changes1 = ConstraintsChanges(mockQQS, null, null)
+ val changes2 = ConstraintsChanges(null, mockQS, null)
+
+ val sum = changes1 + changes2
+
+ assertThat(sum.qqsConstraintsChanges).isSameInstanceAs(mockQQS)
+ assertThat(sum.qsConstraintsChanges).isSameInstanceAs(mockQS)
+ assertThat(sum.largeScreenConstraintsChanges).isNull()
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
new file mode 100644
index 000000000000..ed1a13b36d6c
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerCombinedTest.kt
@@ -0,0 +1,659 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.content.Context
+import android.content.res.Resources
+import android.content.res.XmlResourceParser
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.DisplayCutout
+import android.view.View
+import android.view.WindowInsets
+import android.widget.TextView
+import androidx.constraintlayout.motion.widget.MotionLayout
+import androidx.constraintlayout.widget.ConstraintSet
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.ShadeInterpolation
+import com.android.systemui.battery.BatteryMeterView
+import com.android.systemui.battery.BatteryMeterViewController
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.qs.ChipVisibilityListener
+import com.android.systemui.qs.HeaderPrivacyIconsController
+import com.android.systemui.qs.carrier.QSCarrierGroup
+import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.LARGE_SCREEN_HEADER_TRANSITION_ID
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QQS_HEADER_CONSTRAINT
+import com.android.systemui.shade.LargeScreenShadeHeaderController.Companion.QS_HEADER_CONSTRAINT
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateView
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+private val EMPTY_CHANGES = ConstraintsChanges()
+
+/**
+ * Tests for [LargeScreenShadeHeaderController] when [Flags.COMBINED_QS_HEADERS] is `true`.
+ *
+ * Once that flag is removed, this class will be combined with
+ * [LargeScreenShadeHeaderControllerTest].
+ */
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class LargeScreenShadeHeaderControllerCombinedTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var statusIcons: StatusIconContainer
+ @Mock
+ private lateinit var statusBarIconController: StatusBarIconController
+ @Mock
+ private lateinit var qsCarrierGroupController: QSCarrierGroupController
+ @Mock
+ private lateinit var qsCarrierGroupControllerBuilder: QSCarrierGroupController.Builder
+ @Mock
+ private lateinit var featureFlags: FeatureFlags
+ @Mock
+ private lateinit var clock: TextView
+ @Mock
+ private lateinit var date: VariableDateView
+ @Mock
+ private lateinit var carrierGroup: QSCarrierGroup
+ @Mock
+ private lateinit var batteryMeterView: BatteryMeterView
+ @Mock
+ private lateinit var batteryMeterViewController: BatteryMeterViewController
+ @Mock
+ private lateinit var privacyIconsController: HeaderPrivacyIconsController
+ @Mock
+ private lateinit var insetsProvider: StatusBarContentInsetsProvider
+ @Mock
+ private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock
+ private lateinit var variableDateViewController: VariableDateViewController
+ @Mock
+ private lateinit var dumpManager: DumpManager
+ @Mock
+ private lateinit var combinedShadeHeadersConstraintManager:
+ CombinedShadeHeadersConstraintManager
+
+ @Mock
+ private lateinit var mockedContext: Context
+ @Mock(answer = Answers.RETURNS_MOCKS)
+ private lateinit var view: MotionLayout
+
+ @Mock
+ private lateinit var qqsConstraints: ConstraintSet
+ @Mock
+ private lateinit var qsConstraints: ConstraintSet
+ @Mock
+ private lateinit var largeScreenConstraints: ConstraintSet
+
+ @JvmField @Rule
+ val mockitoRule = MockitoJUnit.rule()
+ var viewVisibility = View.GONE
+
+ private lateinit var controller: LargeScreenShadeHeaderController
+ private lateinit var carrierIconSlots: List<String>
+ private val configurationController = FakeConfigurationController()
+
+ @Before
+ fun setUp() {
+ whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
+ whenever(clock.context).thenReturn(mockedContext)
+
+ whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
+ whenever(date.context).thenReturn(mockedContext)
+ whenever(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
+
+ whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
+ whenever<BatteryMeterView>(view.findViewById(R.id.batteryRemainingIcon))
+ .thenReturn(batteryMeterView)
+
+ whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
+ whenever(statusIcons.context).thenReturn(context)
+
+ whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
+ .thenReturn(qsCarrierGroupControllerBuilder)
+ whenever(qsCarrierGroupControllerBuilder.build()).thenReturn(qsCarrierGroupController)
+
+ whenever(view.context).thenReturn(context)
+ whenever(view.resources).thenReturn(context.resources)
+ whenever(view.setVisibility(ArgumentMatchers.anyInt())).then {
+ viewVisibility = it.arguments[0] as Int
+ null
+ }
+ whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+
+ whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(true)
+ whenever(featureFlags.isEnabled(Flags.NEW_HEADER)).thenReturn(true)
+
+ setUpDefaultInsets()
+ setUpMotionLayout(view)
+
+ controller = LargeScreenShadeHeaderController(
+ view,
+ statusBarIconController,
+ privacyIconsController,
+ insetsProvider,
+ configurationController,
+ variableDateViewControllerFactory,
+ batteryMeterViewController,
+ dumpManager,
+ featureFlags,
+ qsCarrierGroupControllerBuilder,
+ combinedShadeHeadersConstraintManager
+ )
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ controller.init()
+ carrierIconSlots = listOf(
+ context.getString(com.android.internal.R.string.status_bar_mobile))
+ }
+
+ @Test
+ fun testCorrectConstraints() {
+ val captor = ArgumentCaptor.forClass(XmlResourceParser::class.java)
+
+ verify(qqsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qqs_header)
+
+ verify(qsConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.qs_header_new)
+
+ verify(largeScreenConstraints).load(eq(context), capture(captor))
+ assertThat(captor.value.getResId()).isEqualTo(R.xml.large_screen_shade_header)
+ }
+
+ @Test
+ fun testControllersCreatedAndInitialized() {
+ verify(variableDateViewController).init()
+
+ verify(batteryMeterViewController).init()
+ verify(batteryMeterViewController).ignoreTunerUpdates()
+ verify(batteryMeterView).setPercentShowMode(BatteryMeterView.MODE_ESTIMATE)
+
+ val inOrder = inOrder(qsCarrierGroupControllerBuilder)
+ inOrder.verify(qsCarrierGroupControllerBuilder).setQSCarrierGroup(carrierGroup)
+ inOrder.verify(qsCarrierGroupControllerBuilder).build()
+ }
+
+ @Test
+ fun testClockPivotLtr() {
+ val width = 200
+ whenever(clock.width).thenReturn(width)
+ whenever(clock.isLayoutRtl).thenReturn(false)
+
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+
+ captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
+ verify(clock).pivotX = 0f
+ }
+
+ @Test
+ fun testClockPivotRtl() {
+ val width = 200
+ whenever(clock.width).thenReturn(width)
+ whenever(clock.isLayoutRtl).thenReturn(true)
+
+ val captor = ArgumentCaptor.forClass(View.OnLayoutChangeListener::class.java)
+ verify(clock).addOnLayoutChangeListener(capture(captor))
+
+ captor.value.onLayoutChange(clock, 0, 1, 2, 3, 4, 5, 6, 7)
+ verify(clock).pivotX = width.toFloat()
+ }
+
+ @Test
+ fun testShadeExpanded_true() {
+ // When shade is expanded, view should be visible regardless of largeScreenActive
+ controller.largeScreenActive = false
+ controller.qsVisible = true
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+
+ controller.largeScreenActive = true
+ assertThat(viewVisibility).isEqualTo(View.VISIBLE)
+ }
+
+ @Test
+ fun testShadeExpanded_false() {
+ // When shade is not expanded, view should be invisible regardless of largeScreenActive
+ controller.largeScreenActive = false
+ controller.qsVisible = false
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+
+ controller.largeScreenActive = true
+ assertThat(viewVisibility).isEqualTo(View.INVISIBLE)
+ }
+
+ @Test
+ fun testLargeScreenActive_true() {
+ controller.largeScreenActive = false // Make sure there's a change
+ clearInvocations(view)
+
+ controller.largeScreenActive = true
+
+ verify(view).setTransition(LARGE_SCREEN_HEADER_TRANSITION_ID)
+ }
+
+ @Test
+ fun testLargeScreenActive_false() {
+ controller.largeScreenActive = true // Make sure there's a change
+ clearInvocations(view)
+
+ controller.largeScreenActive = false
+
+ verify(view).setTransition(HEADER_TRANSITION_ID)
+ }
+
+ @Test
+ fun testShadeExpandedFraction() {
+ // View needs to be visible for this to actually take effect
+ controller.qsVisible = true
+
+ clearInvocations(view)
+ controller.shadeExpandedFraction = 0.3f
+ verify(view).alpha = ShadeInterpolation.getContentAlpha(0.3f)
+
+ clearInvocations(view)
+ controller.shadeExpandedFraction = 1f
+ verify(view).alpha = ShadeInterpolation.getContentAlpha(1f)
+
+ clearInvocations(view)
+ controller.shadeExpandedFraction = 0f
+ verify(view).alpha = ShadeInterpolation.getContentAlpha(0f)
+ }
+
+ @Test
+ fun testQsExpandedFraction_headerTransition() {
+ controller.qsVisible = true
+ controller.largeScreenActive = false
+
+ clearInvocations(view)
+ controller.qsExpandedFraction = 0.3f
+ verify(view).progress = 0.3f
+ }
+
+ @Test
+ fun testQsExpandedFraction_largeScreen() {
+ controller.qsVisible = true
+ controller.largeScreenActive = true
+
+ clearInvocations(view)
+ controller.qsExpandedFraction = 0.3f
+ verify(view, never()).progress = anyFloat()
+ }
+
+ @Test
+ fun testScrollY_headerTransition() {
+ controller.largeScreenActive = false
+
+ clearInvocations(view)
+ controller.qsScrollY = 20
+ verify(view).scrollY = 20
+ }
+
+ @Test
+ fun testScrollY_largeScreen() {
+ controller.largeScreenActive = true
+
+ clearInvocations(view)
+ controller.qsScrollY = 20
+ verify(view, never()).scrollY = anyInt()
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityChanged_visible_changesCorrectConstraints() {
+ val chipVisibleChanges = createMockConstraintChanges()
+ val chipNotVisibleChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
+ .thenReturn(chipVisibleChanges)
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
+ .thenReturn(chipNotVisibleChanges)
+
+ val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
+ verify(privacyIconsController).chipVisibilityListener = capture(captor)
+
+ captor.value.onChipVisibilityRefreshed(true)
+
+ verify(chipVisibleChanges.qqsConstraintsChanges)!!.invoke(qqsConstraints)
+ verify(chipVisibleChanges.qsConstraintsChanges)!!.invoke(qsConstraints)
+ verify(chipVisibleChanges.largeScreenConstraintsChanges)!!.invoke(largeScreenConstraints)
+
+ verify(chipNotVisibleChanges.qqsConstraintsChanges, never())!!.invoke(any())
+ verify(chipNotVisibleChanges.qsConstraintsChanges, never())!!.invoke(any())
+ verify(chipNotVisibleChanges.largeScreenConstraintsChanges, never())!!.invoke(any())
+ }
+
+ @Test
+ fun testPrivacyChipVisibilityChanged_notVisible_changesCorrectConstraints() {
+ val chipVisibleChanges = createMockConstraintChanges()
+ val chipNotVisibleChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(true))
+ .thenReturn(chipVisibleChanges)
+ whenever(combinedShadeHeadersConstraintManager.privacyChipVisibilityConstraints(false))
+ .thenReturn(chipNotVisibleChanges)
+
+ val captor = ArgumentCaptor.forClass(ChipVisibilityListener::class.java)
+ verify(privacyIconsController).chipVisibilityListener = capture(captor)
+
+ captor.value.onChipVisibilityRefreshed(false)
+
+ verify(chipVisibleChanges.qqsConstraintsChanges, never())!!.invoke(qqsConstraints)
+ verify(chipVisibleChanges.qsConstraintsChanges, never())!!.invoke(qsConstraints)
+ verify(chipVisibleChanges.largeScreenConstraintsChanges, never())!!
+ .invoke(largeScreenConstraints)
+
+ verify(chipNotVisibleChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(chipNotVisibleChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(chipNotVisibleChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testInsetsGuides_ltr() {
+ whenever(view.isLayoutRtl).thenReturn(false)
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ val (insetLeft, insetRight) = 30 to 40
+ val (paddingStart, paddingEnd) = 10 to 20
+ whenever(view.paddingStart).thenReturn(paddingStart)
+ whenever(view.paddingEnd).thenReturn(paddingEnd)
+
+ mockInsetsProvider(insetLeft to insetRight, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager)
+ .edgesGuidelinesConstraints(insetLeft, paddingStart, insetRight, paddingEnd)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testInsetsGuides_rtl() {
+ whenever(view.isLayoutRtl).thenReturn(true)
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ val (insetLeft, insetRight) = 30 to 40
+ val (paddingStart, paddingEnd) = 10 to 20
+ whenever(view.paddingStart).thenReturn(paddingStart)
+ whenever(view.paddingEnd).thenReturn(paddingEnd)
+
+ mockInsetsProvider(insetLeft to insetRight, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager)
+ .edgesGuidelinesConstraints(insetRight, paddingStart, insetLeft, paddingEnd)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testNullCutout() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(null))
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testEmptyCutout() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testCornerCutout_emptyRect() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, true)
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets())
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testCornerCutout_nonEmptyRect() {
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, true)
+
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(1, 2, 3, 4)))
+
+ verify(combinedShadeHeadersConstraintManager).emptyCutoutConstraints()
+ verify(combinedShadeHeadersConstraintManager, never())
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testTopCutout_ltr() {
+ val width = 100
+ val paddingLeft = 10
+ val paddingRight = 20
+ val cutoutWidth = 30
+
+ whenever(view.isLayoutRtl).thenReturn(false)
+ whenever(view.width).thenReturn(width)
+ whenever(view.paddingLeft).thenReturn(paddingLeft)
+ whenever(view.paddingRight).thenReturn(paddingRight)
+
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
+
+ verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
+ val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
+ verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(false, offset)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ @Test
+ fun testTopCutout_rtl() {
+ val width = 100
+ val paddingLeft = 10
+ val paddingRight = 20
+ val cutoutWidth = 30
+
+ whenever(view.isLayoutRtl).thenReturn(true)
+ whenever(view.width).thenReturn(width)
+ whenever(view.paddingLeft).thenReturn(paddingLeft)
+ whenever(view.paddingRight).thenReturn(paddingRight)
+
+ val captor = ArgumentCaptor.forClass(View.OnApplyWindowInsetsListener::class.java)
+ verify(view).setOnApplyWindowInsetsListener(capture(captor))
+ val mockConstraintsChanges = createMockConstraintChanges()
+
+ mockInsetsProvider(0 to 0, false)
+
+ whenever(combinedShadeHeadersConstraintManager
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+ ).thenReturn(mockConstraintsChanges)
+
+ captor.value.onApplyWindowInsets(view, createWindowInsets(Rect(0, 0, cutoutWidth, 1)))
+
+ verify(combinedShadeHeadersConstraintManager, never()).emptyCutoutConstraints()
+ val offset = (width - paddingLeft - paddingRight - cutoutWidth) / 2
+ verify(combinedShadeHeadersConstraintManager).centerCutoutConstraints(true, offset)
+
+ verify(mockConstraintsChanges.qqsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.qsConstraintsChanges)!!.invoke(any())
+ verify(mockConstraintsChanges.largeScreenConstraintsChanges)!!.invoke(any())
+ }
+
+ private fun createWindowInsets(
+ topCutout: Rect? = Rect()
+ ): WindowInsets {
+ val windowInsets: WindowInsets = mock()
+ val displayCutout: DisplayCutout = mock()
+ whenever(windowInsets.displayCutout)
+ .thenReturn(if (topCutout != null) displayCutout else null)
+ whenever(displayCutout.boundingRectTop).thenReturn(topCutout)
+
+ return windowInsets
+ }
+
+ private fun mockInsetsProvider(
+ insets: Pair<Int, Int> = 0 to 0,
+ cornerCutout: Boolean = false,
+ ) {
+ whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+ .thenReturn(insets.toAndroidPair())
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(cornerCutout)
+ }
+
+ private fun createMockConstraintChanges(): ConstraintsChanges {
+ return ConstraintsChanges(mock(), mock(), mock())
+ }
+
+ private fun XmlResourceParser.getResId(): Int {
+ return Resources.getAttributeSetSourceResId(this)
+ }
+
+ private fun setUpMotionLayout(motionLayout: MotionLayout) {
+ whenever(motionLayout.getConstraintSet(QQS_HEADER_CONSTRAINT)).thenReturn(qqsConstraints)
+ whenever(motionLayout.getConstraintSet(QS_HEADER_CONSTRAINT)).thenReturn(qsConstraints)
+ whenever(motionLayout.getConstraintSet(LARGE_SCREEN_HEADER_CONSTRAINT))
+ .thenReturn(largeScreenConstraints)
+ }
+
+ private fun setUpDefaultInsets() {
+ whenever(combinedShadeHeadersConstraintManager
+ .edgesGuidelinesConstraints(anyInt(), anyInt(), anyInt(), anyInt())
+ ).thenReturn(EMPTY_CHANGES)
+ whenever(combinedShadeHeadersConstraintManager.emptyCutoutConstraints())
+ .thenReturn(EMPTY_CHANGES)
+ whenever(combinedShadeHeadersConstraintManager
+ .centerCutoutConstraints(anyBoolean(), anyInt())
+ ).thenReturn(EMPTY_CHANGES)
+ whenever(combinedShadeHeadersConstraintManager
+ .privacyChipVisibilityConstraints(anyBoolean())
+ ).thenReturn(EMPTY_CHANGES)
+ whenever(insetsProvider.getStatusBarContentInsetsForCurrentRotation())
+ .thenReturn(Pair(0, 0).toAndroidPair())
+ whenever(insetsProvider.currentRotationHasCornerCutout()).thenReturn(false)
+ }
+
+ private fun<T, U> Pair<T, U>.toAndroidPair(): android.util.Pair<T, U> {
+ return android.util.Pair(first, second)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
index 80664013f95d..02b26dbbc32d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LargeScreenShadeHeaderControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/LargeScreenShadeHeaderControllerTest.kt
@@ -1,10 +1,8 @@
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
import android.app.StatusBarManager
import android.content.Context
-import android.content.res.TypedArray
import android.testing.AndroidTestingRunner
-import android.util.TypedValue.COMPLEX_UNIT_PX
import android.view.View
import android.widget.TextView
import androidx.test.filters.SmallTest
@@ -19,19 +17,24 @@ import com.android.systemui.flags.Flags
import com.android.systemui.qs.HeaderPrivacyIconsController
import com.android.systemui.qs.carrier.QSCarrierGroup
import com.android.systemui.qs.carrier.QSCarrierGroupController
+import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusIconContainer
import com.android.systemui.statusbar.policy.FakeConfigurationController
+import com.android.systemui.statusbar.policy.VariableDateViewController
+import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
+import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -49,10 +52,14 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
@Mock private lateinit var batteryMeterView: BatteryMeterView
@Mock private lateinit var batteryMeterViewController: BatteryMeterViewController
@Mock private lateinit var privacyIconsController: HeaderPrivacyIconsController
+ @Mock private lateinit var insetsProvider: StatusBarContentInsetsProvider
+ @Mock private lateinit var variableDateViewControllerFactory: VariableDateViewController.Factory
+ @Mock private lateinit var variableDateViewController: VariableDateViewController
@Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var combinedShadeHeadersConstraintManager:
+ CombinedShadeHeadersConstraintManager
@Mock private lateinit var mockedContext: Context
- @Mock private lateinit var typedArray: TypedArray
@JvmField @Rule val mockitoRule = MockitoJUnit.rule()
var viewVisibility = View.GONE
@@ -65,7 +72,6 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
fun setup() {
whenever<TextView>(view.findViewById(R.id.clock)).thenReturn(clock)
whenever(clock.context).thenReturn(mockedContext)
- whenever(mockedContext.obtainStyledAttributes(anyInt(), any())).thenReturn(typedArray)
whenever<TextView>(view.findViewById(R.id.date)).thenReturn(date)
whenever(date.context).thenReturn(mockedContext)
whenever<QSCarrierGroup>(view.findViewById(R.id.carrier_group)).thenReturn(carrierGroup)
@@ -73,6 +79,7 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
.thenReturn(batteryMeterView)
whenever<StatusIconContainer>(view.findViewById(R.id.statusIcons)).thenReturn(statusIcons)
whenever(view.context).thenReturn(context)
+ whenever(view.resources).thenReturn(context.resources)
whenever(statusIcons.context).thenReturn(context)
whenever(qsCarrierGroupControllerBuilder.setQSCarrierGroup(any()))
.thenReturn(qsCarrierGroupControllerBuilder)
@@ -82,27 +89,39 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
null
}
whenever(view.visibility).thenAnswer { _ -> viewVisibility }
+ whenever(variableDateViewControllerFactory.create(any()))
+ .thenReturn(variableDateViewController)
whenever(featureFlags.isEnabled(Flags.COMBINED_QS_HEADERS)).thenReturn(false)
mLargeScreenShadeHeaderController = LargeScreenShadeHeaderController(
view,
statusBarIconController,
privacyIconsController,
+ insetsProvider,
configurationController,
- qsCarrierGroupControllerBuilder,
- featureFlags,
+ variableDateViewControllerFactory,
batteryMeterViewController,
- dumpManager
+ dumpManager,
+ featureFlags,
+ qsCarrierGroupControllerBuilder,
+ combinedShadeHeadersConstraintManager
)
+ whenever(view.isAttachedToWindow).thenReturn(true)
+ mLargeScreenShadeHeaderController.init()
carrierIconSlots = listOf(
context.getString(com.android.internal.R.string.status_bar_mobile))
}
+ @After
+ fun verifyEveryTest() {
+ verifyZeroInteractions(combinedShadeHeadersConstraintManager)
+ }
+
@Test
fun setVisible_onlyWhenActive() {
makeShadeVisible()
assertThat(viewVisibility).isEqualTo(View.VISIBLE)
- mLargeScreenShadeHeaderController.active = false
+ mLargeScreenShadeHeaderController.largeScreenActive = false
assertThat(viewVisibility).isEqualTo(View.GONE)
}
@@ -156,41 +175,16 @@ class LargeScreenShadeHeaderControllerTest : SysuiTestCase() {
}
private fun makeShadeVisible() {
- mLargeScreenShadeHeaderController.active = true
- mLargeScreenShadeHeaderController.shadeExpanded = true
+ mLargeScreenShadeHeaderController.largeScreenActive = true
+ mLargeScreenShadeHeaderController.qsVisible = true
}
@Test
- fun updateConfig_changesFontSize() {
- val updatedTextPixelSize = 32
- setReturnTextSize(updatedTextPixelSize)
-
+ fun updateConfig_changesFontStyle() {
configurationController.notifyDensityOrFontScaleChanged()
- verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
- verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize.toFloat())
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
- }
-
- @Test
- fun updateConfig_changesFontSizeMultipleTimes() {
- val updatedTextPixelSize1 = 32
- setReturnTextSize(updatedTextPixelSize1)
- configurationController.notifyDensityOrFontScaleChanged()
- verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
- verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize1.toFloat())
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
- clearInvocations(carrierGroup)
-
- val updatedTextPixelSize2 = 42
- setReturnTextSize(updatedTextPixelSize2)
- configurationController.notifyDensityOrFontScaleChanged()
- verify(clock).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
- verify(date).setTextSize(COMPLEX_UNIT_PX, updatedTextPixelSize2.toFloat())
- verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status)
- }
-
- private fun setReturnTextSize(resultTextSize: Int) {
- whenever(typedArray.getDimensionPixelSize(anyInt(), anyInt())).thenReturn(resultTextSize)
+ verify(clock).setTextAppearance(R.style.TextAppearance_QS_Status)
+ verify(date).setTextAppearance(R.style.TextAppearance_QS_Status)
+ verify(carrierGroup).updateTextAppearance(R.style.TextAppearance_QS_Status_Carriers)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 8900d8ff9fda..e2673bb74084 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -60,6 +60,7 @@ import android.view.View;
import android.view.ViewParent;
import android.view.ViewPropertyAnimator;
import android.view.ViewStub;
+import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.AccessibilityNodeInfo;
@@ -86,6 +87,7 @@ import com.android.systemui.DejankUtils;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.camera.CameraGestureHelper;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.controls.dagger.ControlsComponent;
@@ -104,8 +106,8 @@ import com.android.systemui.plugins.FalsingManager;
import com.android.systemui.plugins.qs.QS;
import com.android.systemui.qrcodescanner.controller.QRCodeScannerController;
import com.android.systemui.screenrecord.RecordingController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.KeyguardAffordanceView;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
@@ -130,8 +132,26 @@ import com.android.systemui.statusbar.notification.stack.NotificationRoundnessMa
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.notification.stack.NotificationStackSizeCalculator;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.ConfigurationControllerImpl;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
+import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
+import com.android.systemui.statusbar.phone.HeadsUpTouchHelper;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaView;
+import com.android.systemui.statusbar.phone.KeyguardBottomAreaViewController;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
+import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
+import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
+import com.android.systemui.statusbar.phone.NotificationIconAreaController;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.StatusBarTouchableRegionManager;
+import com.android.systemui.statusbar.phone.TapAgainViewController;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
-import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardQsUserSwitchController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -172,6 +192,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
@Mock
private KeyguardBottomAreaView mKeyguardBottomArea;
@Mock
+ private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
+ @Mock
private KeyguardBottomAreaView mQsFrame;
private KeyguardStatusView mKeyguardStatusView;
@Mock
@@ -349,6 +371,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private View mQsHeader;
@Mock
private ViewParent mViewParent;
+ @Mock
+ private ViewTreeObserver mViewTreeObserver;
private NotificationPanelViewController.PanelEventsEmitter mPanelEventsEmitter;
private Optional<SysUIUnfoldComponent> mSysUIUnfoldComponent = Optional.empty();
private SysuiStatusBarStateController mStatusBarStateController;
@@ -397,9 +421,8 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(1000);
when(mNotificationStackScrollLayoutController.getHeadsUpCallback())
.thenReturn(mHeadsUpCallback);
+ when(mKeyguardBottomAreaViewController.getView()).thenReturn(mKeyguardBottomArea);
when(mView.findViewById(R.id.keyguard_bottom_area)).thenReturn(mKeyguardBottomArea);
- when(mKeyguardBottomArea.getLeftView()).thenReturn(mock(KeyguardAffordanceView.class));
- when(mKeyguardBottomArea.getRightView()).thenReturn(mock(KeyguardAffordanceView.class));
when(mKeyguardBottomArea.animate()).thenReturn(mock(ViewPropertyAnimator.class));
when(mView.findViewById(R.id.qs_frame)).thenReturn(mQsFrame);
when(mView.findViewById(R.id.keyguard_status_view))
@@ -474,14 +497,15 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
return null;
}).when(mNotificationShadeWindowController).batchApplyWindowLayoutParams(any());
+ when(mView.getViewTreeObserver()).thenReturn(mViewTreeObserver);
when(mView.getParent()).thenReturn(mViewParent);
when(mQs.getHeader()).thenReturn(mQsHeader);
mMainHandler = new Handler(Looper.getMainLooper());
mPanelEventsEmitter = new NotificationPanelViewController.PanelEventsEmitter();
- mNotificationPanelViewController = new NotificationPanelViewController(mView,
- mResources,
+ mNotificationPanelViewController = new NotificationPanelViewController(
+ mView,
mMainHandler,
mLayoutInflater,
mFeatureFlags,
@@ -522,8 +546,6 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mQuickAccessWalletController,
mQrCodeScannerController,
mRecordingController,
- mExecutor,
- mSecureSettings,
mLargeScreenShadeHeaderController,
mScreenOffAnimationController,
mLockscreenGestureLogger,
@@ -534,13 +556,15 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mInteractionJankMonitor,
mQsFrameTranslateController,
mSysUiState,
+ () -> mKeyguardBottomAreaViewController,
mKeyguardUnlockAnimationController,
mNotificationListContainer,
mPanelEventsEmitter,
mNotificationStackSizeCalculator,
mUnlockedScreenOffAnimationController,
mShadeTransitionController,
- mSystemClock);
+ mSystemClock,
+ mock(CameraGestureHelper.class));
mNotificationPanelViewController.initDependencies(
mCentralSurfaces,
() -> {},
@@ -558,7 +582,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
- mNotificationPanelViewController.mStatusBarStateController
+ mNotificationPanelViewController.getStatusBarStateController()
.addCallback(mNotificationPanelViewController.mStatusBarStateListener);
mNotificationPanelViewController
.setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
@@ -993,7 +1017,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mNotificationPanelViewController.flingToHeight(
0f,
true,
- mNotificationPanelViewController.mExpandedHeight,
+ mNotificationPanelViewController.getExpandedHeight(),
1f,
false);
// Verify that the NSSL is notified that the panel is *not* flinging.
@@ -1021,6 +1045,16 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testRotatingToSplitShadeWithQsExpanded_transitionsToShadeLocked() {
+ mStatusBarStateController.setState(KEYGUARD);
+ mNotificationPanelViewController.setQsExpanded(true);
+
+ enableSplitShade(true);
+
+ assertThat(mStatusBarStateController.getState()).isEqualTo(SHADE_LOCKED);
+ }
+
+ @Test
public void testSwitchesToCorrectClockInSinglePaneShade() {
mStatusBarStateController.setState(KEYGUARD);
@@ -1146,11 +1180,11 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
mStatusBarStateController.setState(SHADE);
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(true);
mNotificationPanelViewController.updateResources();
- verify(mLargeScreenShadeHeaderController).setActive(true);
+ verify(mLargeScreenShadeHeaderController).setLargeScreenActive(true);
when(mResources.getBoolean(R.bool.config_use_large_screen_shade_header)).thenReturn(false);
mNotificationPanelViewController.updateResources();
- verify(mLargeScreenShadeHeaderController).setActive(false);
+ verify(mLargeScreenShadeHeaderController).setLargeScreenActive(false);
}
@Test
@@ -1177,14 +1211,14 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mPowerManager.isPowerSaveMode()).thenReturn(false);
when(mAmbientState.getDozeAmount()).thenReturn(0f);
mNotificationPanelViewController.startUnlockHintAnimation();
- assertThat(mNotificationPanelViewController.mHintAnimationRunning).isTrue();
+ assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isTrue();
}
@Test
public void testUnlockHintAnimation_doesNotRun_inPowerSaveMode() {
when(mPowerManager.isPowerSaveMode()).thenReturn(true);
mNotificationPanelViewController.startUnlockHintAnimation();
- assertThat(mNotificationPanelViewController.mHintAnimationRunning).isFalse();
+ assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse();
}
@Test
@@ -1192,7 +1226,7 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
when(mPowerManager.isPowerSaveMode()).thenReturn(false);
when(mAmbientState.getDozeAmount()).thenReturn(0.5f);
mNotificationPanelViewController.startUnlockHintAnimation();
- assertThat(mNotificationPanelViewController.mHintAnimationRunning).isFalse();
+ assertThat(mNotificationPanelViewController.isHintAnimationRunning()).isFalse();
}
@Test
@@ -1250,6 +1284,29 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
}
@Test
+ public void testPanelClosedWhenClosingQsInSplitShade() {
+ mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ enableSplitShade(/* enabled= */ true);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+ mNotificationPanelViewController.animateCloseQs(false);
+ assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+ }
+
+ @Test
+ public void testPanelStaysOpenWhenClosingQs() {
+ mPanelExpansionStateManager.onPanelExpansionChanged(/* fraction= */ 1,
+ /* expanded= */ true, /* tracking= */ false, /* dragDownPxAmount= */ 0);
+ mNotificationPanelViewController.setExpandedFraction(1f);
+
+ assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+ mNotificationPanelViewController.animateCloseQs(false);
+ assertThat(mNotificationPanelViewController.isClosing()).isFalse();
+ }
+
+ @Test
public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
mNotificationPanelViewController.mQs = mQs;
when(mQsFrame.getX()).thenReturn(0f);
@@ -1301,6 +1358,43 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
false/*goingToFullShade*/, SHADE/*oldStatusBarState*/);
}
+ @Test
+ public void getMaxPanelHeight_expanding_inSplitShade_returnsSplitShadeFullTransitionDistance() {
+ int splitShadeFullTransitionDistance = 123456;
+ enableSplitShade(true);
+ setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance);
+ mNotificationPanelViewController.expandWithQs();
+
+ int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight();
+
+ assertThat(maxPanelHeight).isEqualTo(splitShadeFullTransitionDistance);
+ }
+
+ @Test
+ public void getMaxPanelHeight_expandingSplitShade_keyguard_returnsNonSplitShadeValue() {
+ mStatusBarStateController.setState(KEYGUARD);
+ int splitShadeFullTransitionDistance = 123456;
+ enableSplitShade(true);
+ setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance);
+ mNotificationPanelViewController.expandWithQs();
+
+ int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight();
+
+ assertThat(maxPanelHeight).isNotEqualTo(splitShadeFullTransitionDistance);
+ }
+
+ @Test
+ public void getMaxPanelHeight_expanding_notSplitShade_returnsNonSplitShadeValue() {
+ int splitShadeFullTransitionDistance = 123456;
+ enableSplitShade(false);
+ setSplitShadeFullTransitionDistance(splitShadeFullTransitionDistance);
+ mNotificationPanelViewController.expandWithQs();
+
+ int maxPanelHeight = mNotificationPanelViewController.getMaxPanelHeight();
+
+ assertThat(maxPanelHeight).isNotEqualTo(splitShadeFullTransitionDistance);
+ }
+
private static MotionEvent createMotionEvent(int x, int y, int action) {
return MotionEvent.obtain(
/* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
@@ -1360,4 +1454,10 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase {
private void onTouchEvent(MotionEvent ev) {
mTouchHandler.onTouch(mView, ev);
}
+
+ private void setSplitShadeFullTransitionDistance(int splitShadeFullTransitionDistance) {
+ when(mResources.getDimensionPixelSize(R.dimen.split_shade_full_transition_distance))
+ .thenReturn(splitShadeFullTransitionDistance);
+ mNotificationPanelViewController.updateResources();
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
index de40b7fd0a13..0c6a6a98052f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationQSContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationQSContainerControllerTest.kt
@@ -1,4 +1,4 @@
-package com.android.systemui.statusbar.phone
+package com.android.systemui.shade
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index c402d2e47cf3..1dfd7c26ecbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
@@ -28,8 +28,10 @@ import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -42,6 +44,8 @@ import android.testing.TestableLooper.RunWithLooper;
import android.view.View;
import android.view.WindowManager;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.ViewTreeLifecycleOwner;
import androidx.test.filters.SmallTest;
import com.android.internal.colorextraction.ColorExtractor;
@@ -51,6 +55,11 @@ import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
+import com.android.systemui.statusbar.phone.DozeParameters;
+import com.android.systemui.statusbar.phone.KeyguardBypassController;
+import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
+import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -61,6 +70,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@@ -69,7 +79,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
@Mock private WindowManager mWindowManager;
@Mock private DozeParameters mDozeParameters;
- @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
+ @Spy private final NotificationShadeWindowView mNotificationShadeWindowView = spy(
+ new NotificationShadeWindowView(mContext, null));
@Mock private IActivityManager mActivityManager;
@Mock private SysuiStatusBarStateController mStatusBarStateController;
@Mock private ConfigurationController mConfigurationController;
@@ -85,6 +96,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
+
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
@@ -177,6 +189,24 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase {
}
@Test
+ public void attach_setsUpLifecycleOwner() {
+ mNotificationShadeWindowController.attach();
+
+ assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView)).isNotNull();
+ }
+
+ @Test
+ public void attach_doesNotSetUpLifecycleOwnerIfAlreadySet() {
+ final LifecycleOwner previouslySet = mock(LifecycleOwner.class);
+ ViewTreeLifecycleOwner.set(mNotificationShadeWindowView, previouslySet);
+
+ mNotificationShadeWindowController.attach();
+
+ assertThat(ViewTreeLifecycleOwner.get(mNotificationShadeWindowView))
+ .isEqualTo(previouslySet);
+ }
+
+ @Test
public void setScrimsVisibility_earlyReturn() {
clearInvocations(mWindowManager);
mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
new file mode 100644
index 000000000000..471918cfa695
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt
@@ -0,0 +1,284 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shade
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.keyguard.LockIconViewController
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.classifier.FalsingCollectorFake
+import com.android.systemui.dock.DockManager
+import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.lowlightclock.LowLightClockController
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.NotificationShadeDepthController
+import com.android.systemui.statusbar.NotificationShadeWindowController
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.notification.stack.AmbientState
+import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
+import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler
+import com.android.systemui.statusbar.phone.CentralSurfaces
+import com.android.systemui.statusbar.phone.PhoneStatusBarViewController
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
+import com.android.systemui.statusbar.window.StatusBarWindowStateController
+import com.android.systemui.tuner.TunerService
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers
+import org.mockito.Mock
+import org.mockito.Mockito.anyFloat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
+ @Mock
+ private lateinit var view: NotificationShadeWindowView
+ @Mock
+ private lateinit var tunserService: TunerService
+ @Mock
+ private lateinit var sysuiStatusBarStateController: SysuiStatusBarStateController
+ @Mock
+ private lateinit var centralSurfaces: CentralSurfaces
+ @Mock
+ private lateinit var dockManager: DockManager
+ @Mock
+ private lateinit var notificationPanelViewController: NotificationPanelViewController
+ @Mock
+ private lateinit var notificationShadeDepthController: NotificationShadeDepthController
+ @Mock
+ private lateinit var notificationShadeWindowController: NotificationShadeWindowController
+ @Mock
+ private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController
+ @Mock
+ private lateinit var ambientState: AmbientState
+ @Mock
+ private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
+ @Mock
+ private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
+ @Mock
+ private lateinit var statusBarWindowStateController: StatusBarWindowStateController
+ @Mock
+ private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController
+ @Mock
+ private lateinit var lockIconViewController: LockIconViewController
+ @Mock
+ private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController
+ @Mock
+ private lateinit var lowLightClockController: LowLightClockController
+
+ private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
+ private lateinit var interactionEventHandler: InteractionEventHandler
+
+ private lateinit var underTest: NotificationShadeWindowViewController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(view.bottom).thenReturn(VIEW_BOTTOM)
+
+ underTest = NotificationShadeWindowViewController(
+ lockscreenShadeTransitionController,
+ FalsingCollectorFake(),
+ tunserService,
+ sysuiStatusBarStateController,
+ dockManager,
+ notificationShadeDepthController,
+ view,
+ notificationPanelViewController,
+ PanelExpansionStateManager(),
+ stackScrollLayoutController,
+ statusBarKeyguardViewManager,
+ statusBarWindowStateController,
+ lockIconViewController,
+ Optional.of(lowLightClockController),
+ centralSurfaces,
+ notificationShadeWindowController,
+ keyguardUnlockAnimationController,
+ ambientState
+ )
+ underTest.setupExpandedStatusBar()
+
+ interactionEventHandlerCaptor =
+ ArgumentCaptor.forClass(InteractionEventHandler::class.java)
+ verify(view).setInteractionEventHandler(interactionEventHandlerCaptor.capture())
+ interactionEventHandler = interactionEventHandlerCaptor.value
+ }
+
+ // Note: So far, these tests only cover interactions with the status bar view controller. More
+ // tests need to be added to test the rest of handleDispatchTouchEvent.
+
+ @Test
+ fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
+ underTest.setStatusBarViewController(null)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+
+ assertThat(returnVal).isFalse()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
+ whenever(phoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(ev)
+
+ verify(phoneStatusBarViewController).sendTouchToView(ev)
+ assertThat(returnVal).isTrue()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ val downEvBelow = MotionEvent.obtain(
+ 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0
+ )
+ interactionEventHandler.handleDispatchTouchEvent(downEvBelow)
+
+ val nextEvent = MotionEvent.obtain(
+ 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0
+ )
+ whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+
+ verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+ assertThat(returnVal).isTrue()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+ whenever(phoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+
+ verify(phoneStatusBarViewController).sendTouchToView(downEv)
+ assertThat(returnVal).isTrue()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+ // Item we're testing
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(false)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+
+ verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
+ assertThat(returnVal).isNull()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ // Item we're testing
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(false)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+
+ verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
+ assertThat(returnVal).isNull()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+ // Item we're testing
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(downEv)
+
+ verify(phoneStatusBarViewController, never()).sendTouchToView(downEv)
+ assertThat(returnVal).isTrue()
+ }
+
+ @Test
+ fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
+ underTest.setStatusBarViewController(phoneStatusBarViewController)
+ whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true)
+ whenever(notificationPanelViewController.isFullyCollapsed).thenReturn(true)
+ whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
+ .thenReturn(true)
+
+ // Down event first
+ interactionEventHandler.handleDispatchTouchEvent(downEv)
+
+ // Then another event
+ val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
+ whenever(phoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
+
+ val returnVal = interactionEventHandler.handleDispatchTouchEvent(nextEvent)
+
+ verify(phoneStatusBarViewController).sendTouchToView(nextEvent)
+ assertThat(returnVal).isTrue()
+ }
+
+ @Test
+ fun testLowLightClockAttachedWhenExpandedStatusBarSetup() {
+ verify(lowLightClockController).attachLowLightClockView(ArgumentMatchers.any())
+ }
+
+ @Test
+ fun testLowLightClockShownWhenDozing() {
+ underTest.setDozing(true)
+ verify(lowLightClockController).showLowLightClock(true)
+ }
+
+ @Test
+ fun testLowLightClockDozeTimeTickCalled() {
+ underTest.dozeTimeTick()
+ verify(lowLightClockController).dozeTimeTick()
+ }
+
+ @Test
+ fun testLowLightClockHiddenWhenNotDozing() {
+ underTest.setDozing(true)
+ verify(lowLightClockController).showLowLightClock(true)
+ underTest.setDozing(false)
+ verify(lowLightClockController).showLowLightClock(false)
+ }
+}
+
+private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
+private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 1d86fb13a1f6..665d849e379e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package com.android.systemui.statusbar.phone;
+package com.android.systemui.shade;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
@@ -47,6 +47,9 @@ import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.stack.AmbientState;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
+import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.ShadeController;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.window.StatusBarWindowStateController;
import com.android.systemui.tuner.TunerService;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
new file mode 100644
index 000000000000..baaa447d53a3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ScrimShadeTransitionControllerTest.kt
@@ -0,0 +1,151 @@
+package com.android.systemui.shade.transition
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.phone.ScrimController
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
+import com.android.systemui.statusbar.phone.panelstate.STATE_CLOSED
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPEN
+import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
+import com.android.systemui.statusbar.policy.FakeConfigurationController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ScrimShadeTransitionControllerTest : SysuiTestCase() {
+
+ @Mock private lateinit var scrimController: ScrimController
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ private val configurationController = FakeConfigurationController()
+
+ private lateinit var controller: ScrimShadeTransitionController
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ context.ensureTestableResources()
+ controller =
+ ScrimShadeTransitionController(
+ configurationController,
+ dumpManager,
+ scrimController,
+ context.resources,
+ statusBarStateController)
+
+ controller.onPanelStateChanged(STATE_OPENING)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSingleShade_setsFractionEqualToEventFraction() {
+ setSplitShadeEnabled(false)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_unlockedShade_setsFractionBasedOnDragDownAmount() {
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
+ val scrimShadeTransitionDistance =
+ context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ val expectedFraction = EXPANSION_EVENT.dragDownPxAmount / scrimShadeTransitionDistance
+ verify(scrimController).setRawPanelExpansionFraction(expectedFraction)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_largeDragDownAmount_fractionIsNotGreaterThan1() {
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
+ val scrimShadeTransitionDistance =
+ context.resources.getDimensionPixelSize(R.dimen.split_shade_scrim_transition_distance)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(
+ EXPANSION_EVENT.copy(dragDownPxAmount = 100f * scrimShadeTransitionDistance))
+
+ verify(scrimController).setRawPanelExpansionFraction(1f)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_negativeDragDownAmount_fractionIsNotLessThan0() {
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(StatusBarState.SHADE)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT.copy(dragDownPxAmount = -100f))
+
+ verify(scrimController).setRawPanelExpansionFraction(0f)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_onLockedShade_setsFractionEqualToEventFraction() {
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.SHADE_LOCKED)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_onKeyguard_setsFractionEqualToEventFraction() {
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.KEYGUARD)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_panelOpen_setsFractionEqualToEventFraction() {
+ controller.onPanelStateChanged(STATE_OPEN)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.KEYGUARD)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_panelClosed_setsFractionEqualToEventFraction() {
+ controller.onPanelStateChanged(STATE_CLOSED)
+ whenever(statusBarStateController.currentOrUpcomingState)
+ .thenReturn(StatusBarState.KEYGUARD)
+ setSplitShadeEnabled(true)
+
+ controller.onPanelExpansionChanged(EXPANSION_EVENT)
+
+ verify(scrimController).setRawPanelExpansionFraction(EXPANSION_EVENT.fraction)
+ }
+
+ private fun setSplitShadeEnabled(enabled: Boolean) {
+ overrideResource(R.bool.config_use_split_notification_shade, enabled)
+ configurationController.notifyConfigurationChanged()
+ }
+
+ companion object {
+ val EXPANSION_EVENT =
+ PanelExpansionChangeEvent(
+ fraction = 0.5f, expanded = true, tracking = true, dragDownPxAmount = 10f)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
index 39d33e86925e..b6f8326394fa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/ShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/ShadeTransitionControllerTest.kt
@@ -1,12 +1,16 @@
-package com.android.systemui.statusbar.phone.shade.transition
+package com.android.systemui.shade.transition
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.NotificationPanelViewController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.SysuiStatusBarStateController
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
+import com.android.systemui.statusbar.phone.panelstate.PanelExpansionChangeEvent
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
import com.android.systemui.statusbar.phone.panelstate.STATE_OPENING
import com.android.systemui.statusbar.policy.FakeConfigurationController
@@ -17,6 +21,7 @@ import org.mockito.Mock
import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -28,6 +33,9 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
@Mock private lateinit var qs: QS
@Mock private lateinit var noOpOverScroller: NoOpOverScroller
@Mock private lateinit var splitShadeOverScroller: SplitShadeOverScroller
+ @Mock private lateinit var scrimShadeTransitionController: ScrimShadeTransitionController
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
private lateinit var controller: ShadeTransitionController
@@ -42,9 +50,13 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
ShadeTransitionController(
configurationController,
panelExpansionStateManager,
+ dumpManager,
context,
splitShadeOverScrollerFactory = { _, _ -> splitShadeOverScroller },
- noOpOverScroller)
+ noOpOverScroller,
+ scrimShadeTransitionController,
+ statusBarStateController,
+ )
// Resetting as they are notified upon initialization.
reset(noOpOverScroller, splitShadeOverScroller)
@@ -74,6 +86,45 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
}
@Test
+ fun onPanelStateChanged_inSplitShade_onKeyguard_forwardsToNoOpOverScroller() {
+ initLateProperties()
+ enableSplitShade()
+ setOnKeyguard()
+
+ startPanelExpansion()
+
+ verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+ verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
+
+ @Test
+ fun onPanelStateChanged_inSplitShade_onLockedShade_forwardsToNoOpOverScroller() {
+ initLateProperties()
+ enableSplitShade()
+ setOnLockedShade()
+
+ startPanelExpansion()
+
+ verify(noOpOverScroller).onPanelStateChanged(STATE_OPENING)
+ verify(noOpOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+ verifyZeroInteractions(splitShadeOverScroller)
+ }
+
+ @Test
+ fun onPanelExpansionChanged_inSplitShade_onUnlockedShade_forwardsToSplitShadeOverScroller() {
+ initLateProperties()
+ enableSplitShade()
+ setOnUnlockedShade()
+
+ startPanelExpansion()
+
+ verify(splitShadeOverScroller).onPanelStateChanged(STATE_OPENING)
+ verify(splitShadeOverScroller).onDragDownAmountChanged(DEFAULT_DRAG_DOWN_AMOUNT)
+ verifyZeroInteractions(noOpOverScroller)
+ }
+
+ @Test
fun onPanelStateChanged_notInSplitShade_forwardsToNoOpOverScroller() {
initLateProperties()
disableSplitShade()
@@ -85,6 +136,16 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
verifyZeroInteractions(splitShadeOverScroller)
}
+ @Test
+ fun onPanelStateChanged_forwardsToScrimTransitionController() {
+ initLateProperties()
+
+ startPanelExpansion()
+
+ verify(scrimShadeTransitionController).onPanelStateChanged(STATE_OPENING)
+ verify(scrimShadeTransitionController).onPanelExpansionChanged(DEFAULT_EXPANSION_EVENT)
+ }
+
private fun initLateProperties() {
controller.qs = qs
controller.notificationStackScrollLayoutController = nsslController
@@ -106,13 +167,37 @@ class ShadeTransitionControllerTest : SysuiTestCase() {
private fun startPanelExpansion() {
panelExpansionStateManager.onPanelExpansionChanged(
- fraction = 0.5f,
- expanded = true,
- tracking = true,
- dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
+ DEFAULT_EXPANSION_EVENT.fraction,
+ DEFAULT_EXPANSION_EVENT.expanded,
+ DEFAULT_EXPANSION_EVENT.tracking,
+ DEFAULT_EXPANSION_EVENT.dragDownPxAmount,
+ )
+ }
+
+ private fun setOnKeyguard() {
+ setShadeState(StatusBarState.KEYGUARD)
+ }
+
+ private fun setOnLockedShade() {
+ setShadeState(StatusBarState.SHADE_LOCKED)
+ }
+
+ private fun setOnUnlockedShade() {
+ setShadeState(StatusBarState.SHADE)
+ }
+
+ private fun setShadeState(state: Int) {
+ whenever(statusBarStateController.state).thenReturn(state)
+ whenever(statusBarStateController.currentOrUpcomingState).thenReturn(state)
}
companion object {
private const val DEFAULT_DRAG_DOWN_AMOUNT = 123f
+ private val DEFAULT_EXPANSION_EVENT =
+ PanelExpansionChangeEvent(
+ fraction = 0.5f,
+ expanded = true,
+ tracking = true,
+ dragDownPxAmount = DEFAULT_DRAG_DOWN_AMOUNT)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
index 219737d1dfb4..aafd871f72f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/shade/transition/SplitShadeOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/transition/SplitShadeOverScrollerTest.kt
@@ -1,6 +1,5 @@
-package com.android.systemui.statusbar.phone.shade.transition
+package com.android.systemui.shade.transition
-import org.mockito.Mockito.`when` as whenever
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
@@ -21,6 +20,7 @@ import org.mockito.Mock
import org.mockito.Mockito.atLeastOnce
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -43,7 +43,12 @@ class SplitShadeOverScrollerTest : SysuiTestCase() {
whenever(nsslController.height).thenReturn(1000)
overScroller =
SplitShadeOverScroller(
- configurationController, dumpManager, context, scrimController, qs, nsslController)
+ configurationController,
+ dumpManager,
+ context,
+ scrimController,
+ { qs },
+ { nsslController })
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
index 32314159f865..a4a89a4c67f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
package com.android.systemui.shared.animation
import android.testing.AndroidTestingRunner
@@ -7,31 +21,24 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.Direction
import com.android.systemui.shared.animation.UnfoldConstantTranslateAnimator.ViewIdToTranslate
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
- @Mock private lateinit var progressProvider: UnfoldTransitionProgressProvider
+ private val progressProvider = TestUnfoldTransitionProvider()
@Mock private lateinit var parent: ViewGroup
- @Captor private lateinit var progressListenerCaptor: ArgumentCaptor<TransitionProgressListener>
-
private lateinit var animator: UnfoldConstantTranslateAnimator
- private lateinit var progressListener: TransitionProgressListener
private val viewsIdToRegister =
setOf(
@@ -46,17 +53,14 @@ class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
UnfoldConstantTranslateAnimator(viewsIdToRegister, progressProvider)
animator.init(parent, MAX_TRANSLATION)
-
- verify(progressProvider).addCallback(progressListenerCaptor.capture())
- progressListener = progressListenerCaptor.value
}
@Test
fun onTransition_noMatchingIds() {
// GIVEN no views matching any ids
// WHEN the transition starts
- progressListener.onTransitionStarted()
- progressListener.onTransitionProgress(.1f)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(.1f)
// THEN nothing... no exceptions
}
@@ -86,22 +90,22 @@ class UnfoldConstantTranslateAnimatorTest : SysuiTestCase() {
// Compare values as ints because -0f != 0f
// WHEN the transition starts
- progressListener.onTransitionStarted()
- progressListener.onTransitionProgress(0f)
+ progressProvider.onTransitionStarted()
+ progressProvider.onTransitionProgress(0f)
list.forEach { (view, direction) ->
assertEquals((-MAX_TRANSLATION * direction).toInt(), view.translationX.toInt())
}
// WHEN the transition progresses, translation is updated
- progressListener.onTransitionProgress(.5f)
+ progressProvider.onTransitionProgress(.5f)
list.forEach { (view, direction) ->
assertEquals((-MAX_TRANSLATION / 2f * direction).toInt(), view.translationX.toInt())
}
// WHEN the transition ends, translation is completed
- progressListener.onTransitionProgress(1f)
- progressListener.onTransitionFinished()
+ progressProvider.onTransitionProgress(1f)
+ progressProvider.onTransitionFinished()
list.forEach { (view, _) -> assertEquals(0, view.translationX.toInt()) }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
new file mode 100644
index 000000000000..d61989fc3128
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package com.android.systemui.shared.clocks
+
+import android.content.ContentResolver
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.Clock
+import com.android.systemui.plugins.ClockId
+import com.android.systemui.plugins.ClockMetadata
+import com.android.systemui.plugins.ClockProviderPlugin
+import com.android.systemui.plugins.PluginListener
+import com.android.systemui.shared.plugins.PluginManager
+import com.android.systemui.util.mockito.argumentCaptor
+import com.android.systemui.util.mockito.eq
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ClockRegistryTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+ @Mock private lateinit var mockContext: Context
+ @Mock private lateinit var mockPluginManager: PluginManager
+ @Mock private lateinit var mockClock: Clock
+ @Mock private lateinit var mockDefaultClock: Clock
+ @Mock private lateinit var mockThumbnail: Drawable
+ @Mock private lateinit var mockHandler: Handler
+ @Mock private lateinit var mockContentResolver: ContentResolver
+ private lateinit var fakeDefaultProvider: FakeClockPlugin
+ private lateinit var pluginListener: PluginListener<ClockProviderPlugin>
+ private lateinit var registry: ClockRegistry
+
+ private var settingValue: String = ""
+
+ companion object {
+ private fun failFactory(): Clock {
+ fail("Unexpected call to createClock")
+ return null!!
+ }
+
+ private fun failThumbnail(): Drawable? {
+ fail("Unexpected call to getThumbnail")
+ return null
+ }
+ }
+
+ private class FakeClockPlugin : ClockProviderPlugin {
+ private val metadata = mutableListOf<ClockMetadata>()
+ private val createCallbacks = mutableMapOf<ClockId, () -> Clock>()
+ private val thumbnailCallbacks = mutableMapOf<ClockId, () -> Drawable?>()
+
+ override fun getClocks() = metadata
+ override fun createClock(id: ClockId): Clock = createCallbacks[id]!!()
+ override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!()
+
+ fun addClock(
+ id: ClockId,
+ name: String,
+ create: () -> Clock = ::failFactory,
+ getThumbnail: () -> Drawable? = ::failThumbnail
+ ): FakeClockPlugin {
+ metadata.add(ClockMetadata(id, name))
+ createCallbacks[id] = create
+ thumbnailCallbacks[id] = getThumbnail
+ return this
+ }
+ }
+
+ @Before
+ fun setUp() {
+ fakeDefaultProvider = FakeClockPlugin()
+ .addClock(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME, { mockDefaultClock }, { mockThumbnail })
+ whenever(mockContext.contentResolver).thenReturn(mockContentResolver)
+
+ val captor = argumentCaptor<PluginListener<ClockProviderPlugin>>()
+ registry = object : ClockRegistry(
+ mockContext,
+ mockPluginManager,
+ mockHandler,
+ fakeDefaultProvider
+ ) {
+ override var currentClockId: ClockId
+ get() = settingValue
+ set(value) { settingValue = value }
+ }
+
+ verify(mockPluginManager)
+ .addPluginListener(captor.capture(), eq(ClockProviderPlugin::class.java))
+ pluginListener = captor.value
+ }
+
+ @Test
+ fun pluginRegistration_CorrectState() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_3", "clock 3")
+ .addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ val list = registry.getClocks()
+ assertEquals(list, listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2"),
+ ClockMetadata("clock_3", "clock 3"),
+ ClockMetadata("clock_4", "clock 4")
+ ))
+ }
+
+ @Test
+ fun noPlugins_createDefaultClock() {
+ val clock = registry.createCurrentClock()
+ assertEquals(clock, mockDefaultClock)
+ }
+
+ @Test
+ fun clockIdConflict_ErrorWithoutCrash() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1", { mockClock }, { mockThumbnail })
+ .addClock("clock_2", "clock 2", { mockClock }, { mockThumbnail })
+
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ val list = registry.getClocks()
+ assertEquals(list, listOf(
+ ClockMetadata(DEFAULT_CLOCK_ID, DEFAULT_CLOCK_NAME),
+ ClockMetadata("clock_1", "clock 1"),
+ ClockMetadata("clock_2", "clock 2")
+ ))
+
+ assertEquals(registry.createExampleClock("clock_1"), mockClock)
+ assertEquals(registry.createExampleClock("clock_2"), mockClock)
+ assertEquals(registry.getClockThumbnail("clock_1"), mockThumbnail)
+ assertEquals(registry.getClockThumbnail("clock_2"), mockThumbnail)
+ }
+
+ @Test
+ fun createCurrentClock_pluginConnected() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ settingValue = "clock_3"
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_3", "clock 3", { mockClock })
+ .addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+
+ val clock = registry.createCurrentClock()
+ assertEquals(clock, mockClock)
+ }
+
+ @Test
+ fun createDefaultClock_pluginDisconnected() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ settingValue = "clock_3"
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_3", "clock 3")
+ .addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+ pluginListener.onPluginDisconnected(plugin2)
+
+ val clock = registry.createCurrentClock()
+ assertEquals(clock, mockDefaultClock)
+ }
+
+ @Test
+ fun pluginRemoved_clockChanged() {
+ val plugin1 = FakeClockPlugin()
+ .addClock("clock_1", "clock 1")
+ .addClock("clock_2", "clock 2")
+
+ settingValue = "clock_3"
+ val plugin2 = FakeClockPlugin()
+ .addClock("clock_3", "clock 3", { mockClock })
+ .addClock("clock_4", "clock 4")
+
+ pluginListener.onPluginConnected(plugin1, mockContext)
+ pluginListener.onPluginConnected(plugin2, mockContext)
+
+ var changeCallCount = 0
+ registry.registerClockChangeListener({ changeCallCount++ })
+
+ pluginListener.onPluginDisconnected(plugin1)
+ assertEquals(0, changeCallCount)
+
+ pluginListener.onPluginDisconnected(plugin2)
+ assertEquals(1, changeCallCount)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
new file mode 100644
index 000000000000..78694480ad5b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.systemui.shared.clocks
+
+import android.content.res.Resources
+import android.graphics.drawable.Drawable
+import android.testing.AndroidTestingRunner
+import android.view.LayoutInflater
+import androidx.test.filters.SmallTest
+import com.android.systemui.R
+import com.android.systemui.SysuiTestCase
+import junit.framework.Assert.assertEquals
+import junit.framework.Assert.assertNotNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.junit.MockitoJUnit
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DefaultClockProviderTest : SysuiTestCase() {
+
+ @JvmField @Rule val mockito = MockitoJUnit.rule()
+
+ @Mock private lateinit var mockClockView: AnimatableClockView
+ @Mock private lateinit var layoutInflater: LayoutInflater
+ @Mock private lateinit var mockClockThumbnail: Drawable
+ @Mock private lateinit var resources: Resources
+ private lateinit var provider: DefaultClockProvider
+
+ @Before
+ fun setUp() {
+ whenever(layoutInflater.inflate(R.layout.clock_default_small, null))
+ .thenReturn(mockClockView)
+ whenever(layoutInflater.inflate(R.layout.clock_default_large, null))
+ .thenReturn(mockClockView)
+ whenever(resources.getDrawable(R.drawable.clock_default_thumbnail, null))
+ .thenReturn(mockClockThumbnail)
+
+ provider = DefaultClockProvider(layoutInflater, resources)
+ }
+
+ @Test
+ fun providedClocks_matchesFactory() {
+ // All providers need to provide clocks & thumbnails for exposed clocks
+ for (metadata in provider.getClocks()) {
+ assertNotNull(provider.createClock(metadata.clockId))
+ assertNotNull(provider.getClockThumbnail(metadata.clockId))
+ }
+ }
+
+ @Test
+ fun defaultClock_alwaysProvided() {
+ // Default clock provider must always provide the default clock
+ val clock = provider.createClock(DEFAULT_CLOCK_ID)
+ assertNotNull(clock)
+ assertEquals(clock.smallClock, mockClockView)
+ assertEquals(clock.largeClock, mockClockView)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
index 2cfe6be5c6b2..37f96c8d7023 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
@@ -26,8 +26,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
-import com.android.systemui.dreams.smartspace.DreamsSmartspaceController
+import com.android.systemui.dreams.smartspace.DreamSmartspaceController
import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.smartspace.dagger.SmartspaceViewComponent
import com.android.systemui.util.concurrency.Execution
@@ -35,16 +36,17 @@ import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
-import java.util.concurrent.Executor
+import org.mockito.Spy
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -74,8 +76,8 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
@Mock
private lateinit var precondition: SmartspacePrecondition
- @Mock
- private lateinit var smartspaceView: BcSmartspaceDataPlugin.SmartspaceView
+ @Spy
+ private var smartspaceView: SmartspaceView = TestView(context)
@Mock
private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
@@ -83,6 +85,36 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
@Mock
private lateinit var session: SmartspaceSession
+ private lateinit var controller: DreamSmartspaceController
+
+ /**
+ * A class which implements SmartspaceView and extends View. This is mocked to provide the right
+ * object inheritance and interface implementation used in DreamSmartspaceController
+ */
+ private class TestView(context: Context?) : View(context), SmartspaceView {
+ override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
+
+ override fun setPrimaryTextColor(color: Int) {}
+
+ override fun setIsDreaming(isDreaming: Boolean) {}
+
+ override fun setDozeAmount(amount: Float) {}
+
+ override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
+
+ override fun setFalsingManager(falsingManager: FalsingManager?) {}
+
+ override fun setDnd(image: Drawable?, description: String?) {}
+
+ override fun setNextAlarm(image: Drawable?, description: String?) {}
+
+ override fun setMediaTarget(target: SmartspaceTarget?) {}
+
+ override fun getSelectedPage(): Int { return 0; }
+
+ override fun getCurrentCardTopPadding(): Int { return 0; }
+ }
+
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
@@ -90,6 +122,9 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
.thenReturn(viewComponent)
`when`(viewComponent.getView()).thenReturn(smartspaceView)
`when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
+
+ controller = DreamSmartspaceController(context, smartspaceManager, execution, uiExecutor,
+ viewComponentFactory, precondition, Optional.of(targetFilter), Optional.of(plugin))
}
/**
@@ -97,10 +132,6 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
*/
@Test
fun testConnectOnListen() {
- val controller = DreamsSmartspaceController(context,
- smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition,
- Optional.of(targetFilter), Optional.of(plugin))
-
`when`(precondition.conditionsMet()).thenReturn(true)
controller.addListener(listener)
@@ -127,44 +158,10 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
}
/**
- * A class which implements SmartspaceView and extends View. This is mocked to provide the right
- * object inheritance and interface implementation used in DreamSmartspaceController
- */
- private class TestView(context: Context?) : View(context),
- BcSmartspaceDataPlugin.SmartspaceView {
- override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
-
- override fun setPrimaryTextColor(color: Int) {}
-
- override fun setIsDreaming(isDreaming: Boolean) {}
-
- override fun setDozeAmount(amount: Float) {}
-
- override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
-
- override fun setFalsingManager(falsingManager: FalsingManager?) {}
-
- override fun setDnd(image: Drawable?, description: String?) {}
-
- override fun setNextAlarm(image: Drawable?, description: String?) {}
-
- override fun setMediaTarget(target: SmartspaceTarget?) {}
-
- override fun getSelectedPage(): Int { return 0; }
-
- override fun getCurrentCardTopPadding(): Int { return 0; }
- }
-
- /**
* Ensures session begins when a view is attached.
*/
@Test
fun testConnectOnViewCreate() {
- val controller = DreamsSmartspaceController(context,
- smartspaceManager, execution, uiExecutor, viewComponentFactory, precondition,
- Optional.of(targetFilter),
- Optional.of(plugin))
-
`when`(precondition.conditionsMet()).thenReturn(true)
controller.buildAndConnectView(Mockito.mock(ViewGroup::class.java))
@@ -183,4 +180,4 @@ class DreamSmartspaceControllerTest : SysuiTestCase() {
verify(session).close()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index fc4d9c42eb49..cf7f8dd26647 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -41,6 +41,7 @@ import android.view.WindowInsetsController.Behavior;
import androidx.test.filters.SmallTest;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.view.AppearanceRegion;
import com.android.systemui.SysuiTestCase;
@@ -53,6 +54,13 @@ import org.junit.Test;
@SmallTest
public class CommandQueueTest extends SysuiTestCase {
+ private static final LetterboxDetails[] TEST_LETTERBOX_DETAILS = new LetterboxDetails[] {
+ new LetterboxDetails(
+ /* letterboxInnerBounds= */ new Rect(100, 0, 200, 500),
+ /* letterboxFullBounds= */ new Rect(0, 0, 500, 100),
+ /* appAppearance= */ 123)
+ };
+
private CommandQueue mCommandQueue;
private Callbacks mCallbacks;
private static final int SECONDARY_DISPLAY = 1;
@@ -127,25 +135,27 @@ public class CommandQueueTest extends SysuiTestCase {
public void testOnSystemBarAttributesChanged() {
doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test");
+ BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
}
@Test
public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
- BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test");
+ BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
}
private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName) {
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ LetterboxDetails[] letterboxDetails) {
mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
+ navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ letterboxDetails);
waitForIdleSync();
verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
- eq(requestedVisibilities), eq(packageName));
+ eq(requestedVisibilities), eq(packageName), eq(letterboxDetails));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
index d67e26f138f2..9c25462b7c0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyguardIndicationControllerTest.java
@@ -30,6 +30,7 @@ import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewCont
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRANSIENT;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST;
import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED;
+import static com.android.systemui.keyguard.ScreenLifecycle.SCREEN_ON;
import static com.google.common.truth.Truth.assertThat;
@@ -212,7 +213,7 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
R.string.do_financed_disclosure_with_name, ORGANIZATION_NAME);
when(mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed(anyBoolean())).thenReturn(true);
- when(mScreenLifecycle.getScreenState()).thenReturn(ScreenLifecycle.SCREEN_ON);
+ when(mScreenLifecycle.getScreenState()).thenReturn(SCREEN_ON);
when(mKeyguardUpdateMonitor.isUserUnlocked(anyInt())).thenReturn(true);
when(mIndicationArea.findViewById(R.id.keyguard_indication_text_bottom))
@@ -954,64 +955,170 @@ public class KeyguardIndicationControllerTest extends SysuiTestCase {
}
@Test
- public void nonBypassFaceSuccess_touchExplorationEnabled_showsSwipeToOpen() {
- // GIVEN non bypass face auth and touch exploration is enabled
- when(mKeyguardBypassController.canBypass()).thenReturn(false);
- when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+ public void coEx_faceSuccess_showsPressToOpen() {
+ // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, no a11y enabled
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ when(mAccessibilityManager.isEnabled()).thenReturn(false);
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ createController();
+ mController.setVisible(true);
+
+ // WHEN face auth succeeds
+ when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+ mController.getKeyguardCallback().onBiometricAuthenticated(0,
+ BiometricSourceType.FACE, false);
+
+ // THEN 'face unlocked. press unlock icon to open' message shows
+ String pressToOpen = mContext.getString(R.string.keyguard_face_successful_unlock_press);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
+
+ assertThat(mTextView.getText()).isNotEqualTo(pressToOpen);
+ }
+
+
+ @Test
+ public void coEx_faceSuccess_touchExplorationEnabled_showsFaceUnlockedSwipeToOpen() {
+ // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y enabled
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ when(mAccessibilityManager.isEnabled()).thenReturn(true);
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
createController();
- String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
mController.setVisible(true);
// WHEN face authenticated
+ when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
mController.getKeyguardCallback().onBiometricAuthenticated(0,
BiometricSourceType.FACE, false);
- // THEN show 'swipe up to open' message
- verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
+ // THEN show 'face unlocked. swipe up to open' message
+ String faceUnlockedSwipeToOpen =
+ mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
}
@Test
- public void nonBypassFaceSuccess_a11yEnabled_showsSwipeToOpen() {
- // GIVEN non bypass face auth and a11y is enabled
- when(mKeyguardBypassController.canBypass()).thenReturn(false);
+ public void coEx_faceSuccess_a11yEnabled_showsFaceUnlockedSwipeToOpen() {
+ // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(true);
+ createController();
+ mController.setVisible(true);
+
+ // WHEN face auth is successful
+ when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
+ mController.getKeyguardCallback().onBiometricAuthenticated(0,
+ BiometricSourceType.FACE, false);
+
+ // THEN show 'swipe up to open' message
+ String faceUnlockedSwipeToOpen =
+ mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+ }
+
+ @Test
+ public void faceOnly_faceSuccess_showsFaceUnlockedSwipeToOpen() {
+ // GIVEN bouncer isn't showing, can skip bouncer, no udfps supported
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
createController();
- String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
mController.setVisible(true);
// WHEN face auth is successful
+ when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
mController.getKeyguardCallback().onBiometricAuthenticated(0,
BiometricSourceType.FACE, false);
// THEN show 'swipe up to open' message
+ String faceUnlockedSwipeToOpen =
+ mContext.getString(R.string.keyguard_face_successful_unlock_swipe);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, faceUnlockedSwipeToOpen);
+ }
+
+ @Test
+ public void udfpsOnly_a11yEnabled_showsSwipeToOpen() {
+ // GIVEN bouncer isn't showing, can skip bouncer, udfps is supported, a11y is enabled
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
+ when(mAccessibilityManager.isEnabled()).thenReturn(true);
+ when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
+ createController();
+ mController.setVisible(true);
+
+ // WHEN showActionToUnlock
+ mController.showActionToUnlock();
+
+ // THEN show 'swipe up to open' message
+ String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
}
@Test
- public void coEx_nonBypassFaceSuccess_showsPressLockIcon() {
- // GIVEN udfps is supported, non-bypass face auth, and no a11y enabled
+ public void udfpsOnly_showsPressToOpen() {
+ // GIVEN bouncer isn't showing, udfps is supported, a11y is NOT enabled, can skip bouncer
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(true);
when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true);
- when(mKeyguardBypassController.canBypass()).thenReturn(false);
- when(mKeyguardUpdateMonitor.getIsFaceAuthenticated()).thenReturn(true);
when(mAccessibilityManager.isEnabled()).thenReturn(false);
when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
+ createController();
+ mController.setVisible(true);
+
+ // WHEN showActionToUnlock
+ mController.showActionToUnlock();
+
+ // THEN show 'press unlock icon to open' message
+ String pressToOpen = mContext.getString(R.string.keyguard_unlock_press);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressToOpen);
+ }
+
+ @Test
+ public void canSkipBouncer_noSecurity_showSwipeToUnlockHint() {
+ // GIVEN bouncer isn't showing, can skip bouncer, no security (udfps isn't supported,
+ // face wasn't authenticated)
when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
.thenReturn(true);
+ when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false);
createController();
mController.setVisible(true);
- // WHEN face auth succeeds
- mController.getKeyguardCallback().onBiometricAuthenticated(0,
- BiometricSourceType.FACE, false);
+ // WHEN showActionToUnlock
+ mController.showActionToUnlock();
- // THEN press unlock icon to open message shows
- String pressLockIcon = mContext.getString(R.string.keyguard_face_successful_unlock_press);
- verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, pressLockIcon);
+ // THEN show 'swipe up to open' message
+ String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
+ }
- assertThat(mTextView.getText()).isNotEqualTo(pressLockIcon);
+ @Test
+ public void cannotSkipBouncer_showSwipeToUnlockHint() {
+ // GIVEN bouncer isn't showing and cannot skip bouncer
+ when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(false);
+ when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(KeyguardUpdateMonitor.getCurrentUser()))
+ .thenReturn(false);
+ createController();
+ mController.setVisible(true);
+
+ // WHEN showActionToUnlock
+ mController.showActionToUnlock();
+
+ // THEN show 'swipe up to open' message
+ String swipeToOpen = mContext.getString(R.string.keyguard_unlock);
+ verifyIndicationMessage(INDICATION_TYPE_BIOMETRIC_MESSAGE, swipeToOpen);
}
private void sendUpdateDisclosureBroadcast() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 562c97017862..fe1cd978ef91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -1,6 +1,5 @@
package com.android.systemui.statusbar
-import org.mockito.Mockito.`when` as whenever
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
@@ -14,6 +13,7 @@ import com.android.systemui.keyguard.WakefulnessLifecycle
import com.android.systemui.media.MediaHierarchyManager
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.qs.QS
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.AmbientState
@@ -22,7 +22,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll
import com.android.systemui.statusbar.phone.CentralSurfaces
import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
-import com.android.systemui.statusbar.phone.NotificationPanelViewController
import com.android.systemui.statusbar.phone.ScrimController
import com.android.systemui.statusbar.policy.FakeConfigurationController
import org.junit.After
@@ -45,6 +44,7 @@ import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
private fun <T> anyObject(): T {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 7687d1204541..dd2b66765fae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -29,7 +29,9 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -59,6 +61,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.KeyguardNotificationSuppressor;
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.NotificationStateChangedListener;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
@@ -325,6 +328,38 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase {
}
@Test
+ public void testUpdateIsPublicMode() {
+ when(mKeyguardStateController.isMethodSecure()).thenReturn(true);
+
+ NotificationStateChangedListener listener = mock(NotificationStateChangedListener.class);
+ mLockscreenUserManager.addNotificationStateChangedListener(listener);
+ mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class));
+
+ // first call explicitly sets user 0 to not public; notifies
+ mLockscreenUserManager.updatePublicMode();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ assertFalse(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+
+ // Calling again with keyguard now showing makes user 0 public; notifies
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ mLockscreenUserManager.updatePublicMode();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener).onNotificationStateChanged();
+ clearInvocations(listener);
+
+ // calling again has no changes; does not notify
+ mLockscreenUserManager.updatePublicMode();
+ assertTrue(mLockscreenUserManager.isLockscreenPublicMode(0));
+ verify(listener, never()).onNotificationStateChanged();
+ }
+
+ @Test
public void testShowSilentNotifications_settingSaysShow() {
mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 1);
mSettings.putInt(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, 1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
index 2691ff98f4ee..34d13c76399a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationRemoteInputManagerTest.java
@@ -16,12 +16,9 @@
package com.android.systemui.statusbar;
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
@@ -30,19 +27,14 @@ import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
import android.os.UserHandle;
-import android.service.notification.NotificationListenerService;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
-import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputActiveExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.RemoteInputHistoryExtender;
-import com.android.systemui.statusbar.NotificationRemoteInputManager.LegacyRemoteInputLifetimeExtender.SmartReplyHistoryExtender;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -52,8 +44,6 @@ import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.policy.RemoteInputUriController;
-import com.google.android.collect.Sets;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -76,23 +66,15 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
@Mock private NotificationRemoteInputManager.Callback mCallback;
@Mock private RemoteInputController mController;
@Mock private SmartReplyController mSmartReplyController;
- @Mock private NotificationListenerService.RankingMap mRanking;
@Mock private ExpandableNotificationRow mRow;
@Mock private StatusBarStateController mStateController;
@Mock private RemoteInputUriController mRemoteInputUriController;
@Mock private NotificationClickNotifier mClickNotifier;
-
- // Dependency mocks:
@Mock private NotificationEntryManager mEntryManager;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
private TestableNotificationRemoteInputManager mRemoteInputManager;
private NotificationEntry mEntry;
- private RemoteInputHistoryExtender mRemoteInputHistoryExtender;
- private SmartReplyHistoryExtender mSmartReplyHistoryExtender;
- private RemoteInputActiveExtender mRemoteInputActiveExtender;
- private TestableNotificationRemoteInputManager.FakeLegacyRemoteInputLifetimeExtender
- mLegacyRemoteInputLifetimeExtender;
@Before
public void setUp() {
@@ -121,21 +103,7 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
.build();
mEntry.setRow(mRow);
- mRemoteInputManager.setUpWithPresenterForTest(mCallback,
- mDelegate, mController);
- for (NotificationLifetimeExtender extender : mRemoteInputManager.getLifetimeExtenders()) {
- extender.setCallback(
- mock(NotificationLifetimeExtender.NotificationSafeToRemoveCallback.class));
- }
- }
-
- @Test
- public void testPerformOnRemoveNotification() {
- when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
- mRemoteInputManager.onPerformRemoveNotification(mEntry, mEntry.getKey());
-
- assertFalse(mEntry.mRemoteEditImeVisible);
- verify(mController).removeRemoteInput(mEntry, null);
+ mRemoteInputManager.setUpWithPresenterForTest(mCallback, mDelegate, mController);
}
@Test
@@ -143,7 +111,6 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
when(mController.isRemoteInputActive(mEntry)).thenReturn(true);
assertTrue(mRemoteInputManager.isRemoteInputActive(mEntry));
- assertTrue(mRemoteInputActiveExtender.shouldExtendLifetime(mEntry));
}
@Test
@@ -152,7 +119,6 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
when(mController.isSpinning(mEntry.getKey())).thenReturn(true);
assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
- assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
}
@Test
@@ -161,7 +127,6 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
mEntry.lastRemoteInputSent = SystemClock.elapsedRealtime();
assertTrue(mRemoteInputManager.shouldKeepForRemoteInputHistory(mEntry));
- assertTrue(mRemoteInputHistoryExtender.shouldExtendLifetime(mEntry));
}
@Test
@@ -170,20 +135,6 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
when(mSmartReplyController.isSendingSmartReply(mEntry.getKey())).thenReturn(true);
assertTrue(mRemoteInputManager.shouldKeepForSmartReplyHistory(mEntry));
- assertTrue(mSmartReplyHistoryExtender.shouldExtendLifetime(mEntry));
- }
-
- @Test
- public void testNotificationWithRemoteInputActiveIsRemovedOnCollapse() {
- mRemoteInputActiveExtender.setShouldManageLifetime(mEntry, true /* shouldManage */);
-
- assertEquals(mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive(),
- Sets.newArraySet(mEntry));
-
- mRemoteInputManager.onPanelCollapsed();
-
- assertTrue(
- mLegacyRemoteInputLifetimeExtender.getEntriesKeptForRemoteInputActive().isEmpty());
}
private class TestableNotificationRemoteInputManager extends NotificationRemoteInputManager {
@@ -227,28 +178,5 @@ public class NotificationRemoteInputManagerTest extends SysuiTestCase {
mRemoteInputController = controller;
}
- @NonNull
- @Override
- protected LegacyRemoteInputLifetimeExtender createLegacyRemoteInputLifetimeExtender(
- Handler mainHandler,
- NotificationEntryManager notificationEntryManager,
- SmartReplyController smartReplyController) {
- mLegacyRemoteInputLifetimeExtender = new FakeLegacyRemoteInputLifetimeExtender();
- return mLegacyRemoteInputLifetimeExtender;
- }
-
- class FakeLegacyRemoteInputLifetimeExtender extends LegacyRemoteInputLifetimeExtender {
-
- @Override
- protected void addLifetimeExtenders() {
- mRemoteInputActiveExtender = new RemoteInputActiveExtender();
- mRemoteInputHistoryExtender = new RemoteInputHistoryExtender();
- mSmartReplyHistoryExtender = new SmartReplyHistoryExtender();
- mLifetimeExtenders.add(mRemoteInputHistoryExtender);
- mLifetimeExtenders.add(mSmartReplyHistoryExtender);
- mLifetimeExtenders.add(mRemoteInputActiveExtender);
- }
- }
-
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
index 3500e4d5f701..c75aa81e4c43 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SmartReplyControllerTest.java
@@ -17,15 +17,15 @@ package com.android.systemui.statusbar;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import android.app.ActivityManager;
import android.app.Notification;
import android.os.Handler;
-import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
@@ -39,14 +39,12 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.coordinator.RemoteInputCoordinator;
+import com.android.systemui.statusbar.notification.collection.notifcollection.InternalNotifUpdater;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-import com.android.systemui.statusbar.policy.RemoteInputUriController;
import org.junit.Before;
import org.junit.Test;
@@ -54,8 +52,6 @@ import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import java.util.Optional;
-
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
@SmallTest
@@ -67,57 +63,50 @@ public class SmartReplyControllerTest extends SysuiTestCase {
private static final int TEST_CHOICE_COUNT = 4;
private static final int TEST_ACTION_COUNT = 3;
- private Notification mNotification;
private NotificationEntry mEntry;
private SmartReplyController mSmartReplyController;
- private NotificationRemoteInputManager mRemoteInputManager;
@Mock private NotificationVisibilityProvider mVisibilityProvider;
- @Mock private RemoteInputController.Delegate mDelegate;
- @Mock private NotificationRemoteInputManager.Callback mCallback;
@Mock private StatusBarNotification mSbn;
- @Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private IStatusBarService mIStatusBarService;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private RemoteInputUriController mRemoteInputUriController;
@Mock private NotificationClickNotifier mClickNotifier;
+ @Mock private InternalNotifUpdater mInternalNotifUpdater;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mDependency.injectTestDependency(NotificationEntryManager.class,
- mNotificationEntryManager);
mSmartReplyController = new SmartReplyController(
mock(DumpManager.class),
mVisibilityProvider,
mIStatusBarService,
mClickNotifier);
- mDependency.injectTestDependency(SmartReplyController.class,
+ RemoteInputCoordinator remoteInputCoordinator = new RemoteInputCoordinator(
+ mock(DumpManager.class),
+ new RemoteInputNotificationRebuilder(mContext),
+ mock(NotificationRemoteInputManager.class),
+ mock(Handler.class),
mSmartReplyController);
+ remoteInputCoordinator.setRemoteInputController(mock(RemoteInputController.class));
+ NotifPipeline notifPipeline = mock(NotifPipeline.class);
+ when(notifPipeline.getInternalNotifUpdater(anyString())).thenReturn(mInternalNotifUpdater);
+ remoteInputCoordinator.attach(notifPipeline);
- mRemoteInputManager = new NotificationRemoteInputManager(mContext,
- mock(NotifPipelineFlags.class),
- mock(NotificationLockscreenUserManager.class),
- mSmartReplyController,
- mVisibilityProvider,
- mNotificationEntryManager,
- new RemoteInputNotificationRebuilder(mContext),
- () -> Optional.of(mock(CentralSurfaces.class)),
- mStatusBarStateController,
- Handler.createAsync(Looper.myLooper()),
- mRemoteInputUriController,
- mClickNotifier,
- mock(ActionClickLogger.class),
- mock(DumpManager.class));
- mRemoteInputManager.setUpWithCallback(mCallback, mDelegate);
- mNotification = new Notification.Builder(mContext, "")
+ Notification notification = new Notification.Builder(mContext, "")
.setSmallIcon(R.drawable.ic_person)
.setContentTitle("Title")
.setContentText("Text").build();
-
- mSbn = new StatusBarNotification(TEST_PACKAGE_NAME, TEST_PACKAGE_NAME, 0, null, TEST_UID,
- 0, mNotification, new UserHandle(ActivityManager.getCurrentUser()), null, 0);
+ mSbn = new StatusBarNotification(
+ TEST_PACKAGE_NAME,
+ TEST_PACKAGE_NAME,
+ 0,
+ null,
+ TEST_UID,
+ 0,
+ notification,
+ new UserHandle(ActivityManager.getCurrentUser()),
+ null,
+ 0);
mEntry = new NotificationEntryBuilder()
.setSbn(mSbn)
.build();
@@ -128,10 +117,9 @@ public class SmartReplyControllerTest extends SysuiTestCase {
mSmartReplyController.smartReplySent(mEntry, TEST_CHOICE_INDEX, TEST_CHOICE_TEXT,
MetricsEvent.LOCATION_UNKNOWN, false /* modifiedBeforeSending */);
- // Sending smart reply should make calls to NotificationEntryManager
- // to update the notification with reply and spinner.
- verify(mNotificationEntryManager).updateNotification(
- argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), isNull());
+ // Sending smart reply should update the notification with reply and spinner.
+ verify(mInternalNotifUpdater).onInternalNotificationUpdate(
+ argThat(sbn -> sbn.getKey().equals(mSbn.getKey())), anyString());
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
index 9d5099cfafd0..81d5c4d52b74 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/SplitShadeLockScreenOverScrollerTest.kt
@@ -1,10 +1,10 @@
package com.android.systemui.statusbar
-import org.mockito.Mockito.`when` as whenever
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.qs.QS
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
import com.android.systemui.statusbar.phone.ScrimController
@@ -19,6 +19,7 @@ import org.mockito.Mockito.reset
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -32,6 +33,7 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
@Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
@Mock private lateinit var qS: QS
@Mock private lateinit var nsslController: NotificationStackScrollLayoutController
+ @Mock private lateinit var dumpManager: DumpManager
private lateinit var overScroller: SplitShadeLockScreenOverScroller
@@ -44,11 +46,12 @@ class SplitShadeLockScreenOverScrollerTest : SysuiTestCase() {
overScroller =
SplitShadeLockScreenOverScroller(
configurationController,
+ dumpManager,
context,
scrimController,
statusBarStateController,
- qS,
- nsslController)
+ { qS },
+ { nsslController })
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index e01ebbdda374..0d1879cb2593 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -72,6 +72,7 @@ import com.android.systemui.demomode.DemoModeController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.flags.Flags;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
import com.android.systemui.telephony.TelephonyListenerManager;
@@ -244,7 +245,8 @@ public class NetworkControllerBaseTest extends SysuiTestCase {
mWifiStatusTrackerFactory,
mMainHandler,
mFeatureFlags,
- mock(DumpManager.class)
+ mock(DumpManager.class),
+ mock(LogBuffer.class)
);
setupNetworkController();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index 3a0c203f76e0..e3dd6f4e6e40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -38,6 +38,7 @@ import android.testing.TestableLooper.RunWithLooper;
import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -125,16 +126,29 @@ public class NetworkControllerDataTest extends NetworkControllerBaseTest {
public void test4gDataIcon() {
// Switch to showing 4g icon and re-initialize the NetworkController.
mConfig.show4gForLte = true;
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
- mTelephonyListenerManager, mMockWm,
- mMockSm, mConfig, Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
+ mNetworkController = new NetworkControllerImpl(
+ mContext,
+ mMockCm,
+ mMockTm,
+ mTelephonyListenerManager,
+ mMockWm,
+ mMockSm,
+ mConfig,
+ Looper.getMainLooper(),
+ mFakeExecutor,
+ mCallbackHandler,
mock(AccessPointControllerImpl.class),
- mock(DataUsageController.class), mMockSubDefaults,
- mock(DeviceProvisionedController.class), mMockBd, mDemoModeController,
+ mock(DataUsageController.class),
+ mMockSubDefaults,
+ mock(DeviceProvisionedController.class),
+ mMockBd,
+ mDemoModeController,
mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
new Handler(TestableLooper.get(this).getLooper()),
- mFeatureFlags, mock(DumpManager.class));
+ mFeatureFlags,
+ mock(DumpManager.class),
+ mock(LogBuffer.class));
setupNetworkController();
setupDefaultSignal();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
index ae1b3d1e1f42..698899a8fc36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerSignalTest.java
@@ -43,6 +43,7 @@ import com.android.settingslib.mobile.TelephonyIcons;
import com.android.settingslib.net.DataUsageController;
import com.android.systemui.R;
import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.LogBuffer;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.util.CarrierConfigTracker;
@@ -86,7 +87,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mWifiStatusTrackerFactory,
mMainHandler,
mFeatureFlags,
- mock(DumpManager.class)
+ mock(DumpManager.class),
+ mock(LogBuffer.class)
);
TestableLooper.get(this).processAllMessages();
@@ -100,7 +102,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
when(mMockProvisionController.isCurrentUserSetup()).thenReturn(true);
// WHEN - a NetworkController is created
- mNetworkController = new NetworkControllerImpl(mContext,
+ mNetworkController = new NetworkControllerImpl(
+ mContext,
mMockCm,
mMockTm,
mTelephonyListenerManager,
@@ -120,8 +123,8 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
mWifiStatusTrackerFactory,
mMainHandler,
mFeatureFlags,
- mock(DumpManager.class)
- );
+ mock(DumpManager.class),
+ mock(LogBuffer.class));
TestableLooper.get(this).processAllMessages();
// THEN - NetworkController claims the user is not setup
@@ -133,15 +136,29 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
// Turn off mobile network support.
when(mMockTm.isDataCapable()).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
- mTelephonyListenerManager, mMockWm, mMockSm, mConfig,
- Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
- mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
- mDemoModeController, mock(CarrierConfigTracker.class),
+ mNetworkController = new NetworkControllerImpl(
+ mContext,
+ mMockCm,
+ mMockTm,
+ mTelephonyListenerManager,
+ mMockWm,
+ mMockSm,
+ mConfig,
+ Looper.getMainLooper(),
+ mFakeExecutor,
+ mCallbackHandler,
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class),
+ mMockSubDefaults,
+ mock(DeviceProvisionedController.class),
+ mMockBd,
+ mDemoModeController,
+ mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
- mMainHandler, mFeatureFlags,
- mock(DumpManager.class));
+ mMainHandler,
+ mFeatureFlags,
+ mock(DumpManager.class),
+ mock(LogBuffer.class));
setupNetworkController();
verifyLastMobileDataIndicators(false, -1, 0);
@@ -156,14 +173,29 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
when(mMockTm.getServiceState()).thenReturn(mServiceState);
when(mMockSm.getCompleteActiveSubscriptionInfoList()).thenReturn(Collections.emptyList());
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
- mTelephonyListenerManager, mMockWm, mMockSm, mConfig,
- Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
- mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
- mDemoModeController, mock(CarrierConfigTracker.class),
+ mNetworkController = new NetworkControllerImpl(
+ mContext,
+ mMockCm,
+ mMockTm,
+ mTelephonyListenerManager,
+ mMockWm,
+ mMockSm,
+ mConfig,
+ Looper.getMainLooper(),
+ mFakeExecutor,
+ mCallbackHandler,
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class),
+ mMockSubDefaults,
+ mock(DeviceProvisionedController.class),
+ mMockBd,
+ mDemoModeController,
+ mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
- mMainHandler, mFeatureFlags, mock(DumpManager.class));
+ mMainHandler,
+ mFeatureFlags,
+ mock(DumpManager.class),
+ mock(LogBuffer.class));
mNetworkController.registerListeners();
// Wait for the main looper to execute the previous command
@@ -226,14 +258,29 @@ public class NetworkControllerSignalTest extends NetworkControllerBaseTest {
// Turn off mobile network support.
when(mMockTm.isDataCapable()).thenReturn(false);
// Create a new NetworkController as this is currently handled in constructor.
- mNetworkController = new NetworkControllerImpl(mContext, mMockCm, mMockTm,
- mTelephonyListenerManager, mMockWm, mMockSm, mConfig,
- Looper.getMainLooper(), mFakeExecutor, mCallbackHandler,
- mock(AccessPointControllerImpl.class), mock(DataUsageController.class),
- mMockSubDefaults, mock(DeviceProvisionedController.class), mMockBd,
- mDemoModeController, mock(CarrierConfigTracker.class),
+ mNetworkController = new NetworkControllerImpl(
+ mContext,
+ mMockCm,
+ mMockTm,
+ mTelephonyListenerManager,
+ mMockWm,
+ mMockSm,
+ mConfig,
+ Looper.getMainLooper(),
+ mFakeExecutor,
+ mCallbackHandler,
+ mock(AccessPointControllerImpl.class),
+ mock(DataUsageController.class),
+ mMockSubDefaults,
+ mock(DeviceProvisionedController.class),
+ mMockBd,
+ mDemoModeController,
+ mock(CarrierConfigTracker.class),
mWifiStatusTrackerFactory,
- mMainHandler, mFeatureFlags, mock(DumpManager.class));
+ mMainHandler,
+ mFeatureFlags,
+ mock(DumpManager.class),
+ mock(LogBuffer.class));
setupNetworkController();
// No Subscriptions.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
index b44f53c67714..20747a05a360 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceControllerTest.kt
@@ -33,6 +33,8 @@ import android.view.View
import android.widget.FrameLayout
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.plugins.BcSmartspaceDataPlugin
import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
@@ -41,8 +43,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener
import com.android.systemui.settings.UserTracker
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.phone.KeyguardBypassController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener
import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -54,20 +55,20 @@ import com.android.systemui.util.mockito.capture
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.settings.SecureSettings
import com.android.systemui.util.time.FakeSystemClock
+import java.util.Optional
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.Optional
@SmallTest
class LockscreenSmartspaceControllerTest : SysuiTestCase() {
@@ -81,36 +82,56 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
private lateinit var activityStarter: ActivityStarter
@Mock
private lateinit var falsingManager: FalsingManager
+
@Mock
private lateinit var secureSettings: SecureSettings
+
@Mock
private lateinit var userTracker: UserTracker
+
@Mock
private lateinit var contentResolver: ContentResolver
+
@Mock
private lateinit var configurationController: ConfigurationController
+
@Mock
private lateinit var statusBarStateController: StatusBarStateController
+
+ @Mock
+ private lateinit var keyguardBypassController: KeyguardBypassController
+
@Mock
private lateinit var deviceProvisionedController: DeviceProvisionedController
+
@Mock
private lateinit var handler: Handler
@Mock
private lateinit var plugin: BcSmartspaceDataPlugin
+
@Mock
private lateinit var controllerListener: SmartspaceTargetListener
@Captor
private lateinit var sessionListenerCaptor: ArgumentCaptor<OnTargetsAvailableListener>
+
@Captor
private lateinit var userTrackerCaptor: ArgumentCaptor<UserTracker.Callback>
+
@Captor
private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver>
+
@Captor
private lateinit var configChangeListenerCaptor: ArgumentCaptor<ConfigurationListener>
+
@Captor
private lateinit var statusBarStateListenerCaptor: ArgumentCaptor<StateListener>
+
+ @Captor
+ private lateinit var bypassStateChangedListenerCaptor:
+ ArgumentCaptor<KeyguardBypassController.OnBypassStateChangedListener>
+
@Captor
private lateinit var deviceProvisionedCaptor: ArgumentCaptor<DeviceProvisionedListener>
@@ -119,6 +140,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
private lateinit var settingsObserver: ContentObserver
private lateinit var configChangeListener: ConfigurationListener
private lateinit var statusBarStateListener: StateListener
+ private lateinit var bypassStateChangeListener:
+ KeyguardBypassController.OnBypassStateChangedListener
private lateinit var deviceProvisionedListener: DeviceProvisionedListener
private lateinit var smartspaceView: SmartspaceView
@@ -177,11 +200,12 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
configurationController,
statusBarStateController,
deviceProvisionedController,
+ keyguardBypassController,
execution,
executor,
handler,
Optional.of(plugin)
- )
+ )
verify(deviceProvisionedController).addCallback(capture(deviceProvisionedCaptor))
deviceProvisionedListener = deviceProvisionedCaptor.value
@@ -310,6 +334,19 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
}
@Test
+ fun testKeyguardBypassEnabledUpdatesView() {
+ // GIVEN a connected smartspace session
+ connectSession()
+ `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+
+ // WHEN the doze amount changes
+ bypassStateChangeListener.onBypassStateChanged(true)
+
+ // We pass that along to the view
+ verify(smartspaceView).setKeyguardBypassEnabled(true)
+ }
+
+ @Test
fun testSensitiveTargetsAreNotFilteredIfAllowed() {
// GIVEN the active and managed users allow sensitive content
connectSession()
@@ -457,6 +494,8 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(contentResolver).unregisterContentObserver(settingsObserver)
verify(configurationController).removeCallback(configChangeListener)
verify(statusBarStateController).removeCallback(statusBarStateListener)
+ verify(keyguardBypassController)
+ .unregisterOnBypassStateChangedListener(bypassStateChangeListener)
}
@Test
@@ -478,6 +517,19 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
}
@Test
+ fun testViewGetInitializedWithBypassEnabledState() {
+ // GIVEN keyguard bypass is enabled.
+ `when`(keyguardBypassController.bypassEnabled).thenReturn(true)
+
+ // WHEN the view is being built
+ val view = controller.buildAndConnectView(fakeParent)
+ smartspaceView = view as SmartspaceView
+
+ // THEN the view is initialized with the keyguard bypass enabled state.
+ verify(smartspaceView).setKeyguardBypassEnabled(true)
+ }
+
+ @Test
fun testConnectAttemptBeforeInitializationShouldNotCreateSession() {
// GIVEN an uninitalized smartspaceView
// WHEN the device is provisioned
@@ -517,6 +569,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
verify(statusBarStateController).addCallback(statusBarStateListenerCaptor.capture())
statusBarStateListener = statusBarStateListenerCaptor.value
+ verify(keyguardBypassController)
+ .registerOnBypassStateChangedListener(capture(bypassStateChangedListenerCaptor))
+ bypassStateChangeListener = bypassStateChangedListenerCaptor.value
verify(smartspaceSession).requestSmartspaceUpdate()
clearInvocations(smartspaceSession)
@@ -585,6 +640,9 @@ class LockscreenSmartspaceControllerTest : SysuiTestCase() {
override fun setDozeAmount(amount: Float) {
}
+ override fun setKeyguardBypassEnabled(enabled: Boolean) {
+ }
+
override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
index 7d06abf5cd67..3fc0c8176f2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/DynamicPrivacyControllerTest.java
@@ -63,7 +63,7 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase {
mock(StatusBarKeyguardViewManager.class));
mDynamicPrivacyController.addListener(mListener);
// Disable dynamic privacy by default
- allowPrivateNotificationsInPublic(true);
+ allowNotificationsInPublic(false);
}
@Test
@@ -108,24 +108,21 @@ public class DynamicPrivacyControllerTest extends SysuiTestCase {
@Test
public void dynamicPrivacyOnlyWhenHidingPrivate() {
- // Verify that when only hiding notifications, this isn't enabled
- allowPrivateNotificationsInPublic(true);
- when(mLockScreenUserManager.shouldHideNotifications(any())).thenReturn(
- false);
- assertFalse("Dynamic privacy shouldn't be enabled when only hiding notifications",
+ // Verify that when hiding notifications, this isn't enabled
+ allowNotificationsInPublic(false);
+ assertFalse("Dynamic privacy shouldn't be enabled when hiding notifications",
mDynamicPrivacyController.isDynamicPrivacyEnabled());
- allowPrivateNotificationsInPublic(false);
- assertTrue("Should be enabled when hiding notification contents",
+ allowNotificationsInPublic(true);
+ assertTrue("Should be enabled whenever notifications are visible",
mDynamicPrivacyController.isDynamicPrivacyEnabled());
}
private void enableDynamicPrivacy() {
- allowPrivateNotificationsInPublic(false);
+ allowNotificationsInPublic(true);
}
- private void allowPrivateNotificationsInPublic(boolean allow) {
- when(mLockScreenUserManager.userAllowsPrivateNotificationsInPublic(anyInt())).thenReturn(
- allow);
+ private void allowNotificationsInPublic(boolean allow) {
+ when(mLockScreenUserManager.userAllowsNotificationsInPublic(anyInt())).thenReturn(allow);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 16b0376ba1f9..aeef6b04108e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.notification;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_CANCEL;
+import static android.service.notification.NotificationStats.DISMISSAL_SHADE;
+import static android.service.notification.NotificationStats.DISMISS_SENTIMENT_NEUTRAL;
import static com.android.systemui.statusbar.notification.NotificationEntryManager.UNDEFINED_DISMISS_REASON;
@@ -41,6 +43,7 @@ import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
import android.app.ActivityManager;
@@ -50,6 +53,7 @@ import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.drawable.Icon;
import android.os.Handler;
+import android.os.RemoteException;
import android.os.UserHandle;
import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.NotificationListenerService.RankingMap;
@@ -91,7 +95,9 @@ import com.android.systemui.statusbar.notification.row.NotificationEntryManagerI
import com.android.systemui.statusbar.notification.row.RowInflaterTask;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
import org.junit.Test;
@@ -138,9 +144,14 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
@Mock private NotificationMediaManager mNotificationMediaManager;
@Mock private NotificationRowBinder mNotificationRowBinder;
@Mock private NotificationListener mNotificationListener;
+ @Mock private IStatusBarService mStatusBarService;
+
+ private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
+ private FakeExecutor mBgExecutor = new FakeExecutor(mFakeSystemClock);
private int mId;
private NotificationEntry mEntry;
+ private DismissedByUserStats mStats;
private StatusBarNotification mSbn;
private NotificationEntryManager mEntryManager;
@@ -191,6 +202,7 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
Handler.createAsync(TestableLooper.get(this).getLooper()));
mEntry = createNotification();
+ mStats = defaultStats(mEntry);
mSbn = mEntry.getSbn();
when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
@@ -201,9 +213,10 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
() -> mNotificationRowBinder,
() -> mRemoteInputManager,
mLeakDetector,
- mock(IStatusBarService.class),
+ mStatusBarService,
NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class)
+ mock(DumpManager.class),
+ mBgExecutor
);
mEntryManager.initialize(
mNotificationListener,
@@ -316,6 +329,31 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
}
@Test
+ public void testPerformRemoveNotification_sendRemovalToServer() throws RemoteException {
+ // GIVEN an entry manager with a notification
+ mEntryManager.addActiveNotificationForTest(mEntry);
+
+ // GIVEN interceptor that doesn't intercept
+ when(mRemoveInterceptor.onNotificationRemoveRequested(
+ eq(mEntry.getKey()), argThat(matchEntryOnKey()), anyInt()))
+ .thenReturn(false);
+
+ // WHEN the notification entry is removed
+ mEntryManager.performRemoveNotification(mSbn, mStats, REASON_CANCEL);
+
+ // THEN notification removal is sent to the server
+ FakeExecutor.exhaustExecutors(mBgExecutor);
+ verify(mStatusBarService).onNotificationClear(
+ mSbn.getPackageName(),
+ mSbn.getUser().getIdentifier(),
+ mSbn.getKey(),
+ mStats.dismissalSurface,
+ mStats.dismissalSentiment,
+ mStats.notificationVisibility);
+ verifyNoMoreInteractions(mStatusBarService);
+ }
+
+ @Test
public void testRemoveNotification_onEntryRemoveNotFiredIfEntryDoesntExist() {
mEntryManager.removeNotification("not_a_real_key", mRankingMap, UNDEFINED_DISMISS_REASON);
@@ -573,23 +611,6 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
any(NotificationVisibility.class), anyBoolean(), eq(UNDEFINED_DISMISS_REASON));
}
- private NotificationEntry createNotification() {
- Notification.Builder n = new Notification.Builder(mContext, "id")
- .setSmallIcon(R.drawable.ic_person)
- .setContentTitle("Title")
- .setContentText("Text");
-
- return new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setId(mId++)
- .setNotification(n.build())
- .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
- .setUser(new UserHandle(ActivityManager.getCurrentUser()))
- .build();
- }
-
/* Tests annexed from NotificationDataTest go here */
@Test
@@ -713,4 +734,28 @@ public class NotificationEntryManagerTest extends SysuiTestCase {
return mManagedNotifs.contains(notificationKey);
}
}
+
+ private NotificationEntry createNotification() {
+ Notification.Builder n = new Notification.Builder(mContext, "id")
+ .setSmallIcon(R.drawable.ic_person)
+ .setContentTitle("Title")
+ .setContentText("Text");
+
+ return new NotificationEntryBuilder()
+ .setPkg(TEST_PACKAGE_NAME)
+ .setOpPkg(TEST_PACKAGE_NAME)
+ .setUid(TEST_UID)
+ .setId(mId++)
+ .setNotification(n.build())
+ .setChannel(new NotificationChannel("id", "", IMPORTANCE_DEFAULT))
+ .setUser(new UserHandle(ActivityManager.getCurrentUser()))
+ .build();
+ }
+
+ private static DismissedByUserStats defaultStats(NotificationEntry entry) {
+ return new DismissedByUserStats(
+ DISMISSAL_SHADE,
+ DISMISS_SENTIMENT_NEUTRAL,
+ NotificationVisibility.obtain(entry.getKey(), 7, 2, true));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
index 9f82a5673c6e..f4458bbdc497 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorControllerTest.kt
@@ -6,11 +6,11 @@ import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.NotificationShadeWindowViewController
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationTestHelper
import com.android.systemui.statusbar.notification.stack.NotificationListContainer
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone
-import com.android.systemui.statusbar.phone.NotificationShadeWindowViewController
import com.android.systemui.statusbar.policy.HeadsUpUtil
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
@@ -19,8 +19,8 @@ import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.junit.MockitoJUnit
@SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
index 4507366e3073..ee7d55864931 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NoManSimulator.java
@@ -85,6 +85,11 @@ public class NoManSimulator {
mRankings.put(key, ranking);
}
+ /** This is for testing error cases: b/216384850 */
+ public Ranking removeRankingWithoutEvent(String key) {
+ return mRankings.remove(key);
+ }
+
private RankingMap buildRankingMap() {
return new RankingMap(mRankings.values().toArray(new Ranking[0]));
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
index 958d54230f1c..4df99be9efd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotifCollectionTest.java
@@ -92,6 +92,7 @@ import com.android.systemui.statusbar.notification.collection.notifcollection.No
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionLogger;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
+import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
import org.junit.Before;
@@ -146,14 +147,13 @@ public class NotifCollectionTest extends SysuiTestCase {
private NoManSimulator mNoMan;
private FakeSystemClock mClock = new FakeSystemClock();
+ private FakeExecutor mBgExecutor = new FakeExecutor(mClock);
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
-
when(mEulogizer.record(any(Exception.class))).thenAnswer(i -> i.getArguments()[0]);
mListenerInOrder = inOrder(mCollectionListener);
@@ -164,6 +164,7 @@ public class NotifCollectionTest extends SysuiTestCase {
mNotifPipelineFlags,
mLogger,
mMainHandler,
+ mBgExecutor,
mEulogizer,
mock(DumpManager.class));
mCollection.attach(mGroupCoalescer);
@@ -463,6 +464,8 @@ public class NotifCollectionTest extends SysuiTestCase {
DismissedByUserStats stats = defaultStats(entry2);
mCollection.dismissNotification(entry2, defaultStats(entry2));
+ FakeExecutor.exhaustExecutors(mBgExecutor);
+
// THEN we send the dismissal to system server
verify(mStatusBarService).onNotificationClear(
notif2.sbn.getPackageName(),
@@ -676,6 +679,8 @@ public class NotifCollectionTest extends SysuiTestCase {
mInterceptor1.onEndInterceptionCallback.onEndDismissInterception(mInterceptor1, entry,
stats);
+ FakeExecutor.exhaustExecutors(mBgExecutor);
+
// THEN we send the dismissal to system server
verify(mStatusBarService).onNotificationClear(
eq(notif.sbn.getPackageName()),
@@ -1213,6 +1218,7 @@ public class NotifCollectionTest extends SysuiTestCase {
new Pair<>(entry2, defaultStats(entry2))));
// THEN we send the dismissals to system server
+ FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mStatusBarService).onNotificationClear(
notif1.sbn.getPackageName(),
notif1.sbn.getUser().getIdentifier(),
@@ -1492,6 +1498,80 @@ public class NotifCollectionTest extends SysuiTestCase {
}
@Test
+ public void testMissingRankingWhenRemovalFeatureIsDisabled() {
+ // GIVEN a pipeline with one two notifications
+ when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(false);
+ String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
+ String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
+ NotificationEntry entry1 = mCollectionListener.getEntry(key1);
+ NotificationEntry entry2 = mCollectionListener.getEntry(key2);
+ clearInvocations(mCollectionListener);
+
+ // GIVEN the message for removing key1 gets does not reach NotifCollection
+ Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
+ // WHEN the message for removing key2 arrives
+ mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
+
+ // THEN only entry2 gets removed
+ verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
+ verify(mCollectionListener).onEntryCleanUp(eq(entry2));
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
+ verify(mLogger, never()).logRecoveredRankings(any());
+ clearInvocations(mCollectionListener, mLogger);
+
+ // WHEN a ranking update includes key1 again
+ mNoMan.setRanking(key1, ranking1);
+ mNoMan.issueRankingUpdate();
+
+ // VERIFY that we do nothing but log the 'recovery'
+ verify(mCollectionListener).onRankingUpdate(any());
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
+ verify(mLogger).logRecoveredRankings(eq(List.of(key1)));
+ }
+
+ @Test
+ public void testMissingRankingWhenRemovalFeatureIsEnabled() {
+ // GIVEN a pipeline with one two notifications
+ when(mNotifPipelineFlags.removeUnrankedNotifs()).thenReturn(true);
+ String key1 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 1, "myTag")).key;
+ String key2 = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 2, "myTag")).key;
+ NotificationEntry entry1 = mCollectionListener.getEntry(key1);
+ NotificationEntry entry2 = mCollectionListener.getEntry(key2);
+ clearInvocations(mCollectionListener);
+
+ // GIVEN the message for removing key1 gets does not reach NotifCollection
+ Ranking ranking1 = mNoMan.removeRankingWithoutEvent(key1);
+ // WHEN the message for removing key2 arrives
+ mNoMan.retractNotif(entry2.getSbn(), REASON_APP_CANCEL);
+
+ // THEN both entry1 and entry2 get removed
+ verify(mCollectionListener).onEntryRemoved(eq(entry2), eq(REASON_APP_CANCEL));
+ verify(mCollectionListener).onEntryRemoved(eq(entry1), eq(REASON_UNKNOWN));
+ verify(mCollectionListener).onEntryCleanUp(eq(entry2));
+ verify(mCollectionListener).onEntryCleanUp(eq(entry1));
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger).logMissingRankings(eq(List.of(entry1)), eq(1), any());
+ verify(mLogger, never()).logRecoveredRankings(any());
+ clearInvocations(mCollectionListener, mLogger);
+
+ // WHEN a ranking update includes key1 again
+ mNoMan.setRanking(key1, ranking1);
+ mNoMan.issueRankingUpdate();
+
+ // VERIFY that we do nothing but log the 'recovery'
+ verify(mCollectionListener).onRankingUpdate(any());
+ verify(mCollectionListener).onRankingApplied();
+ verifyNoMoreInteractions(mCollectionListener);
+ verify(mLogger, never()).logMissingRankings(any(), anyInt(), any());
+ verify(mLogger).logRecoveredRankings(eq(List.of(key1)));
+ }
+
+ @Test
public void testRegisterFutureDismissal() throws RemoteException {
// GIVEN a pipeline with one notification
NotifEvent notifEvent = mNoMan.postNotif(buildNotif(TEST_PACKAGE, 47, "myTag"));
@@ -1505,6 +1585,7 @@ public class NotifCollectionTest extends SysuiTestCase {
// WHEN finally dismissing
onDismiss.run();
+ FakeExecutor.exhaustExecutors(mBgExecutor);
verify(mStatusBarService).onNotificationClear(any(), anyInt(), eq(notifEvent.key),
anyInt(), anyInt(), any());
verifyNoMoreInteractions(mStatusBarService);
@@ -1610,9 +1691,9 @@ public class NotifCollectionTest extends SysuiTestCase {
return new CollectionEvent(rawEvent, requireNonNull(mEntryCaptor.getValue()));
}
- private void verifyBuiltList(Collection<NotificationEntry> list) {
- verify(mBuildListener).onBuildList(mBuildListCaptor.capture());
- assertEquals(new ArraySet<>(list), new ArraySet<>(mBuildListCaptor.getValue()));
+ private void verifyBuiltList(Collection<NotificationEntry> expectedList) {
+ verify(mBuildListener).onBuildList(mBuildListCaptor.capture(), any());
+ assertThat(mBuildListCaptor.getValue()).containsExactly(expectedList.toArray());
}
private static class RecordingCollectionListener implements NotifCollectionListener {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
index 769143ddbc0d..d4add7547656 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/NotificationEntryTest.java
@@ -108,6 +108,7 @@ public class NotificationEntryTest extends SysuiTestCase {
@Test
public void testBlockableEntryWhenCritical() {
doReturn(true).when(mChannel).isBlockable();
+ mEntry.setRanking(mEntry.getRanking());
assertTrue(mEntry.isBlockable());
}
@@ -117,6 +118,7 @@ public class NotificationEntryTest extends SysuiTestCase {
public void testBlockableEntryWhenCriticalAndChannelNotBlockable() {
doReturn(true).when(mChannel).isBlockable();
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
+ mEntry.setRanking(mEntry.getRanking());
assertTrue(mEntry.isBlockable());
}
@@ -125,6 +127,7 @@ public class NotificationEntryTest extends SysuiTestCase {
public void testNonBlockableEntryWhenCriticalAndChannelNotBlockable() {
doReturn(false).when(mChannel).isBlockable();
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
+ mEntry.setRanking(mEntry.getRanking());
assertFalse(mEntry.isBlockable());
}
@@ -164,6 +167,9 @@ public class NotificationEntryTest extends SysuiTestCase {
doReturn(true).when(mChannel).isImportanceLockedByCriticalDeviceFunction();
doReturn(false).when(mChannel).isBlockable();
+ mEntry.setRanking(mEntry.getRanking());
+
+ assertFalse(mEntry.isBlockable());
assertTrue(mEntry.isExemptFromDndVisualSuppression());
assertFalse(mEntry.shouldSuppressAmbient());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index 4e7e79f2cb26..dfa38abc1ff8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -53,6 +53,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.statusbar.NotificationInteractionTracker;
+import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.collection.ShadeListBuilder.OnRenderListListener;
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection;
@@ -569,7 +570,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
assertTrue(entry.hasFinishedInitialization());
// WHEN the pipeline is kicked off
- mReadyForBuildListener.onBuildList(singletonList(entry));
+ mReadyForBuildListener.onBuildList(singletonList(entry), "test");
mPipelineChoreographer.runIfScheduled();
// THEN the entry's initialization time is reset
@@ -1027,37 +1028,37 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// WHEN each pluggable is invalidated THEN the list is re-rendered
clearInvocations(mOnRenderListListener);
- packageFilter.invalidateList();
+ packageFilter.invalidateList(null);
assertTrue(mPipelineChoreographer.isScheduled());
mPipelineChoreographer.runIfScheduled();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- idPromoter.invalidateList();
+ idPromoter.invalidateList(null);
assertTrue(mPipelineChoreographer.isScheduled());
mPipelineChoreographer.runIfScheduled();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- section.invalidateList();
+ section.invalidateList(null);
assertTrue(mPipelineChoreographer.isScheduled());
mPipelineChoreographer.runIfScheduled();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- hypeComparator.invalidateList();
+ hypeComparator.invalidateList(null);
assertTrue(mPipelineChoreographer.isScheduled());
mPipelineChoreographer.runIfScheduled();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- sectionComparator.invalidateList();
+ sectionComparator.invalidateList(null);
assertTrue(mPipelineChoreographer.isScheduled());
mPipelineChoreographer.runIfScheduled();
verify(mOnRenderListListener).onRenderList(anyList());
clearInvocations(mOnRenderListListener);
- preRenderInvalidator.invalidateList();
+ preRenderInvalidator.invalidateList(null);
assertTrue(mPipelineChoreographer.isScheduled());
mPipelineChoreographer.runIfScheduled();
verify(mOnRenderListListener).onRenderList(anyList());
@@ -1583,7 +1584,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// WHEN visual stability manager allows group changes again
mStabilityManager.setAllowGroupChanges(true);
- mStabilityManager.invalidateList();
+ mStabilityManager.invalidateList(null);
mPipelineChoreographer.runIfScheduled();
// THEN entries are grouped
@@ -1622,7 +1623,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// WHEN section changes are allowed again
mStabilityManager.setAllowSectionChanges(true);
- mStabilityManager.invalidateList();
+ mStabilityManager.invalidateList(null);
mPipelineChoreographer.runIfScheduled();
// THEN the section updates
@@ -1718,7 +1719,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
public void testOutOfOrderPreGroupFilterInvalidationThrows() {
// GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage
NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(null);
mListBuilder.addPreGroupFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
@@ -1734,7 +1735,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// GIVEN a NotifPromoter that gets invalidated during the sorting stage
NotifPromoter promoter = new IdPromoter(47);
OnBeforeSortListener listener =
- (list) -> promoter.invalidateList();
+ (list) -> promoter.invalidateList(null);
mListBuilder.addPromoter(promoter);
mListBuilder.addOnBeforeSortListener(listener);
@@ -1750,7 +1751,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
// GIVEN a NotifComparator that gets invalidated during the finalizing stage
NotifComparator comparator = new HypeComparator(PACKAGE_5);
OnBeforeRenderListListener listener =
- (list) -> comparator.invalidateList();
+ (list) -> comparator.invalidateList(null);
mListBuilder.setComparators(singletonList(comparator));
mListBuilder.addOnBeforeRenderListListener(listener);
@@ -1765,7 +1766,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
public void testOutOfOrderPreRenderFilterInvalidationThrows() {
// GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage
NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeRenderListListener listener = (list) -> filter.invalidateList();
+ OnBeforeRenderListListener listener = (list) -> filter.invalidateList(null);
mListBuilder.addFinalizeFilter(filter);
mListBuilder.addOnBeforeRenderListListener(listener);
@@ -1797,6 +1798,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
@Test
public void testStableMultipleSectionOrdering() {
+ // WHEN the list is originally built with reordering disabled
mListBuilder.setSectioners(asList(
new PackageSectioner(PACKAGE_1), new PackageSectioner(PACKAGE_2)));
mStabilityManager.setAllowEntryReordering(false);
@@ -1807,19 +1809,101 @@ public class ShadeListBuilderTest extends SysuiTestCase {
addNotif(3, PACKAGE_1).setRank(3);
dispatchBuild();
+ // VERIFY the order and that entry reordering has not been suppressed
verifyBuiltList(
notif(0),
notif(1),
notif(3),
notif(2)
);
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the ranks change
+ setNewRank(notif(0).entry, 4);
+ dispatchBuild();
+
+ // VERIFY the order does not change that entry reordering has been suppressed
+ verifyBuiltList(
+ notif(0),
+ notif(1),
+ notif(3),
+ notif(2)
+ );
+ verify(mStabilityManager).onEntryReorderSuppressed();
+
+ // WHEN reordering is now allowed again
+ mStabilityManager.setAllowEntryReordering(true);
+ dispatchBuild();
+
+ // VERIFY that list order changes
+ verifyBuiltList(
+ notif(1),
+ notif(3),
+ notif(0),
+ notif(2)
+ );
+ }
+
+ @Test
+ public void testStableChildOrdering() {
+ // WHEN the list is originally built with reordering disabled
+ mStabilityManager.setAllowEntryReordering(false);
+ addGroupSummary(0, PACKAGE_1, GROUP_1).setRank(0);
+ addGroupChild(1, PACKAGE_1, GROUP_1).setRank(1);
+ addGroupChild(2, PACKAGE_1, GROUP_1).setRank(2);
+ addGroupChild(3, PACKAGE_1, GROUP_1).setRank(3);
+ dispatchBuild();
+
+ // VERIFY the order and that entry reordering has not been suppressed
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(1),
+ child(2),
+ child(3)
+ )
+ );
+ verify(mStabilityManager, never()).onEntryReorderSuppressed();
+
+ // WHEN the ranks change
+ setNewRank(notif(2).entry, 5);
+ dispatchBuild();
+
+ // VERIFY the order does not change that entry reordering has been suppressed
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(1),
+ child(2),
+ child(3)
+ )
+ );
+ verify(mStabilityManager).onEntryReorderSuppressed();
+
+ // WHEN reordering is now allowed again
+ mStabilityManager.setAllowEntryReordering(true);
+ dispatchBuild();
+
+ // VERIFY that list order changes
+ verifyBuiltList(
+ group(
+ summary(0),
+ child(1),
+ child(3),
+ child(2)
+ )
+ );
+ }
+
+ private static void setNewRank(NotificationEntry entry, int rank) {
+ entry.setRanking(new RankingBuilder(entry.getRanking()).setRank(rank).build());
}
@Test
public void testInOrderPreRenderFilter() {
// GIVEN a PreRenderFilter that gets invalidated during the grouping stage
NotifFilter filter = new PackageFilter(PACKAGE_5);
- OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList();
+ OnBeforeTransformGroupsListener listener = (list) -> filter.invalidateList(null);
mListBuilder.addFinalizeFilter(filter);
mListBuilder.addOnBeforeTransformGroupsListener(listener);
@@ -1852,8 +1936,8 @@ public class ShadeListBuilderTest extends SysuiTestCase {
mListBuilder.addFinalizeFilter(filter2);
// WHEN both filters invalidate
- filter1.invalidateList();
- filter2.invalidateList();
+ filter1.invalidateList(null);
+ filter2.invalidateList(null);
// THEN the pipeline choreographer is scheduled to evaluate, AND the pipeline hasn't
// actually run.
@@ -2008,7 +2092,7 @@ public class ShadeListBuilderTest extends SysuiTestCase {
mPendingSet.clear();
}
- mReadyForBuildListener.onBuildList(mEntrySet);
+ mReadyForBuildListener.onBuildList(mEntrySet, "test");
mPipelineChoreographer.runIfScheduled();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 699f77f9b7bb..2ee31265ff7b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -59,6 +59,7 @@ import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.util.ArrayList
+import java.util.function.Consumer
import org.mockito.Mockito.`when` as whenever
@SmallTest
@@ -75,6 +76,7 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
private lateinit var mBeforeFinalizeFilterListener: OnBeforeFinalizeFilterListener
private lateinit var mOnHeadsUpChangedListener: OnHeadsUpChangedListener
private lateinit var mNotifSectioner: NotifSectioner
+ private lateinit var mActionPressListener: Consumer<NotificationEntry>
private val mNotifPipeline: NotifPipeline = mock()
private val mLogger = HeadsUpCoordinatorLogger(logcatLogBuffer(), verbose = true)
@@ -131,6 +133,9 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
mOnHeadsUpChangedListener = withArgCaptor {
verify(mHeadsUpManager).addListener(capture())
}
+ mActionPressListener = withArgCaptor {
+ verify(mRemoteInputManager).addActionPressListener(capture())
+ }
given(mHeadsUpManager.allEntries).willAnswer { mHuns.stream() }
given(mHeadsUpManager.isAlerting(anyString())).willAnswer { invocation ->
val key = invocation.getArgument<String>(0)
@@ -199,6 +204,19 @@ class HeadsUpCoordinatorTest : SysuiTestCase() {
}
@Test
+ fun hunExtensionCancelledWhenHunActionPressed() {
+ whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
+ addHUN(mEntry)
+ whenever(mHeadsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
+ whenever(mHeadsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
+ assertTrue(mNotifLifetimeExtender.maybeExtendLifetime(mEntry, 0))
+ mActionPressListener.accept(mEntry)
+ mExecutor.advanceClockToLast()
+ mExecutor.runAllReady()
+ verify(mHeadsUpManager, times(1)).removeNotification(eq(mEntry.key), eq(true))
+ }
+
+ @Test
fun testCancelUpdatedStickyNotification() {
whenever(mHeadsUpManager.isSticky(anyString())).thenReturn(true)
addHUN(mEntry)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
index d21053bcd691..27542a462d36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HideNotifsForOtherUsersCoordinatorTest.java
@@ -18,7 +18,9 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -51,7 +53,6 @@ public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase {
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock private NotifPipeline mNotifPipeline;
@Mock private PluggableListener<NotifFilter> mInvalidationListener;
- @Mock private SharedCoordinatorLogger mLogger;
@Captor private ArgumentCaptor<UserChangedListener> mUserChangedListenerCaptor;
@Captor private ArgumentCaptor<NotifFilter> mNotifFilterCaptor;
@@ -66,7 +67,7 @@ public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
HideNotifsForOtherUsersCoordinator coordinator =
- new HideNotifsForOtherUsersCoordinator(mLockscreenUserManager, mLogger);
+ new HideNotifsForOtherUsersCoordinator(mLockscreenUserManager);
coordinator.attach(mNotifPipeline);
verify(mLockscreenUserManager).addUserChangedListener(mUserChangedListenerCaptor.capture());
@@ -102,6 +103,6 @@ public class HideNotifsForOtherUsersCoordinatorTest extends SysuiTestCase {
mCapturedUserChangeListener.onCurrentProfilesChanged(new SparseArray<>());
// THEN the filter is invalidated
- verify(mInvalidationListener).onPluggableInvalidated(mCapturedNotifFilter);
+ verify(mInvalidationListener).onPluggableInvalidated(eq(mCapturedNotifFilter), any());
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
deleted file mode 100644
index d082d74078db..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.coordinator;
-
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-
-import android.os.Handler;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
-import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * TODO(b/224771204) Create test cases
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@Ignore
-public class KeyguardCoordinatorTest extends SysuiTestCase {
- private static final int NOTIF_USER_ID = 0;
- private static final int CURR_USER_ID = 1;
-
- @Mock private Handler mMainHandler;
- @Mock private KeyguardStateController mKeyguardStateController;
- @Mock private BroadcastDispatcher mBroadcastDispatcher;
- @Mock private StatusBarStateController mStatusBarStateController;
- @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private SectionHeaderVisibilityProvider mSectionHeaderVisibilityProvider;
- @Mock private NotifPipeline mNotifPipeline;
- @Mock private KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
-
- private NotificationEntry mEntry;
- private NotifFilter mKeyguardFilter;
-
- @Before
- public void setup() {
- MockitoAnnotations.initMocks(this);
- KeyguardCoordinator keyguardCoordinator = new KeyguardCoordinator(
- mStatusBarStateController,
- mKeyguardUpdateMonitor, mHighPriorityProvider, mSectionHeaderVisibilityProvider,
- mKeyguardNotificationVisibilityProvider, mock(SharedCoordinatorLogger.class));
-
- mEntry = new NotificationEntryBuilder()
- .setUser(new UserHandle(NOTIF_USER_ID))
- .build();
-
- ArgumentCaptor<NotifFilter> filterCaptor = ArgumentCaptor.forClass(NotifFilter.class);
- keyguardCoordinator.attach(mNotifPipeline);
- verify(mNotifPipeline, times(1)).addFinalizeFilter(filterCaptor.capture());
- mKeyguardFilter = filterCaptor.getValue();
- }
-}
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
new file mode 100644
index 000000000000..7e2e6f619946
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinatorTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
+import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class KeyguardCoordinatorTest : SysuiTestCase() {
+ private val notifPipeline: NotifPipeline = mock()
+ private val keyguardNotifVisibilityProvider: KeyguardNotificationVisibilityProvider = mock()
+ private val sectionHeaderVisibilityProvider: SectionHeaderVisibilityProvider = mock()
+ private val statusBarStateController: StatusBarStateController = mock()
+
+ private lateinit var onStateChangeListener: Consumer<String>
+ private lateinit var keyguardFilter: NotifFilter
+
+ @Before
+ fun setup() {
+ val keyguardCoordinator = KeyguardCoordinator(
+ keyguardNotifVisibilityProvider,
+ sectionHeaderVisibilityProvider,
+ statusBarStateController
+ )
+ keyguardCoordinator.attach(notifPipeline)
+ onStateChangeListener = withArgCaptor {
+ verify(keyguardNotifVisibilityProvider).addOnStateChangedListener(capture())
+ }
+ keyguardFilter = withArgCaptor {
+ verify(notifPipeline).addFinalizeFilter(capture())
+ }
+ }
+
+ @Test
+ fun testSetSectionHeadersVisibleInShade() {
+ clearInvocations(sectionHeaderVisibilityProvider)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.SHADE)
+ onStateChangeListener.accept("state change")
+ verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(true)
+ }
+
+ @Test
+ fun testSetSectionHeadersNotVisibleOnKeyguard() {
+ clearInvocations(sectionHeaderVisibilityProvider)
+ whenever(statusBarStateController.state).thenReturn(StatusBarState.KEYGUARD)
+ onStateChangeListener.accept("state change")
+ verify(sectionHeaderVisibilityProvider).sectionHeadersVisible = eq(false)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
index d327be4c436a..f4adf6927e31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/PreparationCoordinatorTest.java
@@ -31,6 +31,7 @@ import static org.mockito.Mockito.when;
import static java.util.Objects.requireNonNull;
+import android.os.Handler;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -42,7 +43,6 @@ import com.android.internal.statusbar.IStatusBarService;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.ListEntry;
@@ -57,8 +57,10 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.OnBefo
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NotifViewBarn;
import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager;
+import com.android.systemui.util.settings.SecureSettings;
import org.junit.Before;
import org.junit.Test;
@@ -97,8 +99,10 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Mock private IStatusBarService mService;
@Mock private BindEventManagerImpl mBindEventManagerImpl;
@Mock private NotificationLockscreenUserManager mLockscreenUserManager;
+ @Mock private Handler mHandler;
+ @Mock private SecureSettings mSecureSettings;
@Spy private FakeNotifInflater mNotifInflater = new FakeNotifInflater();
- private final SectionClassifier mSectionClassifier = new SectionClassifier();
+ private final SectionStyleProvider mSectionStyleProvider = new SectionStyleProvider();
private NotifUiAdjustmentProvider mAdjustmentProvider;
@@ -110,8 +114,11 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mAdjustmentProvider =
- new NotifUiAdjustmentProvider(mLockscreenUserManager, mSectionClassifier);
+ mAdjustmentProvider = new NotifUiAdjustmentProvider(
+ mHandler,
+ mSecureSettings,
+ mLockscreenUserManager,
+ mSectionStyleProvider);
mEntry = getNotificationEntryBuilder().setParent(ROOT_ENTRY).build();
mInflationError = new Exception(TEST_MESSAGE);
mErrorManager = new NotifInflationErrorManager();
@@ -449,13 +456,15 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
mInflateCallbacks.put(entry, callback);
}
+
@Override
public void rebindViews(@NonNull NotificationEntry entry, @NonNull Params params,
@NonNull InflationCallback callback) {
}
@Override
- public void abortInflation(@NonNull NotificationEntry entry) {
+ public boolean abortInflation(@NonNull NotificationEntry entry) {
+ return false;
}
public InflationCallback getInflateCallback(NotificationEntry entry) {
@@ -465,6 +474,10 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
public void invokeInflateCallbackForEntry(NotificationEntry entry) {
getInflateCallback(entry).onInflationFinished(entry, entry.getRowController());
}
+
+ @Override
+ public void releaseViews(@NonNull NotificationEntry entry) {
+ }
}
private void fireAddEvents(List<? extends ListEntry> entries) {
@@ -490,7 +503,7 @@ public class PreparationCoordinatorTest extends SysuiTestCase {
private static final int TEST_MAX_GROUP_DELAY = 100;
private void setSectionIsLowPriority(boolean minimized) {
- mSectionClassifier.setMinimizedSections(minimized
+ mSectionStyleProvider.setMinimizedSections(minimized
? Collections.singleton(mNotifSection.getSectioner())
: Collections.emptyList());
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
index 15c1cb7b123a..50b3fc7e1c42 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RankingCoordinatorTest.java
@@ -42,7 +42,6 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SbnBuilder;
-import com.android.systemui.statusbar.notification.SectionClassifier;
import com.android.systemui.statusbar.notification.collection.ListEntry;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -50,6 +49,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner;
import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider;
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider;
import com.android.systemui.statusbar.notification.collection.render.NodeController;
import com.android.systemui.statusbar.notification.collection.render.SectionHeaderController;
@@ -70,7 +70,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
@Mock private StatusBarStateController mStatusBarStateController;
@Mock private HighPriorityProvider mHighPriorityProvider;
- @Mock private SectionClassifier mSectionClassifier;
+ @Mock private SectionStyleProvider mSectionStyleProvider;
@Mock private NotifPipeline mNotifPipeline;
@Mock private NodeController mAlertingHeaderController;
@Mock private NodeController mSilentNodeController;
@@ -94,7 +94,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mRankingCoordinator = new RankingCoordinator(
mStatusBarStateController,
mHighPriorityProvider,
- mSectionClassifier,
+ mSectionStyleProvider,
mAlertingHeaderController,
mSilentHeaderController,
mSilentNodeController);
@@ -102,7 +102,7 @@ public class RankingCoordinatorTest extends SysuiTestCase {
mEntry.setRanking(getRankingForUnfilteredNotif().build());
mRankingCoordinator.attach(mNotifPipeline);
- verify(mSectionClassifier).setMinimizedSections(any());
+ verify(mSectionStyleProvider).setMinimizedSections(any());
verify(mNotifPipeline, times(2)).addPreGroupFilter(mNotifFilterCaptor.capture());
mCapturedSuspendedFilter = mNotifFilterCaptor.getAllValues().get(0);
mCapturedDozingFilter = mNotifFilterCaptor.getAllValues().get(1);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
index 447ba1510e13..3f3de009fb04 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/RowAppearanceCoordinatorTest.kt
@@ -21,13 +21,13 @@ import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.AssistantFeedbackController
import com.android.systemui.statusbar.notification.FeedbackIcon
-import com.android.systemui.statusbar.notification.SectionClassifier
import com.android.systemui.statusbar.notification.collection.NotifPipeline
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.OnAfterRenderEntryListener
import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
import com.android.systemui.statusbar.notification.collection.render.NotifRowController
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
@@ -37,8 +37,8 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.verify
-import org.mockito.MockitoAnnotations.initMocks
import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations.initMocks
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -53,7 +53,7 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
@Mock private lateinit var pipeline: NotifPipeline
@Mock private lateinit var assistantFeedbackController: AssistantFeedbackController
- @Mock private lateinit var sectionClassifier: SectionClassifier
+ @Mock private lateinit var sectionStyleProvider: SectionStyleProvider
@Mock private lateinit var section1: NotifSection
@Mock private lateinit var section2: NotifSection
@@ -66,7 +66,7 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
coordinator = RowAppearanceCoordinator(
mContext,
assistantFeedbackController,
- sectionClassifier
+ sectionStyleProvider
)
coordinator.attach(pipeline)
beforeRenderListListener = withArgCaptor {
@@ -82,8 +82,8 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
@Test
fun testSetSystemExpandedOnlyOnFirst() {
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(false)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(false)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(false)
beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setSystemExpanded(eq(true))
@@ -93,8 +93,8 @@ class RowAppearanceCoordinatorTest : SysuiTestCase() {
@Test
fun testSetSystemExpandedNeverIfMinimized() {
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
- whenever(sectionClassifier.isMinimizedSection(eq(section1))).thenReturn(true)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(true)
+ whenever(sectionStyleProvider.isMinimizedSection(eq(section1))).thenReturn(true)
beforeRenderListListener.onBeforeRenderList(listOf(entry1, entry2))
afterRenderEntryListener.onAfterRenderEntry(entry1, controller1)
verify(controller1).setSystemExpanded(eq(false))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
index a2d8e3ddba24..27be4c802aec 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SensitiveContentCoordinatorTest.kt
@@ -34,6 +34,7 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.policy.KeyguardStateController
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 dagger.BindsInstance
@@ -79,7 +80,7 @@ class SensitiveContentCoordinatorTest : SysuiTestCase() {
dynamicPrivacyListener.onDynamicPrivacyChanged()
- verify(invalidationListener).onPluggableInvalidated(invalidator)
+ verify(invalidationListener).onPluggableInvalidated(eq(invalidator), any())
}
@Test
@@ -265,4 +266,4 @@ interface TestSensitiveContentCoordinatorComponent {
@BindsInstance keyguardStateController: KeyguardStateController
): TestSensitiveContentCoordinatorComponent
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
index fdff6e9a52f3..d4f05050b6dd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/SmartspaceDedupingCoordinatorTest.kt
@@ -35,21 +35,23 @@ import com.android.systemui.statusbar.notification.collection.listbuilder.plugga
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.withArgCaptor
import com.android.systemui.util.time.FakeSystemClock
+import java.util.concurrent.TimeUnit
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyString
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
-import java.util.concurrent.TimeUnit
@SmallTest
class SmartspaceDedupingCoordinatorTest : SysuiTestCase() {
@@ -349,7 +351,7 @@ class SmartspaceDedupingCoordinatorTest : SysuiTestCase() {
// THEN the new pipeline is invalidated (but the old one isn't because it's not
// necessary) because the notif should no longer be filtered out
- verify(pluggableListener).onPluggableInvalidated(filter)
+ verify(pluggableListener).onPluggableInvalidated(eq(filter), any())
verify(notificationEntryManager, never()).updateNotifications(anyString())
assertFalse(filter.shouldFilterOut(entry2HasNotRecentlyAlerted, now))
}
@@ -387,7 +389,7 @@ class SmartspaceDedupingCoordinatorTest : SysuiTestCase() {
}
private fun verifyPipelinesInvalidated() {
- verify(pluggableListener).onPluggableInvalidated(filter)
+ verify(pluggableListener).onPluggableInvalidated(eq(filter), any())
verify(notificationEntryManager).updateNotifications(anyString())
}
@@ -396,7 +398,7 @@ class SmartspaceDedupingCoordinatorTest : SysuiTestCase() {
}
private fun verifyPipelinesNotInvalidated() {
- verify(pluggableListener, never()).onPluggableInvalidated(filter)
+ verify(pluggableListener, never()).onPluggableInvalidated(eq(filter), any())
verify(notificationEntryManager, never()).updateNotifications(anyString())
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
new file mode 100644
index 000000000000..0830191fe035
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinatorTest.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.coordinator
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.keyguard.KeyguardUpdateMonitorCallback
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.NotificationLockscreenUserManager
+import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener
+import com.android.systemui.statusbar.notification.collection.NotifPipeline
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationGutsManager
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.withArgCaptor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class ViewConfigCoordinatorTest : SysuiTestCase() {
+ private lateinit var coordinator: ViewConfigCoordinator
+
+ // Captured
+ private lateinit var userChangedListener: UserChangedListener
+ private lateinit var configurationListener: ConfigurationController.ConfigurationListener
+ private lateinit var keyguardUpdateMonitorCallback: KeyguardUpdateMonitorCallback
+
+ // Mocks
+ private val entry: NotificationEntry = mock()
+ private val row: ExpandableNotificationRow = mock()
+ private val pipeline: NotifPipeline = mock()
+ private val configurationController: ConfigurationController = mock()
+ private val lockscreenUserManager: NotificationLockscreenUserManager = mock()
+ private val gutsManager: NotificationGutsManager = mock()
+ private val keyguardUpdateMonitor: KeyguardUpdateMonitor = mock()
+
+ @Before
+ fun setUp() {
+ whenever(pipeline.allNotifs).thenReturn(listOf(entry))
+ whenever(entry.row).thenReturn(row)
+ coordinator = ViewConfigCoordinator(
+ configurationController,
+ lockscreenUserManager,
+ gutsManager,
+ keyguardUpdateMonitor)
+ coordinator.attach(pipeline)
+ userChangedListener = withArgCaptor {
+ verify(lockscreenUserManager).addUserChangedListener(capture())
+ }
+ configurationListener = withArgCaptor {
+ verify(configurationController).addCallback(capture())
+ }
+ keyguardUpdateMonitorCallback = withArgCaptor {
+ verify(keyguardUpdateMonitor).registerCallback(capture())
+ }
+ }
+
+ @Test
+ fun uiModeChangePropagatesToRow() {
+ configurationListener.onUiModeChanged()
+ verify(entry).row
+ verify(row).onUiModeChanged()
+ verifyNoMoreInteractions(entry, row)
+ }
+
+ @Test
+ fun themeChangePropagatesToEntry() {
+ configurationListener.onThemeChanged()
+ verify(entry).onDensityOrFontScaleChanged()
+ verify(entry).areGutsExposed()
+ verifyNoMoreInteractions(entry, row)
+ }
+
+ @Test
+ fun densityChangePropagatesToEntry() {
+ configurationListener.onDensityOrFontScaleChanged()
+ verify(entry).onDensityOrFontScaleChanged()
+ verify(entry).areGutsExposed()
+ verifyNoMoreInteractions(entry, row)
+ }
+
+ @Test
+ fun switchingUserDefersChangesWithUserChangedEventAfter() {
+ // GIVEN switching users
+ keyguardUpdateMonitorCallback.onUserSwitching(10)
+
+ // WHEN configuration changes happen
+ configurationListener.onUiModeChanged()
+ configurationListener.onDensityOrFontScaleChanged()
+ configurationListener.onThemeChanged()
+
+ // VERIFY no changes are propagated
+ verifyNoMoreInteractions(entry, row)
+
+ // WHEN user switch completes
+ keyguardUpdateMonitorCallback.onUserSwitchComplete(10)
+
+ // VERIFY the changes propagate
+ verify(entry).row
+ verify(row).onUiModeChanged()
+ verify(entry).onDensityOrFontScaleChanged()
+ verify(entry).areGutsExposed()
+ verifyNoMoreInteractions(entry, row)
+ clearInvocations(entry, row)
+
+ // WHEN user change happens after the switching window
+ userChangedListener.onUserChanged(10)
+
+ // VERIFY user change itself does not re-trigger updates
+ verifyNoMoreInteractions(entry, row)
+ }
+
+ @Test
+ fun switchingUserDefersChangesWithUserChangedEventDuring() {
+ // GIVEN switching users
+ keyguardUpdateMonitorCallback.onUserSwitching(10)
+
+ // WHEN configuration changes happen
+ configurationListener.onUiModeChanged()
+ configurationListener.onDensityOrFontScaleChanged()
+ configurationListener.onThemeChanged()
+
+ // VERIFY no changes are propagated
+ verifyNoMoreInteractions(entry, row)
+
+ // WHEN user change happens during the switching window
+ userChangedListener.onUserChanged(10)
+
+ // VERIFY the changes propagate
+ verify(entry).row
+ verify(row).onUiModeChanged()
+ verify(entry).onDensityOrFontScaleChanged()
+ verify(entry).areGutsExposed()
+ verifyNoMoreInteractions(entry, row)
+ clearInvocations(entry, row)
+
+ // WHEN user switch completes
+ keyguardUpdateMonitorCallback.onUserSwitchComplete(10)
+
+ // VERIFY the switching window closing does not re-propagate
+ verifyNoMoreInteractions(entry, row)
+ }
+
+ @Test
+ fun switchingUserDefersChangesWithUserChangedEventBefore() {
+ // WHEN user change happens before configuration changes or switching window
+ userChangedListener.onUserChanged(10)
+
+ // VERIFY no changes happen
+ verifyNoMoreInteractions(entry, row)
+
+ // WHEN switching users then configuration changes happen
+ keyguardUpdateMonitorCallback.onUserSwitching(10)
+
+ configurationListener.onUiModeChanged()
+ configurationListener.onDensityOrFontScaleChanged()
+ configurationListener.onThemeChanged()
+
+ // VERIFY no changes happen
+ verifyNoMoreInteractions(entry, row)
+
+ // WHEN user switch completes
+ keyguardUpdateMonitorCallback.onUserSwitchComplete(10)
+
+ // VERIFY the changes propagate
+ verify(entry).row
+ verify(row).onUiModeChanged()
+ verify(entry).onDensityOrFontScaleChanged()
+ verify(entry).areGutsExposed()
+ verifyNoMoreInteractions(entry, row)
+ clearInvocations(entry, row)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index f3aa20ba4527..c961cec39208 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator;
import static junit.framework.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
@@ -35,6 +36,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotifPanelEvents;
import com.android.systemui.statusbar.notification.collection.GroupEntry;
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -43,7 +45,6 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifStabilityManager;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.Pluggable;
import com.android.systemui.statusbar.notification.collection.provider.VisualStabilityProvider;
-import com.android.systemui.statusbar.phone.NotifPanelEvents;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.time.FakeSystemClock;
@@ -55,6 +56,7 @@ import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
+import org.mockito.verification.VerificationMode;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@@ -130,13 +132,14 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
doAnswer(i -> {
mNotifStabilityManager.onBeginRun();
return null;
- }).when(mInvalidateListener).onPluggableInvalidated(eq(mNotifStabilityManager));
+ }).when(mInvalidateListener).onPluggableInvalidated(eq(mNotifStabilityManager), any());
}
@Test
public void testScreenOff_groupAndSectionChangesAllowed() {
// GIVEN screen is off, panel isn't expanded and device isn't pulsing
- setScreenOn(false);
+ setFullyDozed(true);
+ setSleepy(true);
setPanelExpanded(false);
setPulsing(false);
@@ -149,9 +152,42 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
}
@Test
+ public void testScreenTurningOff_groupAndSectionChangesNotAllowed() {
+ // GIVEN the screen is turning off (sleepy but partially dozed)
+ setFullyDozed(false);
+ setSleepy(true);
+ setPanelExpanded(true);
+ setPulsing(false);
+
+ // THEN group changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+ assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
+
+ // THEN section changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+ }
+
+ @Test
+ public void testScreenTurningOn_groupAndSectionChangesNotAllowed() {
+ // GIVEN the screen is turning on (still fully dozed, not sleepy)
+ setFullyDozed(true);
+ setSleepy(false);
+ setPanelExpanded(true);
+ setPulsing(false);
+
+ // THEN group changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
+ assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
+
+ // THEN section changes are NOT allowed
+ assertFalse(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
+ }
+
+ @Test
public void testPanelNotExpanded_groupAndSectionChangesAllowed() {
// GIVEN screen is on but the panel isn't expanded and device isn't pulsing
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(false);
setPulsing(false);
@@ -166,7 +202,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
public void testPanelExpanded_groupAndSectionChangesNotAllowed() {
// GIVEN the panel true expanded and device isn't pulsing
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
setPulsing(false);
@@ -181,7 +218,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
public void testPulsing_screenOff_groupAndSectionChangesNotAllowed() {
// GIVEN the device is pulsing and screen is off
- setScreenOn(false);
+ setFullyDozed(true);
+ setSleepy(true);
setPulsing(true);
// THEN group changes are NOT allowed
@@ -195,7 +233,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
public void testPulsing_panelNotExpanded_groupAndSectionChangesNotAllowed() {
// GIVEN the device is pulsing and screen is off with the panel not expanded
- setScreenOn(false);
+ setFullyDozed(true);
+ setSleepy(true);
setPanelExpanded(false);
setPulsing(true);
@@ -211,7 +250,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
public void testOverrideReorderingSuppression_onlySectionChangesAllowed() {
// GIVEN section changes typically wouldn't be allowed because the panel is expanded and
// we're not pulsing
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
setPulsing(true);
@@ -233,7 +273,8 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
public void testTemporarilyAllowSectionChanges_callsInvalidate() {
// GIVEN section changes typically wouldn't be allowed because the panel is expanded
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
setPulsing(false);
@@ -241,13 +282,14 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.uptimeMillis());
// THEN the notification list is invalidated
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
}
@Test
public void testTemporarilyAllowSectionChanges_noInvalidationCalled() {
// GIVEN section changes typically WOULD be allowed
- setScreenOn(false);
+ setFullyDozed(true);
+ setSleepy(true);
setPanelExpanded(false);
setPulsing(false);
@@ -255,13 +297,14 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
// THEN invalidate is not called because this entry was never suppressed from reordering
- verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(never());
}
@Test
public void testTemporarilyAllowSectionChangesTimeout() {
// GIVEN section changes typically WOULD be allowed
- setScreenOn(false);
+ setFullyDozed(true);
+ setSleepy(true);
setPanelExpanded(false);
setPulsing(false);
assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
@@ -271,7 +314,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
// THEN invalidate is not called because this entry was never suppressed from reordering;
// THEN section changes are allowed for this notification
- verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(never());
assertTrue(mNotifStabilityManager.isSectionChangeAllowed(mEntry));
// WHEN we're pulsing (now disallowing reordering)
@@ -292,20 +335,21 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
@Test
public void testTemporarilyAllowSectionChanges_isPulsingChangeBeforeTimeout() {
// GIVEN section changes typically wouldn't be allowed because the device is pulsing
- setScreenOn(false);
+ setFullyDozed(true);
+ setSleepy(true);
setPanelExpanded(false);
setPulsing(true);
// WHEN we temporarily allow section changes for this notification entry
mCoordinator.temporarilyAllowSectionChanges(mEntry, mFakeSystemClock.currentTimeMillis());
// can now reorder, so invalidates
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
// WHEN reordering is now allowed because device isn't pulsing anymore
setPulsing(false);
// THEN invalidate isn't called a second time since reordering was already allowed
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
}
@Test
@@ -315,22 +359,26 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
// WHEN device isn't pulsing anymore
setPulsing(false);
- // WHEN screen isn't on
- setScreenOn(false);
+ // WHEN fully dozed
+ setFullyDozed(true);
+
+ // WHEN sleepy
+ setSleepy(true);
// WHEN panel isn't expanded
setPanelExpanded(false);
// THEN we never see any calls to invalidate since there weren't any notifications that
// were being suppressed from grouping or section changes
- verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(never());
}
@Test
public void testNotSuppressingGroupChangesAnymore_invalidationCalled() {
// GIVEN visual stability is being maintained b/c panel is expanded
setPulsing(false);
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
@@ -340,7 +388,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setPanelExpanded(false);
// invalidate is called because we were previously suppressing a group change
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
}
@Test
@@ -354,7 +402,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setActivityLaunching(false);
// invalidate is called, b/c we were previously suppressing the pipeline from running
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
}
@Test
@@ -368,7 +416,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setPanelCollapsing(false);
// invalidate is called, b/c we were previously suppressing the pipeline from running
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
}
@Test
@@ -380,7 +428,7 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setPanelCollapsing(false);
// THEN invalidate is not called, b/c nothing has been suppressed
- verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(never());
}
@Test
@@ -392,14 +440,15 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setActivityLaunching(false);
// THEN invalidate is not called, b/c nothing has been suppressed
- verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(never());
}
@Test
public void testNotSuppressingEntryReorderingAnymoreWillInvalidate() {
// GIVEN visual stability is being maintained b/c panel is expanded
setPulsing(false);
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
@@ -410,14 +459,15 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setPanelExpanded(false);
// invalidate is called because we were previously suppressing an entry reorder
- verify(mInvalidateListener, times(1)).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(times(1));
}
@Test
public void testQueryingEntryReorderingButNotReportingReorderSuppressedDoesNotInvalidate() {
// GIVEN visual stability is being maintained b/c panel is expanded
setPulsing(false);
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
assertFalse(mNotifStabilityManager.isEntryReorderingAllowed(mEntry));
@@ -426,13 +476,14 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
setPanelExpanded(false);
// invalidate is not called because we were not told that an entry reorder was suppressed
- verify(mInvalidateListener, never()).onPluggableInvalidated(mNotifStabilityManager);
+ verifyStabilityManagerWasInvalidated(never());
}
@Test
public void testHeadsUp_allowedToChangeGroupAndSection() {
// GIVEN group + section changes disallowed
- setScreenOn(true);
+ setFullyDozed(false);
+ setSleepy(false);
setPanelExpanded(true);
setPulsing(true);
assertFalse(mNotifStabilityManager.isGroupChangeAllowed(mEntry));
@@ -450,6 +501,10 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
assertFalse(mNotifStabilityManager.isGroupPruneAllowed(mGroupEntry));
}
+ private void verifyStabilityManagerWasInvalidated(VerificationMode mode) {
+ verify(mInvalidateListener, mode).onPluggableInvalidated(eq(mNotifStabilityManager), any());
+ }
+
private void setActivityLaunching(boolean activityLaunching) {
mNotifPanelEventsCallback.onLaunchingActivityChanged(activityLaunching);
}
@@ -462,11 +517,16 @@ public class VisualStabilityCoordinatorTest extends SysuiTestCase {
mStatusBarStateListener.onPulsingChanged(pulsing);
}
- private void setScreenOn(boolean screenOn) {
- if (screenOn) {
- mWakefulnessObserver.onStartedWakingUp();
- } else {
+ private void setFullyDozed(boolean fullyDozed) {
+ float dozeAmount = fullyDozed ? 1 : 0;
+ mStatusBarStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
+ }
+
+ private void setSleepy(boolean sleepy) {
+ if (sleepy) {
mWakefulnessObserver.onFinishedGoingToSleep();
+ } else {
+ mWakefulnessObserver.onStartedWakingUp();
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
index dd15caebf9e6..246943e3088e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/inflation/NotifUiAdjustmentProviderTest.kt
@@ -1,29 +1,87 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.android.systemui.statusbar.notification.collection.inflation
+import android.database.ContentObserver
+import android.os.Handler
+import android.provider.Settings.Secure.SHOW_NOTIFICATION_SNOOZE
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper.RunWithLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.NotificationLockscreenUserManager
-import com.android.systemui.statusbar.notification.SectionClassifier
+import com.android.systemui.statusbar.notification.collection.GroupEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
+import com.android.systemui.statusbar.notification.collection.provider.SectionStyleProvider
+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.android.systemui.util.settings.SecureSettings
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
@SmallTest
@RunWith(AndroidTestingRunner::class)
@RunWithLooper
class NotifUiAdjustmentProviderTest : SysuiTestCase() {
private val lockscreenUserManager: NotificationLockscreenUserManager = mock()
- private val sectionClassifier: SectionClassifier = mock()
+ private val sectionStyleProvider: SectionStyleProvider = mock()
+ private val handler: Handler = mock()
+ private val secureSettings: SecureSettings = mock()
+ private val uri = FakeSettings().getUriFor(SHOW_NOTIFICATION_SNOOZE)
+ private val dirtyListener: Runnable = mock()
+
+ private val section = NotifSection(mock(), 0)
+ private val entry = NotificationEntryBuilder()
+ .setSection(section)
+ .setParent(GroupEntry.ROOT_ENTRY)
+ .build()
+
+ private lateinit var contentObserver: ContentObserver
private val adjustmentProvider = NotifUiAdjustmentProvider(
+ handler,
+ secureSettings,
lockscreenUserManager,
- sectionClassifier,
+ sectionStyleProvider,
)
+ @Before
+ fun setup() {
+ verifyNoMoreInteractions(secureSettings)
+ adjustmentProvider.addDirtyListener(dirtyListener)
+ verify(secureSettings).getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())
+ contentObserver = withArgCaptor {
+ verify(secureSettings).registerContentObserverForUser(
+ eq(SHOW_NOTIFICATION_SNOOZE), capture(), any()
+ )
+ }
+ verifyNoMoreInteractions(secureSettings, dirtyListener)
+ }
+
@Test
fun notifLockscreenStateChangeWillNotifDirty() {
val dirtyListener = mock<Runnable>()
@@ -33,6 +91,35 @@ class NotifUiAdjustmentProviderTest : SysuiTestCase() {
verify(lockscreenUserManager).addNotificationStateChangedListener(capture())
}
notifLocksreenStateChangeListener.onNotificationStateChanged()
- verify(dirtyListener).run();
+ verify(dirtyListener).run()
+ }
+
+ @Test
+ fun additionalAddDoesNotRegisterAgain() {
+ clearInvocations(secureSettings)
+ adjustmentProvider.addDirtyListener(mock())
+ verifyNoMoreInteractions(secureSettings)
+ }
+
+ @Test
+ fun onChangeWillQueryThenNotifyDirty() {
+ contentObserver.onChange(false, listOf(uri), 0, 0)
+ with(inOrder(secureSettings, dirtyListener)) {
+ verify(secureSettings).getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())
+ verify(dirtyListener).run()
+ }
+ }
+
+ @Test
+ fun changingSnoozeChangesProvidedAdjustment() {
+ whenever(secureSettings.getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())).thenReturn(0)
+ val original = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(original.isSnoozeEnabled).isFalse()
+
+ whenever(secureSettings.getInt(eq(SHOW_NOTIFICATION_SNOOZE), any())).thenReturn(1)
+ contentObserver.onChange(false, listOf(uri), 0, 0)
+ val withSnoozing = adjustmentProvider.calculateAdjustment(entry)
+ assertThat(withSnoozing.isSnoozeEnabled).isTrue()
+ assertThat(withSnoozing).isNotEqualTo(original)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt
new file mode 100644
index 000000000000..6c07174bce65
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLoggerTest.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.notifcollection
+
+import android.service.notification.NotificationListenerService.RankingMap
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper
+class NotifCollectionLoggerTest : SysuiTestCase() {
+ private val logger: NotifCollectionLogger = mock()
+ private val entry1: NotificationEntry = NotificationEntryBuilder().setId(1).build()
+ private val entry2: NotificationEntry = NotificationEntryBuilder().setId(2).build()
+
+ private fun mapOfEntries(vararg entries: NotificationEntry): Map<String, NotificationEntry> =
+ entries.associateBy { it.key }
+
+ private fun rankingMapOf(vararg entries: NotificationEntry): RankingMap =
+ RankingMap(entries.map { it.ranking }.toTypedArray())
+
+ @Test
+ fun testMaybeLogInconsistentRankings_logsNewlyInconsistentRanking() {
+ val rankingMap = rankingMapOf(entry1)
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = emptySet(),
+ newEntriesWithoutRankings = mapOfEntries(entry2),
+ rankingMap = rankingMap
+ )
+ verify(logger).logMissingRankings(
+ newlyInconsistentEntries = eq(listOf(entry2)),
+ totalInconsistent = eq(1),
+ rankingMap = eq(rankingMap),
+ )
+ verifyNoMoreInteractions(logger)
+ }
+
+ @Test
+ fun testMaybeLogInconsistentRankings_doesNotLogAlreadyInconsistentRanking() {
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = setOf(entry2.key),
+ newEntriesWithoutRankings = mapOfEntries(entry2),
+ rankingMap = rankingMapOf(entry1)
+ )
+ verifyNoMoreInteractions(logger)
+ }
+
+ @Test
+ fun testMaybeLogInconsistentRankings_logsWhenRankingIsAdded() {
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = setOf(entry2.key),
+ newEntriesWithoutRankings = mapOfEntries(),
+ rankingMap = rankingMapOf(entry1, entry2)
+ )
+ verify(logger).logRecoveredRankings(
+ newlyConsistentKeys = eq(listOf(entry2.key)),
+ )
+ verifyNoMoreInteractions(logger)
+ }
+
+ @Test
+ fun testMaybeLogInconsistentRankings_doesNotLogsWhenEntryIsRemoved() {
+ maybeLogInconsistentRankings(
+ logger = logger,
+ oldKeysWithoutRankings = setOf(entry2.key),
+ newEntriesWithoutRankings = mapOfEntries(),
+ rankingMap = rankingMapOf(entry1)
+ )
+ verifyNoMoreInteractions(logger)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
index 0e1865861cae..ac254abe60b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/NodeSpecBuilderTest.kt
@@ -19,7 +19,6 @@ package com.android.systemui.statusbar.notification.collection.render
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager
-import com.android.systemui.statusbar.notification.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.collection.GroupEntry
import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder
import com.android.systemui.statusbar.notification.collection.ListEntry
@@ -28,6 +27,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntryB
import com.android.systemui.statusbar.notification.collection.getAttachState
import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner
+import com.android.systemui.statusbar.notification.collection.provider.SectionHeaderVisibilityProvider
import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
import com.android.systemui.statusbar.notification.stack.BUCKET_PEOPLE
import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
@@ -413,4 +413,4 @@ private fun buildSection(
return nodeController
}
}, index)
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
index 0d5a5fe086a3..3f641df376ed 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/HeadsUpViewBinderTest.java
@@ -72,32 +72,32 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
});
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryBoundSuccessfully(eq("key"));
+ verify(mLogger).entryBoundSuccessfully(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryBoundSuccessfully(eq("key"));
+ verify(mLogger).entryBoundSuccessfully(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.unbindHeadsUpView(mEntry);
- verify(mLogger).entryContentViewMarkedFreeable(eq("key"));
+ verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryUnbound(eq("key"));
+ verify(mLogger).entryUnbound(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
}
@@ -111,12 +111,12 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
});
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.abortBindCallback(mEntry);
- verify(mLogger).currentOngoingBindingAborted(eq("key"));
+ verify(mLogger).currentOngoingBindingAborted(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
@@ -135,18 +135,18 @@ public class HeadsUpViewBinderTest extends SysuiTestCase {
});
mViewBinder.bindHeadsUpView(mEntry, null);
- verify(mLogger).startBindingHun(eq("key"));
+ verify(mLogger).startBindingHun(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
mViewBinder.unbindHeadsUpView(mEntry);
- verify(mLogger).currentOngoingBindingAborted(eq("key"));
- verify(mLogger).entryContentViewMarkedFreeable(eq("key"));
+ verify(mLogger).currentOngoingBindingAborted(eq(mEntry));
+ verify(mLogger).entryContentViewMarkedFreeable(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
callback.get().onBindFinished(mEntry);
- verify(mLogger).entryUnbound(eq("key"));
+ verify(mLogger).entryUnbound(eq(mEntry));
verifyNoMoreInteractions(mLogger);
clearInvocations(mLogger);
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
index 90627cb0fa78..5386171eeda2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderImplTest.java
@@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.interruption;
import static android.app.Notification.FLAG_BUBBLE;
+import static android.app.Notification.GROUP_ALERT_SUMMARY;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
@@ -24,13 +25,16 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_AMBIENT;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
+import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
import static com.android.systemui.statusbar.StatusBarState.SHADE;
+import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -58,6 +62,7 @@ import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
import org.junit.Test;
@@ -84,6 +89,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Mock
StatusBarStateController mStatusBarStateController;
@Mock
+ KeyguardStateController mKeyguardStateController;
+ @Mock
HeadsUpManager mHeadsUpManager;
@Mock
NotificationInterruptLogger mLogger;
@@ -95,12 +102,15 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
NotifPipelineFlags mFlags;
@Mock
KeyguardNotificationVisibilityProvider mKeyguardNotificationVisibilityProvider;
+ @Mock
+ PendingIntent mPendingIntent;
private NotificationInterruptStateProviderImpl mNotifInterruptionStateProvider;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
mNotifInterruptionStateProvider =
new NotificationInterruptStateProviderImpl(
@@ -111,6 +121,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
mNotificationFilter,
mBatteryController,
mStatusBarStateController,
+ mKeyguardStateController,
mHeadsUpManager,
mLogger,
mMockHandler,
@@ -196,7 +207,7 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
ensureStateForHeadsUpWhenAwake();
// WHEN this entry should be filtered out
- NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
when(mNotificationFilter.shouldFilterOut(entry)).thenReturn(true);
// THEN we shouldn't heads up this entry
@@ -205,11 +216,9 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
@Test
public void testDoNotRunFilterOnNewPipeline() {
- when(mFlags.isNewPipelineEnabled()).thenReturn(true);
// WHEN this entry should be filtered out
- NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
+ NotificationEntry entry = createNotification(IMPORTANCE_DEFAULT);
mNotifInterruptionStateProvider.shouldHeadsUp(entry);
- verify(mFlags, times(1)).isNewPipelineEnabled();
verify(mNotificationFilter, times(0)).shouldFilterOut(eq(entry));
}
@@ -325,7 +334,8 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
public void testShouldNotHeadsUp_filtered() throws RemoteException {
ensureStateForHeadsUpWhenAwake();
// Make canAlertCommon false by saying it's filtered out
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+ when(mKeyguardNotificationVisibilityProvider.shouldHideNotification(any()))
+ .thenReturn(true);
NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
@@ -422,6 +432,224 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
assertThat(mNotifInterruptionStateProvider.shouldHeadsUp(entry)).isFalse();
}
+ @Test
+ public void testShouldNotFullScreen_notPendingIntent_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_notPendingIntent();
+ }
+
+ @Test
+ public void testShouldNotFullScreen_notPendingIntent() throws RemoteException {
+ NotificationEntry entry = createNotification(IMPORTANCE_HIGH);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldNotFullScreen_notHighImportance_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_notHighImportance();
+ }
+
+ @Test
+ public void testShouldNotFullScreen_notHighImportance() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_DEFAULT, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Not important enough");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldNotFullScreen_isGroupAlertSilenced_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_isGroupAlertSilenced();
+ }
+
+ @Test
+ public void testShouldNotFullScreen_isGroupAlertSilenced() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ true);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger).logNoFullscreenWarning(entry, "GroupAlertBehavior will prevent HUN");
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldFullScreen_notInteractive_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldFullScreen_notInteractive();
+ }
+
+ @Test
+ public void testShouldFullScreen_notInteractive() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(false);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Device is not interactive");
+ }
+
+ @Test
+ public void testShouldFullScreen_isDreaming_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldFullScreen_isDreaming();
+ }
+
+ @Test
+ public void testShouldFullScreen_isDreaming() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(true);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Device is dreaming");
+ }
+
+ @Test
+ public void testShouldFullScreen_onKeyguard_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldFullScreen_onKeyguard();
+ }
+
+ @Test
+ public void testShouldFullScreen_onKeyguard() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(KEYGUARD);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Keyguard is showing");
+ }
+
+ @Test
+ public void testShouldNotFullScreen_willHun_withStrictFlag() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ testShouldNotFullScreen_willHun();
+ }
+
+ @Test
+ public void testShouldNotFullScreen_willHun() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoFullscreen(entry, "Expected to HUN");
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
+ @Test
+ public void testShouldFullScreen_packageSnoozed() throws RemoteException {
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Expected not to HUN");
+ }
+
+ @Test
+ public void testShouldFullScreen_snoozed_occluding_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Expected not to HUN while keyguard occluded");
+ }
+
+ @Test
+ public void testShouldFullScreen_snoozed_lockedShade_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE_LOCKED);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isTrue();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger, never()).logNoFullscreenWarning(any(), any());
+ verify(mLogger).logFullscreen(entry, "Keyguard is showing and not occluded");
+ }
+
+ @Test
+ public void testShouldNotFullScreen_snoozed_unlocked_withStrictRules() throws Exception {
+ when(mFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ NotificationEntry entry = createFsiNotification(IMPORTANCE_HIGH, /* silenced */ false);
+ when(mPowerManager.isInteractive()).thenReturn(true);
+ when(mPowerManager.isScreenOn()).thenReturn(true);
+ when(mDreamManager.isDreaming()).thenReturn(false);
+ when(mStatusBarStateController.getState()).thenReturn(SHADE);
+ when(mHeadsUpManager.isSnoozed("a")).thenReturn(true);
+ when(mKeyguardStateController.isShowing()).thenReturn(false);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+
+ assertThat(mNotifInterruptionStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+ .isFalse();
+ verify(mLogger).logNoHeadsUpPackageSnoozed(entry);
+ verify(mLogger, never()).logNoFullscreen(any(), any());
+ verify(mLogger).logNoFullscreenWarning(entry, "Expected not to HUN while not on keyguard");
+ verify(mLogger, never()).logFullscreen(any(), any());
+ }
+
/**
* Bubbles can happen.
*/
@@ -432,6 +660,17 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
}
/**
+ * Test that notification can bubble even if it is a child in a group and group settings are
+ * set to alert only for summary notifications.
+ */
+ @Test
+ public void testShouldBubbleUp_notifInGroupWithOnlySummaryAlerts() {
+ ensureStateForBubbleUp();
+ NotificationEntry bubble = createBubble("testgroup", GROUP_ALERT_SUMMARY);
+ assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(bubble)).isTrue();
+ }
+
+ /**
* If the notification doesn't have permission to bubble, it shouldn't bubble.
*/
@Test
@@ -492,22 +731,34 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
ensureStateForBubbleUp();
// Make canAlertCommon false by saying it's filtered out
- when(mNotificationFilter.shouldFilterOut(any())).thenReturn(true);
+ when(mKeyguardNotificationVisibilityProvider.shouldHideNotification(any()))
+ .thenReturn(true);
assertThat(mNotifInterruptionStateProvider.shouldBubbleUp(createBubble())).isFalse();
}
private NotificationEntry createBubble() {
+ return createBubble(null, null);
+ }
+
+ private NotificationEntry createBubble(String groupKey, Integer groupAlert) {
Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(
PendingIntent.getActivity(mContext, 0, new Intent(),
- PendingIntent.FLAG_MUTABLE),
- Icon.createWithResource(mContext.getResources(), R.drawable.android))
+ PendingIntent.FLAG_MUTABLE),
+ Icon.createWithResource(mContext.getResources(), R.drawable.android))
.build();
- Notification n = new Notification.Builder(getContext(), "a")
+ Notification.Builder nb = new Notification.Builder(getContext(), "a")
.setContentTitle("title")
.setContentText("content text")
- .setBubbleMetadata(data)
- .build();
+ .setBubbleMetadata(data);
+ if (groupKey != null) {
+ nb.setGroup(groupKey);
+ nb.setGroupSummary(false);
+ }
+ if (groupAlert != null) {
+ nb.setGroupAlertBehavior(groupAlert);
+ }
+ Notification n = nb.build();
n.flags |= FLAG_BUBBLE;
return new NotificationEntryBuilder()
@@ -526,6 +777,10 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
.setContentText("content text")
.build();
+ return createNotification(importance, n);
+ }
+
+ private NotificationEntry createNotification(int importance, Notification n) {
return new NotificationEntryBuilder()
.setPkg("a")
.setOpPkg("a")
@@ -536,45 +791,57 @@ public class NotificationInterruptStateProviderImplTest extends SysuiTestCase {
.build();
}
+ private NotificationEntry createFsiNotification(int importance, boolean silent) {
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setContentTitle("title")
+ .setContentText("content text")
+ .setFullScreenIntent(mPendingIntent, true)
+ .setGroup("fsi")
+ .setGroupAlertBehavior(silent ? GROUP_ALERT_SUMMARY : Notification.GROUP_ALERT_ALL)
+ .build();
+
+ return createNotification(importance, n);
+ }
+
private final NotificationInterruptSuppressor
mSuppressAwakeHeadsUp =
new NotificationInterruptSuppressor() {
- @Override
- public String getName() {
- return "suppressAwakeHeadsUp";
- }
+ @Override
+ public String getName() {
+ return "suppressAwakeHeadsUp";
+ }
- @Override
- public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
- return true;
- }
- };
+ @Override
+ public boolean suppressAwakeHeadsUp(NotificationEntry entry) {
+ return true;
+ }
+ };
private final NotificationInterruptSuppressor
mSuppressAwakeInterruptions =
new NotificationInterruptSuppressor() {
- @Override
- public String getName() {
- return "suppressAwakeInterruptions";
- }
+ @Override
+ public String getName() {
+ return "suppressAwakeInterruptions";
+ }
- @Override
- public boolean suppressAwakeInterruptions(NotificationEntry entry) {
- return true;
- }
- };
+ @Override
+ public boolean suppressAwakeInterruptions(NotificationEntry entry) {
+ return true;
+ }
+ };
private final NotificationInterruptSuppressor
mSuppressInterruptions =
new NotificationInterruptSuppressor() {
- @Override
- public String getName() {
- return "suppressInterruptions";
- }
-
- @Override
- public boolean suppressInterruptions(NotificationEntry entry) {
- return true;
- }
- };
+ @Override
+ public String getName() {
+ return "suppressInterruptions";
+ }
+
+ @Override
+ public boolean suppressInterruptions(NotificationEntry entry) {
+ return true;
+ }
+ };
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
deleted file mode 100644
index 429d2ed36cc7..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerLegacyTest.java
+++ /dev/null
@@ -1,299 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.logging;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.Notification;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.InstanceId;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationListener;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.collection.NotifLiveData;
-import com.android.systemui.statusbar.notification.collection.NotifLiveDataStore;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.logging.nano.Notifications;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import com.google.android.collect.Lists;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.Executor;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper
-public class NotificationLoggerLegacyTest extends SysuiTestCase {
- private static final String TEST_PACKAGE_NAME = "test";
- private static final int TEST_UID = 0;
-
- @Mock private NotificationListContainer mListContainer;
- @Mock private IStatusBarService mBarService;
- @Mock private ExpandableNotificationRow mRow;
- @Mock private NotificationLogger.ExpansionStateLogger mExpansionStateLogger;
-
- // Dependency mocks:
- @Mock private NotifPipelineFlags mNotifPipelineFlags;
- @Mock private NotifLiveDataStore mNotifLiveDataStore;
- @Mock private NotifLiveData<List<NotificationEntry>> mActiveNotifList;
- @Mock private NotificationVisibilityProvider mVisibilityProvider;
- @Mock private NotificationEntryManager mEntryManager;
- @Mock private NotifPipeline mNotifPipeline;
- @Mock private NotificationListener mListener;
-
- private NotificationEntry mEntry;
- private TestableNotificationLogger mLogger;
- private ConcurrentLinkedQueue<AssertionError> mErrorQueue = new ConcurrentLinkedQueue<>();
- private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
- private NotificationPanelLoggerFake mNotificationPanelLoggerFake =
- new NotificationPanelLoggerFake();
-
- @Before
- public void setUp() {
- MockitoAnnotations.initMocks(this);
- when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifList);
-
- mEntry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setNotification(new Notification())
- .setUser(UserHandle.CURRENT)
- .setInstanceId(InstanceId.fakeInstanceId(1))
- .build();
- mEntry.setRow(mRow);
-
- mLogger = new TestableNotificationLogger(
- mListener,
- mUiBgExecutor,
- mNotifPipelineFlags,
- mNotifLiveDataStore,
- mVisibilityProvider,
- mEntryManager,
- mNotifPipeline,
- mock(StatusBarStateControllerImpl.class),
- mBarService,
- mExpansionStateLogger
- );
- mLogger.setUpWithContainer(mListContainer);
- verify(mEntryManager).addNotificationEntryListener(any());
- verify(mNotifPipeline, never()).addCollectionListener(any());
- }
-
- @After
- public void tearDown() {
- mLogger.mHandler.removeCallbacksAndMessages(null);
- }
-
- @Test
- public void testOnChildLocationsChangedReportsVisibilityChanged() throws Exception {
- NotificationVisibility[] newlyVisibleKeys = {
- NotificationVisibility.obtain(mEntry.getKey(), 0, 1, true)
- };
- NotificationVisibility[] noLongerVisibleKeys = {};
- doAnswer(invocation -> {
- try {
- assertArrayEquals(newlyVisibleKeys,
- (NotificationVisibility[]) invocation.getArguments()[0]);
- assertArrayEquals(noLongerVisibleKeys,
- (NotificationVisibility[]) invocation.getArguments()[1]);
- } catch (AssertionError error) {
- mErrorQueue.offer(error);
- }
- return null;
- }
- ).when(mBarService).onNotificationVisibilityChanged(any(NotificationVisibility[].class),
- any(NotificationVisibility[].class));
-
- when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
- when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
- TestableLooper.get(this).processAllMessages();
- mUiBgExecutor.runAllReady();
-
- if (!mErrorQueue.isEmpty()) {
- throw mErrorQueue.poll();
- }
-
- // |mEntry| won't change visibility, so it shouldn't be reported again:
- Mockito.reset(mBarService);
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
- TestableLooper.get(this).processAllMessages();
- mUiBgExecutor.runAllReady();
-
- verify(mBarService, never()).onNotificationVisibilityChanged(any(), any());
- }
-
- @Test
- public void testStoppingNotificationLoggingReportsCurrentNotifications()
- throws Exception {
- when(mListContainer.isInVisibleLocation(any())).thenReturn(true);
- when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
- mLogger.getChildLocationsChangedListenerForTest().onChildLocationsChanged();
- TestableLooper.get(this).processAllMessages();
- mUiBgExecutor.runAllReady();
- Mockito.reset(mBarService);
-
- setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
- mLogger.onDozingChanged(true); // And go back to sleep, turning off logging
- mUiBgExecutor.runAllReady();
- // The visibility objects are recycled by NotificationLogger, so we can't use specific
- // matchers here.
- verify(mBarService, times(1)).onNotificationVisibilityChanged(any(), any());
- }
-
- private void setStateAsleep() {
- mLogger.onPanelExpandedChanged(true);
- mLogger.onDozingChanged(true);
- mLogger.onStateChanged(StatusBarState.KEYGUARD);
- }
-
- private void setStateAwake() {
- mLogger.onPanelExpandedChanged(false);
- mLogger.onDozingChanged(false);
- mLogger.onStateChanged(StatusBarState.SHADE);
- }
-
- @Test
- public void testLogPanelShownOnWake() {
- when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
- setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
- assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
- assertTrue(mNotificationPanelLoggerFake.get(0).isLockscreen);
- assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
- Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
- assertEquals(TEST_PACKAGE_NAME, n.packageName);
- assertEquals(TEST_UID, n.uid);
- assertEquals(1, n.instanceId);
- assertFalse(n.isGroupSummary);
- assertEquals(Notifications.Notification.SECTION_ALERTING, n.section);
- }
-
- @Test
- public void testLogPanelShownOnShadePull() {
- when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(mEntry));
- setStateAwake();
- // Now expand panel
- mLogger.onPanelExpandedChanged(true);
- assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
- assertFalse(mNotificationPanelLoggerFake.get(0).isLockscreen);
- assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
- Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
- assertEquals(TEST_PACKAGE_NAME, n.packageName);
- assertEquals(TEST_UID, n.uid);
- assertEquals(1, n.instanceId);
- assertFalse(n.isGroupSummary);
- assertEquals(Notifications.Notification.SECTION_ALERTING, n.section);
- }
-
-
- @Test
- public void testLogPanelShownHandlesNullInstanceIds() {
- // Construct a NotificationEntry like mEntry, but with a null instance id.
- NotificationEntry entry = new NotificationEntryBuilder()
- .setPkg(TEST_PACKAGE_NAME)
- .setOpPkg(TEST_PACKAGE_NAME)
- .setUid(TEST_UID)
- .setNotification(new Notification())
- .setUser(UserHandle.CURRENT)
- .build();
- entry.setRow(mRow);
-
- when(mActiveNotifList.getValue()).thenReturn(Lists.newArrayList(entry));
- setStateAsleep();
- mLogger.onDozingChanged(false); // Wake to lockscreen
- assertEquals(1, mNotificationPanelLoggerFake.getCalls().size());
- assertEquals(1, mNotificationPanelLoggerFake.get(0).list.notifications.length);
- Notifications.Notification n = mNotificationPanelLoggerFake.get(0).list.notifications[0];
- assertEquals(0, n.instanceId);
- }
-
- private class TestableNotificationLogger extends NotificationLogger {
-
- TestableNotificationLogger(NotificationListener notificationListener,
- Executor uiBgExecutor,
- NotifPipelineFlags notifPipelineFlags,
- NotifLiveDataStore notifLiveDataStore,
- NotificationVisibilityProvider visibilityProvider,
- NotificationEntryManager entryManager,
- NotifPipeline notifPipeline,
- StatusBarStateControllerImpl statusBarStateController,
- IStatusBarService barService,
- ExpansionStateLogger expansionStateLogger) {
- super(
- notificationListener,
- uiBgExecutor,
- notifPipelineFlags,
- notifLiveDataStore,
- visibilityProvider,
- entryManager,
- notifPipeline,
- statusBarStateController,
- expansionStateLogger,
- mNotificationPanelLoggerFake
- );
- mBarService = barService;
- mHandler.removeCallbacksAndMessages(null);
- // Make this on the current thread so we can wait for it during tests.
- mHandler = Handler.createAsync(Looper.myLooper());
- }
-
- OnChildLocationsChangedListener getChildLocationsChangedListenerForTest() {
- return mNotificationLocationsChangedListener;
- }
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
index b69bd8dfca9c..8a7b9d3b6024 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/logging/NotificationLoggerTest.java
@@ -103,7 +103,6 @@ public class NotificationLoggerTest extends SysuiTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
when(mNotifLiveDataStore.getActiveNotifList()).thenReturn(mActiveNotifEntries);
mEntry = new NotificationEntryBuilder()
@@ -278,10 +277,8 @@ public class NotificationLoggerTest extends SysuiTestCase {
super(
notificationListener,
uiBgExecutor,
- notifPipelineFlags,
notifLiveDataStore,
visibilityProvider,
- entryManager,
notifPipeline,
statusBarStateController,
expansionStateLogger,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index b1bf971c52fc..f8b39e8cff71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -43,6 +43,7 @@ import static org.mockito.Mockito.when;
import android.app.Notification;
import android.app.NotificationChannel;
+import android.graphics.Color;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper;
@@ -110,6 +111,28 @@ public class ExpandableNotificationRowTest extends SysuiTestCase {
}
@Test
+ public void testUpdateBackgroundColors_isRecursive() {
+ mGroupRow.setTintColor(Color.RED);
+ mGroupRow.getChildNotificationAt(0).setTintColor(Color.GREEN);
+ mGroupRow.getChildNotificationAt(1).setTintColor(Color.BLUE);
+
+ assertThat(mGroupRow.getCurrentBackgroundTint()).isEqualTo(Color.RED);
+ assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
+ .isEqualTo(Color.GREEN);
+ assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
+ .isEqualTo(Color.BLUE);
+
+ mGroupRow.updateBackgroundColors();
+
+ int resetTint = mGroupRow.getCurrentBackgroundTint();
+ assertThat(resetTint).isNotEqualTo(Color.RED);
+ assertThat(mGroupRow.getChildNotificationAt(0).getCurrentBackgroundTint())
+ .isEqualTo(resetTint);
+ assertThat(mGroupRow.getChildNotificationAt(1).getCurrentBackgroundTint())
+ .isEqualTo(resetTint);
+ }
+
+ @Test
public void testSetSensitiveOnNotifRowNotifiesOfHeightChange() throws InterruptedException {
// GIVEN a sensitive notification row that's currently redacted
measureAndLayout(mNotifRow);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
index 251ac7d250fe..bf7549a23707 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationEntryManagerInflationTest.java
@@ -194,7 +194,8 @@ public class NotificationEntryManagerInflationTest extends SysuiTestCase {
mLeakDetector,
mock(IStatusBarService.class),
NotifLiveDataStoreMocksKt.createNotifLiveDataStoreImplMock(),
- mock(DumpManager.class)
+ mock(DumpManager.class),
+ mBgExecutor
);
mEntryManager.initialize(
mNotificationListener,
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 94a93ad6cf33..c199147b4f79 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
@@ -47,10 +47,12 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.media.KeyguardMediaController;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.OnMenuEventListener;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener;
@@ -74,7 +76,6 @@ import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.ScrimController;
import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.phone.shade.transition.ShadeTransitionController;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
import com.android.systemui.statusbar.policy.ZenModeController;
@@ -113,6 +114,7 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
@Mock private SysuiColorExtractor mColorExtractor;
@Mock private NotificationLockscreenUserManager mNotificationLockscreenUserManager;
@Mock private MetricsLogger mMetricsLogger;
+ @Mock private DumpManager mDumpManager;
@Mock private Resources mResources;
@Mock(answer = Answers.RETURNS_SELF)
private NotificationSwipeHelper.Builder mNotificationSwipeHelperBuilder;
@@ -148,7 +150,6 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
MockitoAnnotations.initMocks(this);
when(mNotificationSwipeHelperBuilder.build()).thenReturn(mNotificationSwipeHelper);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
mController = new NotificationStackScrollLayoutController(
true,
@@ -164,9 +165,9 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mKeyguardMediaController,
mKeyguardBypassController,
mZenModeController,
- mColorExtractor,
mNotificationLockscreenUserManager,
mMetricsLogger,
+ mDumpManager,
new FalsingCollectorFake(),
new FalsingManagerFake(),
mResources,
@@ -176,15 +177,12 @@ public class NotificationStackScrollLayoutControllerTest extends SysuiTestCase {
mLegacyGroupManager,
mLegacyGroupManager,
mSilentHeaderController,
- mNotifPipelineFlags,
mNotifPipeline,
mNotifCollection,
mEntryManager,
mLockscreenShadeTransitionController,
mShadeTransitionController,
- mIStatusBarService,
mUiEventLogger,
- mLayoutInflater,
mRemoteInputManager,
mVisualStabilityManager,
mShadeController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
index 275dbfd516e4..8fd6842911de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithmTest.kt
@@ -15,6 +15,7 @@ import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
import com.google.common.truth.Truth.assertThat
import junit.framework.Assert.assertFalse
import junit.framework.Assert.assertTrue
+import junit.framework.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.mockito.Mockito.mock
@@ -164,4 +165,178 @@ class StackScrollAlgorithmTest : SysuiTestCase() {
stackScrollAlgorithm.updateViewWithShelf(expandableView, expandableViewState, shelfStart)
assertFalse(expandableViewState.hidden)
}
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_endVisible_true() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = false
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 0f,
+ /* maxHunY= */ 10f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_endHidden_false() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 0f)
+
+ assertFalse(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_shadeClosed_noUpdate() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ false,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_notHUN_noUpdate() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ false,
+ /* isViewEndVisible= */ true,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun maybeUpdateHeadsUpIsVisible_topHidden_noUpdate() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.headsUpIsVisible = true
+
+ stackScrollAlgorithm.maybeUpdateHeadsUpIsVisible(expandableViewState,
+ /* isShadeExpanded= */ true,
+ /* mustStayOnScreen= */ true,
+ /* isViewEndVisible= */ false,
+ /* viewEnd= */ 10f,
+ /* maxHunY= */ 1f)
+
+ assertTrue(expandableViewState.headsUpIsVisible)
+ }
+
+ @Test
+ fun clampHunToTop_viewYGreaterThanQqs_viewYUnchanged() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.yTranslation = 50f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f, expandableViewState)
+
+ // qqs (10 + 0) < viewY (50)
+ assertEquals(50f, expandableViewState.yTranslation)
+ }
+
+ @Test
+ fun clampHunToTop_viewYLessThanQqs_viewYChanged() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.yTranslation = -10f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 1f, expandableViewState)
+
+ // qqs (10 + 0) > viewY (-10)
+ assertEquals(10f, expandableViewState.yTranslation)
+ }
+
+
+ @Test
+ fun clampHunToTop_viewYFarAboveVisibleStack_heightCollapsed() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.height = 20
+ expandableViewState.yTranslation = -100f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f, expandableViewState)
+
+ // newTranslation = max(10, -100) = 10
+ // distToRealY = 10 - (-100f) = 110
+ // height = max(20 - 110, 10f)
+ assertEquals(10, expandableViewState.height)
+ }
+
+ @Test
+ fun clampHunToTop_viewYNearVisibleStack_heightTallerThanCollapsed() {
+ val expandableViewState = ExpandableViewState()
+ expandableViewState.height = 20
+ expandableViewState.yTranslation = 5f
+
+ stackScrollAlgorithm.clampHunToTop(/* quickQsOffsetHeight= */ 10f,
+ /* stackTranslation= */ 0f,
+ /* collapsedHeight= */ 10f, expandableViewState)
+
+ // newTranslation = max(10, 5) = 10
+ // distToRealY = 10 - 5 = 5
+ // height = max(20 - 5, 10) = 15
+ assertEquals(15, expandableViewState.height)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_stackBelowScreen_round() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 110f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 0f)
+ assertEquals(1f, currentRoundness)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_stackAboveScreenBelowPinPoint_halfRound() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 90f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 0f)
+ assertEquals(0.5f, currentRoundness)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_stackAbovePinPoint_notRound() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 0f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 0f)
+ assertEquals(0f, currentRoundness)
+ }
+
+ @Test
+ fun computeCornerRoundnessForPinnedHun_originallyRoundAndStackAbovePinPoint_round() {
+ val currentRoundness = stackScrollAlgorithm.computeCornerRoundnessForPinnedHun(
+ /* hostViewHeight= */ 100f,
+ /* stackY= */ 0f,
+ /* viewMaxHeight= */ 20f,
+ /* originalCornerRoundness= */ 1f)
+ assertEquals(1f, currentRoundness)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
index 371119cc6d01..4ccbc6d45e63 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
@@ -490,7 +490,7 @@ public class AutoTileManagerTest extends SysuiTestCase {
mAutoTileManager.init();
when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
- verify(mQsTileHost, times(1)).removeTile(safetyComponent);
+ verify(mQsTileHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index e5b6286fcd7c..272ef3ddc64e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -131,6 +131,7 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
when(mKeyguardBypassController.onBiometricAuthenticated(any(), anyBoolean()))
.thenReturn(true);
when(mAuthController.isUdfpsFingerDown()).thenReturn(false);
+ when(mVibratorHelper.hasVibrator()).thenReturn(true);
mDependency.injectTestDependency(NotificationMediaManager.class, mMediaManager);
mBiometricUnlockController = new BiometricUnlockController(mDozeScrimController,
mKeyguardViewMediator, mScrimController, mShadeController,
@@ -423,4 +424,35 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase {
verify(mHandler).post(captor.capture());
captor.getValue().run();
}
+
+ @Test
+ public void onFPFailureNoHaptics_notDeviceInteractive_showBouncer() {
+ // GIVEN no vibrator and the screen is off
+ when(mVibratorHelper.hasVibrator()).thenReturn(false);
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
+ when(mUpdateMonitor.isDreaming()).thenReturn(false);
+
+ // WHEN FP fails
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+ // after device is finished waking up
+ mBiometricUnlockController.mWakefulnessObserver.onFinishedWakingUp();
+
+ // THEN show the bouncer
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
+
+ @Test
+ public void onFPFailureNoHaptics_dreaming_showBouncer() {
+ // GIVEN no vibrator and device is dreaming
+ when(mVibratorHelper.hasVibrator()).thenReturn(false);
+ when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
+ when(mUpdateMonitor.isDreaming()).thenReturn(true);
+
+ // WHEN FP fails
+ mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
+
+ // THEN show the bouncer
+ verify(mStatusBarKeyguardViewManager).showBouncer(true);
+ }
}
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 9bfb2c4ce00c..7b7a48be5b17 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
@@ -22,24 +22,28 @@ import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import android.app.StatusBarManager;
import android.os.PowerManager;
import android.os.Vibrator;
import android.testing.AndroidTestingRunner;
+import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.testing.FakeMetricsLogger;
+import com.android.internal.statusbar.LetterboxDetails;
+import com.android.internal.view.AppearanceRegion;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
-import com.android.systemui.statusbar.StatusBarStateControllerImpl;
import com.android.systemui.statusbar.VibratorHelper;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
import com.android.systemui.statusbar.policy.DeviceProvisionedController;
@@ -58,6 +62,7 @@ import java.util.Optional;
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
+
@Mock private CentralSurfaces mCentralSurfaces;
@Mock private ShadeController mShadeController;
@Mock private CommandQueue mCommandQueue;
@@ -72,14 +77,12 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
@Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
@Mock private AssistManager mAssistManager;
@Mock private DozeServiceHost mDozeServiceHost;
- @Mock private StatusBarStateControllerImpl mStatusBarStateController;
- @Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
@Mock private PowerManager mPowerManager;
@Mock private VibratorHelper mVibratorHelper;
@Mock private Vibrator mVibrator;
- @Mock private LightBarController mLightBarController;
@Mock private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+ @Mock private SystemBarAttributesListener mSystemBarAttributesListener;
CentralSurfacesCommandQueueCallbacks mSbcqCallbacks;
@@ -104,16 +107,14 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
mStatusBarKeyguardViewManager,
mAssistManager,
mDozeServiceHost,
- mStatusBarStateController,
- mNotificationShadeWindowView,
mNotificationStackScrollLayoutController,
mStatusBarHideIconsForBouncerManager,
mPowerManager,
mVibratorHelper,
Optional.of(mVibrator),
- mLightBarController,
new DisableFlagsLogger(),
- DEFAULT_DISPLAY);
+ DEFAULT_DISPLAY,
+ mSystemBarAttributesListener);
when(mDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
when(mRemoteInputQuickSettingsDisabler.adjustDisableFlags(anyInt()))
@@ -168,5 +169,59 @@ public class CentralSurfacesCommandQueueCallbacksTest extends SysuiTestCase {
verify(mDozeServiceHost).setAlwaysOnSuppressed(false);
}
+ @Test
+ public void onSystemBarAttributesChanged_forwardsToSysBarAttrsListener() {
+ int displayId = DEFAULT_DISPLAY;
+ int appearance = 123;
+ AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
+ boolean navbarColorManagedByIme = true;
+ int behavior = 456;
+ InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ String packageName = "test package name";
+ LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
+
+ mSbcqCallbacks.onSystemBarAttributesChanged(
+ displayId,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails);
+
+ verify(mSystemBarAttributesListener).onSystemBarAttributesChanged(
+ displayId,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails
+ );
+ }
+ @Test
+ public void onSystemBarAttributesChanged_differentDisplayId_doesNotForwardToAttrsListener() {
+ int appearance = 123;
+ AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
+ boolean navbarColorManagedByIme = true;
+ int behavior = 456;
+ InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+ String packageName = "test package name";
+ LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
+
+ mSbcqCallbacks.onSystemBarAttributesChanged(
+ DEFAULT_DISPLAY + 1,
+ appearance,
+ appearanceRegions,
+ navbarColorManagedByIme,
+ behavior,
+ requestedVisibilities,
+ packageName,
+ letterboxDetails);
+
+ verifyZeroInteractions(mSystemBarAttributesListener);
+ }
}
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 2faff0ced70a..c6fb0ce34e85 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
@@ -19,6 +19,8 @@ package com.android.systemui.statusbar.phone;
import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
+import static com.google.common.truth.Truth.assertThat;
+
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static junit.framework.TestCase.fail;
@@ -44,6 +46,7 @@ import android.app.WallpaperManager;
import android.app.trust.TrustManager;
import android.content.BroadcastReceiver;
import android.content.ContentResolver;
+import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.display.AmbientDisplayConfiguration;
@@ -84,11 +87,11 @@ import com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenu
import com.android.systemui.animation.ActivityLaunchAnimator;
import com.android.systemui.assist.AssistManager;
import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.charging.WiredChargingRippleController;
import com.android.systemui.classifier.FalsingCollectorFake;
import com.android.systemui.classifier.FalsingManagerFake;
import com.android.systemui.colorextraction.SysuiColorExtractor;
import com.android.systemui.demomode.DemoModeController;
-import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.fragments.FragmentService;
@@ -102,6 +105,10 @@ import com.android.systemui.plugins.PluginDependencyProvider;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.recents.ScreenPinningRequest;
import com.android.systemui.settings.brightness.BrightnessSliderController;
+import com.android.systemui.shade.NotificationPanelView;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.shared.plugins.PluginManager;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
@@ -117,8 +124,6 @@ import com.android.systemui.statusbar.OperatorNameViewController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
import com.android.systemui.statusbar.StatusBarStateControllerImpl;
-import com.android.systemui.statusbar.charging.WiredChargingRippleController;
-import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
@@ -129,7 +134,6 @@ import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
-import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.init.NotificationsController;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
@@ -159,7 +163,6 @@ import com.android.systemui.util.concurrency.FakeExecutor;
import com.android.systemui.util.concurrency.MessageRouterImpl;
import com.android.systemui.util.time.FakeSystemClock;
import com.android.systemui.volume.VolumeComponent;
-import com.android.systemui.wmshell.BubblesManager;
import com.android.wm.shell.bubbles.Bubbles;
import com.android.wm.shell.startingsurface.StartingSurface;
@@ -223,7 +226,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotificationShadeWindowView mNotificationShadeWindowView;
@Mock private BroadcastDispatcher mBroadcastDispatcher;
@Mock private AssistManager mAssistManager;
- @Mock private NotifShadeEventSource mNotifShadeEventSource;
@Mock private NotificationEntryManager mNotificationEntryManager;
@Mock private NotificationGutsManager mNotificationGutsManager;
@Mock private NotificationMediaManager mNotificationMediaManager;
@@ -240,15 +242,12 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private StatusBarWindowStateController mStatusBarWindowStateController;
@Mock private NotificationViewHierarchyManager mNotificationViewHierarchyManager;
@Mock private UserSwitcherController mUserSwitcherController;
- @Mock private NetworkController mNetworkController;
- @Mock private BubblesManager mBubblesManager;
@Mock private Bubbles mBubbles;
@Mock private NotificationShadeWindowController mNotificationShadeWindowController;
@Mock private NotificationIconAreaController mNotificationIconAreaController;
@Mock private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
@Mock private DozeParameters mDozeParameters;
@Mock private Lazy<LockscreenWallpaper> mLockscreenWallpaperLazy;
- @Mock private LockscreenGestureLogger mLockscreenGestureLogger;
@Mock private LockscreenWallpaper mLockscreenWallpaper;
@Mock private DozeServiceHost mDozeServiceHost;
@Mock private ViewMediatorCallback mKeyguardVieMediatorCallback;
@@ -286,7 +285,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
@Mock private NotifLiveDataStore mNotifLiveDataStore;
@Mock private InteractionJankMonitor mJankMonitor;
@Mock private DeviceStateManager mDeviceStateManager;
- @Mock private DreamOverlayStateController mDreamOverlayStateController;
@Mock private WiredChargingRippleController mWiredChargingRippleController;
private ShadeController mShadeController;
private final FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -308,8 +306,13 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mNotificationInterruptStateProvider =
new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
mPowerManager,
- mDreamManager, mAmbientDisplayConfiguration, mNotificationFilter,
- mStatusBarStateController, mBatteryController, mHeadsUpManager,
+ mDreamManager,
+ mAmbientDisplayConfiguration,
+ mNotificationFilter,
+ mStatusBarStateController,
+ mKeyguardStateController,
+ mBatteryController,
+ mHeadsUpManager,
mock(NotificationInterruptLogger.class),
new Handler(TestableLooper.get(this).getLooper()),
mock(NotifPipelineFlags.class),
@@ -322,10 +325,8 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
NotificationLogger notificationLogger = new NotificationLogger(
mNotificationListener,
mUiBgExecutor,
- mNotifPipelineFlags,
mNotifLiveDataStore,
mVisibilityProvider,
- mock(NotificationEntryManager.class),
mock(NotifPipeline.class),
mStatusBarStateController,
mExpansionStateLogger,
@@ -403,7 +404,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
new FalsingManagerFake(),
new FalsingCollectorFake(),
mBroadcastDispatcher,
- mNotifShadeEventSource,
mNotificationEntryManager,
mNotificationGutsManager,
notificationLogger,
@@ -418,13 +418,11 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mLockscreenUserManager,
mRemoteInputManager,
mUserSwitcherController,
- mNetworkController,
mBatteryController,
mColorExtractor,
new ScreenLifecycle(mDumpManager),
wakefulnessLifecycle,
mStatusBarStateController,
- Optional.of(mBubblesManager),
Optional.of(mBubbles),
mVisualStabilityManager,
mDeviceProvisionedController,
@@ -436,7 +434,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mDozeParameters,
mScrimController,
mLockscreenWallpaperLazy,
- mLockscreenGestureLogger,
mBiometricUnlockControllerLazy,
mDozeServiceHost,
mPowerManager, mScreenPinningRequest,
@@ -468,7 +465,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mLockscreenTransitionController,
mFeatureFlags,
mKeyguardUnlockAnimationController,
- new Handler(TestableLooper.get(this).getLooper()),
mMainExecutor,
new MessageRouterImpl(mMainExecutor),
mWallpaperManager,
@@ -477,7 +473,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mNotifPipelineFlags,
mJankMonitor,
mDeviceStateManager,
- mDreamOverlayStateController,
mWiredChargingRippleController, mDreamManager);
when(mKeyguardViewMediator.registerCentralSurfaces(
any(CentralSurfacesImpl.class),
@@ -848,7 +843,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(true);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.UNLOCKED), any());
@@ -885,7 +879,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
mCentralSurfaces.showKeyguardImpl();
// Starting a pulse should change the scrim controller to the pulsing state
- when(mNotificationPanelViewController.isLaunchTransitionRunning()).thenReturn(true);
when(mNotificationPanelViewController.isLaunchingAffordanceWithPreview()).thenReturn(false);
mCentralSurfaces.updateScrimController();
verify(mScrimController).transitionTo(eq(ScrimState.KEYGUARD));
@@ -1003,6 +996,22 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
verify(mStatusBarStateController, never()).setLeaveOpenOnKeyguardHide(true);
}
+ @Test
+ public void startActivityDismissingKeyguard_isShowingandIsOccluded() {
+ when(mStatusBarKeyguardViewManager.isShowing()).thenReturn(true);
+ when(mStatusBarKeyguardViewManager.isOccluded()).thenReturn(true);
+ mCentralSurfaces.startActivityDismissingKeyguard(
+ new Intent(),
+ /* onlyProvisioned = */false,
+ /* dismissShade = */false);
+ verify(mStatusBarKeyguardViewManager).addAfterKeyguardGoneRunnable(any(Runnable.class));
+ ArgumentCaptor<OnDismissAction> onDismissActionCaptor =
+ ArgumentCaptor.forClass(OnDismissAction.class);
+ verify(mStatusBarKeyguardViewManager)
+ .dismissWithAction(onDismissActionCaptor.capture(), any(Runnable.class), eq(true));
+ assertThat(onDismissActionCaptor.getValue().onDismiss()).isFalse();
+ }
+
private void setDeviceState(int state) {
ArgumentCaptor<DeviceStateManager.DeviceStateCallback> callbackCaptor =
ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class);
@@ -1032,15 +1041,28 @@ public class CentralSurfacesImplTest extends SysuiTestCase {
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter filter,
StatusBarStateController controller,
+ KeyguardStateController keyguardStateController,
BatteryController batteryController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
Handler mainHandler,
NotifPipelineFlags flags,
KeyguardNotificationVisibilityProvider keyguardNotificationVisibilityProvider) {
- super(contentResolver, powerManager, dreamManager, ambientDisplayConfiguration, filter,
- batteryController, controller, headsUpManager, logger, mainHandler,
- flags, keyguardNotificationVisibilityProvider);
+ super(
+ contentResolver,
+ powerManager,
+ dreamManager,
+ ambientDisplayConfiguration,
+ filter,
+ batteryController,
+ controller,
+ keyguardStateController,
+ headsUpManager,
+ logger,
+ mainHandler,
+ flags,
+ keyguardNotificationVisibilityProvider
+ );
mUseHeadsUp = true;
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 26ac70c70e7f..5c9871a01536 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -41,6 +41,8 @@ import com.android.systemui.doze.DozeHost;
import com.android.systemui.doze.DozeLog;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.keyguard.WakefulnessLifecycle;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.PulseExpansionHandler;
import com.android.systemui.statusbar.StatusBarState;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index ed22cd3c55fc..103b7b4268de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -34,6 +34,7 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.HeadsUpStatusBarView;
import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
index 31465f45af42..3440fa5ac9b1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBottomAreaTest.kt
@@ -12,12 +12,12 @@ import com.android.systemui.statusbar.policy.AccessibilityController
import com.android.systemui.statusbar.policy.FlashlightController
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.tuner.TunerService
+import java.util.concurrent.Executor
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
-import java.util.concurrent.Executor
@SmallTest
@RunWith(AndroidTestingRunner::class)
@@ -42,7 +42,6 @@ class KeyguardBottomAreaTest : SysuiTestCase() {
mKeyguardBottomArea = LayoutInflater.from(mContext).inflate(
R.layout.keyguard_bottom_area, null, false) as KeyguardBottomAreaView
- mKeyguardBottomArea.setCentralSurfaces(mCentralSurfaces)
}
@Test
@@ -51,6 +50,5 @@ class KeyguardBottomAreaTest : SysuiTestCase() {
null, false) as KeyguardBottomAreaView
other.initFrom(mKeyguardBottomArea)
- other.launchVoiceAssist()
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
index 39021d8732d3..60a3d95e24f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java
@@ -230,6 +230,15 @@ public class KeyguardBouncerTest extends SysuiTestCase {
}
@Test
+ public void show_notifiesKeyguardViewController() {
+ mBouncer.ensureView();
+
+ mBouncer.show(/* resetSecuritySelection= */ false);
+
+ verify(mKeyguardHostViewController).onBouncerVisibilityChanged(View.VISIBLE);
+ }
+
+ @Test
public void testHide_notifiesFalsingManager() {
mBouncer.hide(false);
verify(mFalsingCollector).onBouncerHidden();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
index ec20271ea43b..ed3f7100ecc5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java
@@ -266,7 +266,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
@Test
public void clockPositionedDependingOnMarginInSplitShade() {
setSplitShadeTopMargin(400);
- mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
@@ -294,7 +293,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void notifPaddingAccountsForMultiUserSwitcherInSplitShade() {
setSplitShadeTopMargin(100);
mUserSwitchHeight = 150;
- mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
@@ -307,7 +305,6 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
public void clockDoesntAccountForMultiUserSwitcherInSplitShade() {
setSplitShadeTopMargin(100);
mUserSwitchHeight = 150;
- mClockPositionAlgorithm.loadDimens(mResources);
givenLockScreen();
mIsSplitShade = true;
// WHEN the position algorithm is run
@@ -382,11 +379,25 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
mQsExpansion = 1;
// WHEN the clock position algorithm is run
positionClock();
- // THEN the clock Y position is the middle of the screen (SCREEN_HEIGHT / 2).
+ // THEN the clock is transparent.
assertThat(mClockPosition.clockAlpha).isEqualTo(TRANSPARENT);
}
@Test
+ public void clockNotHiddenWhenQsIsExpandedInSplitShade() {
+ // GIVEN on the split lock screen with QS expansion
+ givenLockScreen();
+ mIsSplitShade = true;
+ setSplitShadeTopMargin(100);
+ mQsExpansion = 1;
+
+ // WHEN the clock position algorithm is run
+ positionClock();
+
+ assertThat(mClockPosition.clockAlpha).isEqualTo(1);
+ }
+
+ @Test
public void clockPositionMinimizesBurnInMovementToAvoidUdfpsOnAOD() {
// GIVEN a center aligned clock
mClockTopAligned = false;
@@ -524,6 +535,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase {
private void setSplitShadeTopMargin(int value) {
when(mResources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin))
.thenReturn(value);
+ mClockPositionAlgorithm.loadDimens(mResources);
}
private void givenHighestBurnInOffset() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
index 4e1a7088b17f..11e502fc79bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardStatusBarViewControllerTest.java
@@ -49,6 +49,7 @@ import com.android.systemui.SysuiTestCase;
import com.android.systemui.battery.BatteryMeterViewController;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserInfoTracker;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
new file mode 100644
index 000000000000..65401356b3e3
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LetterboxAppearanceCalculatorTest.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent
+import com.google.common.truth.Expect
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.`when` as whenever
+import com.android.systemui.dump.DumpManager
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class LetterboxAppearanceCalculatorTest : SysuiTestCase() {
+
+ companion object {
+ private const val DEFAULT_APPEARANCE = 0
+ private const val TEST_APPEARANCE = WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+ private val TEST_APPEARANCE_REGION_BOUNDS = Rect(0, 0, 20, 100)
+ private val TEST_APPEARANCE_REGION =
+ AppearanceRegion(TEST_APPEARANCE, TEST_APPEARANCE_REGION_BOUNDS)
+ private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
+ private val TEST_WINDOW_BOUNDS = Rect(0, 0, 500, 500)
+ }
+
+ @get:Rule var expect = Expect.create()
+
+ @Mock private lateinit var lightBarController: LightBarController
+ @Mock private lateinit var statusBarBoundsProvider: StatusBarBoundsProvider
+ @Mock private lateinit var statusBarFragmentComponent: StatusBarFragmentComponent
+ @Mock private lateinit var dumpManager: DumpManager
+
+ private lateinit var calculator: LetterboxAppearanceCalculator
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ whenever(statusBarFragmentComponent.boundsProvider).thenReturn(statusBarBoundsProvider)
+ calculator = LetterboxAppearanceCalculator(lightBarController, dumpManager)
+ calculator.onStatusBarViewInitialized(statusBarFragmentComponent)
+ }
+
+ @Test
+ fun getLetterboxAppearance_overlapStartSide_returnsOriginalWithScrim() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(50, 50, 150, 150))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ expect
+ .that(letterboxAppearance.appearance)
+ .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
+ expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
+ }
+
+ @Test
+ fun getLetterboxAppearance_overlapEndSide_returnsOriginalWithScrim() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(150, 50, 250, 150))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ expect
+ .that(letterboxAppearance.appearance)
+ .isEqualTo(TEST_APPEARANCE or APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS)
+ expect.that(letterboxAppearance.appearanceRegions).isEqualTo(TEST_APPEARANCE_REGIONS)
+ }
+
+ @Test
+ fun getLetterboxAppearance_noOverlap_returnsAppearanceWithoutScrim() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(101, 0, 199, 100))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
+ }
+
+ @Test
+ fun getLetterboxAppearance_letterboxContainsStartSide_returnsAppearanceWithoutScrim() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(0, 0, 101, 101))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
+ }
+
+ @Test
+ fun getLetterboxAppearance_letterboxContainsEndSide_returnsAppearanceWithoutScrim() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(199, 0, 301, 101))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
+ }
+
+ @Test
+ fun getLetterboxAppearance_letterboxContainsEntireStatusBar_returnsAppearanceWithoutScrim() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 100, 100))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(200, 0, 300, 100))
+ val letterbox = letterboxWithInnerBounds(Rect(0, 0, 300, 100))
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf(letterbox))
+
+ assertThat(letterboxAppearance.appearance).isEqualTo(TEST_APPEARANCE)
+ }
+
+ @Test
+ fun getLetterboxAppearance_returnsAdaptedAppearanceRegions_basedOnLetterboxInnerBounds() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
+ val letterbox = letterboxWithInnerBounds(Rect(150, 0, 300, 800))
+ val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds)
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
+
+ val letterboxAdaptedRegion = letterboxRegion.copy(bounds = letterbox.letterboxInnerBounds)
+ assertThat(letterboxAppearance.appearanceRegions.toList()).contains(letterboxAdaptedRegion)
+ assertThat(letterboxAppearance.appearanceRegions.toList()).doesNotContain(letterboxRegion)
+ }
+
+ @Test
+ fun getLetterboxAppearance_returnsDefaultAppearanceRegions_basedOnLetterboxOuterBounds() {
+ whenever(statusBarBoundsProvider.visibleStartSideBounds).thenReturn(Rect(0, 0, 0, 0))
+ whenever(statusBarBoundsProvider.visibleEndSideBounds).thenReturn(Rect(0, 0, 0, 0))
+ val letterbox =
+ letterboxWithBounds(
+ innerBounds = Rect(left = 25, top = 0, right = 75, bottom = 100),
+ fullBounds = Rect(left = 0, top = 0, right = 100, bottom = 100))
+ val letterboxRegion = TEST_APPEARANCE_REGION.copy(bounds = letterbox.letterboxFullBounds)
+
+ val letterboxAppearance =
+ calculator.getLetterboxAppearance(
+ TEST_APPEARANCE, arrayOf(letterboxRegion), arrayOf(letterbox))
+
+ val outerRegions =
+ listOf(
+ AppearanceRegion(
+ DEFAULT_APPEARANCE, Rect(left = 0, top = 0, right = 25, bottom = 100)),
+ AppearanceRegion(
+ DEFAULT_APPEARANCE, Rect(left = 75, top = 0, right = 100, bottom = 100)),
+ )
+ assertThat(letterboxAppearance.appearanceRegions.toList())
+ .containsAtLeastElementsIn(outerRegions)
+ }
+
+ private fun letterboxWithBounds(innerBounds: Rect, fullBounds: Rect) =
+ LetterboxDetails(innerBounds, fullBounds, TEST_APPEARANCE)
+
+ private fun letterboxWithInnerBounds(innerBounds: Rect) =
+ letterboxWithBounds(innerBounds, fullBounds = TEST_WINDOW_BOUNDS)
+}
+
+private fun AppearanceRegion.copy(appearance: Int = this.appearance, bounds: Rect = this.bounds) =
+ AppearanceRegion(appearance, bounds)
+
+private fun Rect(left: Int, top: Int, right: Int, bottom: Int) = Rect(left, top, right, bottom)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index 9664035e1e1b..fca9771648ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -108,7 +108,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
null /* requestedVisibilities */,
- null /* packageName */);
+ null /* packageName */,
+ null /* letterboxDetails */);
assertTrue(mLightsOutNotifController.areLightsOut());
}
@@ -121,7 +122,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
null /* requestedVisibilities */,
- null /* packageName */);
+ null /* packageName */,
+ null /* letterboxDetails */);
assertFalse(mLightsOutNotifController.areLightsOut());
}
@@ -152,7 +154,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
null /* requestedVisibilities */,
- null /* packageName */);
+ null /* packageName */,
+ null /* letterboxDetails */);
// THEN we should show dot
assertTrue(mLightsOutNotifController.shouldShowDot());
@@ -172,7 +175,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
null /* requestedVisibilities */,
- null /* packageName */);
+ null /* packageName */,
+ null /* letterboxDetails */);
// THEN we shouldn't show the dot
assertFalse(mLightsOutNotifController.shouldShowDot());
@@ -192,7 +196,8 @@ public class LightsOutNotifControllerTest extends SysuiTestCase {
false /* navbarColorManagedByIme */,
BEHAVIOR_DEFAULT,
null /* requestedVisibilities */,
- null /* packageName */);
+ null /* packageName */,
+ null /* letterboxDetails */);
// THEN we shouldn't show the dot
assertFalse(mLightsOutNotifController.shouldShowDot());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt
deleted file mode 100644
index 7e245fcea22d..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowViewControllerTest.kt
+++ /dev/null
@@ -1,281 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone
-
-import android.testing.AndroidTestingRunner
-import android.testing.TestableLooper.RunWithLooper
-import android.view.MotionEvent
-import androidx.test.filters.SmallTest
-import com.android.keyguard.LockIconViewController
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.classifier.FalsingCollectorFake
-import com.android.systemui.dock.DockManager
-import com.android.systemui.keyguard.KeyguardUnlockAnimationController
-import com.android.systemui.lowlightclock.LowLightClockController
-import com.android.systemui.statusbar.LockscreenShadeTransitionController
-import com.android.systemui.statusbar.NotificationShadeDepthController
-import com.android.systemui.statusbar.NotificationShadeWindowController
-import com.android.systemui.statusbar.SysuiStatusBarStateController
-import com.android.systemui.statusbar.notification.stack.AmbientState
-import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView.InteractionEventHandler
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager
-import com.android.systemui.statusbar.window.StatusBarWindowStateController
-import com.android.systemui.tuner.TunerService
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.ArgumentMatchers
-import org.mockito.Mock
-import org.mockito.MockitoAnnotations
-import org.mockito.Mockito.anyFloat
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import java.util.Optional
-import org.mockito.Mockito.`when` as whenever
-
-@RunWith(AndroidTestingRunner::class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class NotificationShadeWindowViewControllerTest : SysuiTestCase() {
- private lateinit var mController: NotificationShadeWindowViewController
-
- @Mock
- private lateinit var mView: NotificationShadeWindowView
- @Mock
- private lateinit var mTunerService: TunerService
- @Mock
- private lateinit var mStatusBarStateController: SysuiStatusBarStateController
- @Mock
- private lateinit var mCentralSurfaces: CentralSurfaces
- @Mock
- private lateinit var mDockManager: DockManager
- @Mock
- private lateinit var mNotificationPanelViewController: NotificationPanelViewController
- @Mock
- private lateinit var mNotificationShadeDepthController: NotificationShadeDepthController
- @Mock
- private lateinit var mNotificationShadeWindowController: NotificationShadeWindowController
- @Mock
- private lateinit var mKeyguardUnlockAnimationController: KeyguardUnlockAnimationController
- @Mock
- private lateinit var mAmbientState: AmbientState
- @Mock
- private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController
- @Mock
- private lateinit var mStatusBarKeyguardViewManager: StatusBarKeyguardViewManager
- @Mock
- private lateinit var mStatusBarWindowStateController: StatusBarWindowStateController
- @Mock
- private lateinit var mLockscreenShadeTransitionController: LockscreenShadeTransitionController
- @Mock
- private lateinit var mLockIconViewController: LockIconViewController
- @Mock
- private lateinit var mPhoneStatusBarViewController: PhoneStatusBarViewController
- @Mock
- private lateinit var mLowLightClockController: LowLightClockController
-
- private lateinit var mInteractionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler>
- private lateinit var mInteractionEventHandler: InteractionEventHandler
-
- @Before
- fun setUp() {
- MockitoAnnotations.initMocks(this)
- whenever(mView.bottom).thenReturn(VIEW_BOTTOM)
-
- mController = NotificationShadeWindowViewController(
- mLockscreenShadeTransitionController,
- FalsingCollectorFake(),
- mTunerService,
- mStatusBarStateController,
- mDockManager,
- mNotificationShadeDepthController,
- mView,
- mNotificationPanelViewController,
- PanelExpansionStateManager(),
- stackScrollLayoutController,
- mStatusBarKeyguardViewManager,
- mStatusBarWindowStateController,
- mLockIconViewController,
- Optional.of(mLowLightClockController),
- mCentralSurfaces,
- mNotificationShadeWindowController,
- mKeyguardUnlockAnimationController,
- mAmbientState
- )
- mController.setupExpandedStatusBar()
-
- mInteractionEventHandlerCaptor =
- ArgumentCaptor.forClass(InteractionEventHandler::class.java)
- verify(mView).setInteractionEventHandler(mInteractionEventHandlerCaptor.capture())
- mInteractionEventHandler = mInteractionEventHandlerCaptor.value
- }
-
- // Note: So far, these tests only cover interactions with the status bar view controller. More
- // tests need to be added to test the rest of handleDispatchTouchEvent.
-
- @Test
- fun handleDispatchTouchEvent_nullStatusBarViewController_returnsFalse() {
- mController.setStatusBarViewController(null)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
-
- assertThat(returnVal).isFalse()
- }
-
- @Test
- fun handleDispatchTouchEvent_downTouchBelowView_sendsTouchToSb() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- val ev = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0)
- whenever(mPhoneStatusBarViewController.sendTouchToView(ev)).thenReturn(true)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(ev)
-
- verify(mPhoneStatusBarViewController).sendTouchToView(ev)
- assertThat(returnVal).isTrue()
- }
-
- @Test
- fun handleDispatchTouchEvent_downTouchBelowViewThenAnotherTouch_sendsTouchToSb() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- val downEvBelow = MotionEvent.obtain(
- 0L, 0L, MotionEvent.ACTION_DOWN, 0f, VIEW_BOTTOM + 4f, 0
- )
- mInteractionEventHandler.handleDispatchTouchEvent(downEvBelow)
-
- val nextEvent = MotionEvent.obtain(
- 0L, 0L, MotionEvent.ACTION_MOVE, 0f, VIEW_BOTTOM + 5f, 0
- )
- whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent)
-
- verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent)
- assertThat(returnVal).isTrue()
- }
-
- @Test
- fun handleDispatchTouchEvent_downAndPanelCollapsedAndInSbBoundAndSbWindowShow_sendsTouchToSb() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
- whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
- whenever(mPhoneStatusBarViewController.sendTouchToView(downEv)).thenReturn(true)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
-
- verify(mPhoneStatusBarViewController).sendTouchToView(downEv)
- assertThat(returnVal).isTrue()
- }
-
- @Test
- fun handleDispatchTouchEvent_panelNotCollapsed_returnsNull() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
- // Item we're testing
- whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(false)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
-
- verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
- assertThat(returnVal).isNull()
- }
-
- @Test
- fun handleDispatchTouchEvent_touchNotInSbBounds_returnsNull() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
- // Item we're testing
- whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(false)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
-
- verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
- assertThat(returnVal).isNull()
- }
-
- @Test
- fun handleDispatchTouchEvent_sbWindowNotShowing_noSendTouchToSbAndReturnsTrue() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
- whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
- // Item we're testing
- whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(false)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(downEv)
-
- verify(mPhoneStatusBarViewController, never()).sendTouchToView(downEv)
- assertThat(returnVal).isTrue()
- }
-
- @Test
- fun handleDispatchTouchEvent_downEventSentToSbThenAnotherEvent_sendsTouchToSb() {
- mController.setStatusBarViewController(mPhoneStatusBarViewController)
- whenever(mStatusBarWindowStateController.windowIsShowing()).thenReturn(true)
- whenever(mNotificationPanelViewController.isFullyCollapsed).thenReturn(true)
- whenever(mPhoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat()))
- .thenReturn(true)
-
- // Down event first
- mInteractionEventHandler.handleDispatchTouchEvent(downEv)
-
- // Then another event
- val nextEvent = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0)
- whenever(mPhoneStatusBarViewController.sendTouchToView(nextEvent)).thenReturn(true)
-
- val returnVal = mInteractionEventHandler.handleDispatchTouchEvent(nextEvent)
-
- verify(mPhoneStatusBarViewController).sendTouchToView(nextEvent)
- assertThat(returnVal).isTrue()
- }
-
- @Test
- fun testLowLightClockAttachedWhenExpandedStatusBarSetup() {
- verify(mLowLightClockController).attachLowLightClockView(ArgumentMatchers.any())
- }
-
- @Test
- fun testLowLightClockShownWhenDozing() {
- mController.setDozing(true)
- verify(mLowLightClockController).showLowLightClock(true)
- }
-
- @Test
- fun testLowLightClockDozeTimeTickCalled() {
- mController.dozeTimeTick()
- verify(mLowLightClockController).dozeTimeTick()
- }
-
- @Test
- fun testLowLightClockHiddenWhenNotDozing() {
- mController.setDozing(true)
- verify(mLowLightClockController).showLowLightClock(true)
- mController.setDozing(false)
- verify(mLowLightClockController).showLowLightClock(false)
- }
-}
-
-private val downEv = MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)
-private const val VIEW_BOTTOM = 100
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 9ab88dc2d764..9892448aed03 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -26,6 +26,7 @@ import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.PanelViewController
import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherController
import com.android.systemui.statusbar.policy.ConfigurationController
import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -136,6 +137,7 @@ class PhoneStatusBarViewControllerTest : SysuiTestCase() {
private class UnfoldConfig : UnfoldTransitionConfig {
override var isEnabled: Boolean = false
override var isHingeAngleEnabled: Boolean = false
+ override val halfFoldedTimeoutMillis: Int = 0
}
private class TestTouchEventHandler : PhoneStatusBarView.TouchEventHandler {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index 8d686ae94e79..d6c995bef229 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -20,6 +20,7 @@ import android.view.MotionEvent
import android.view.ViewGroup
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.shade.PanelViewController
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 32df2d790bd1..7cd275db1ed6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -59,7 +59,6 @@ import com.android.systemui.animation.ShadeInterpolation;
import com.android.systemui.dock.DockManager;
import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
import com.android.systemui.scrim.ScrimView;
-import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.util.concurrency.FakeExecutor;
@@ -122,8 +121,6 @@ public class ScrimControllerTest extends SysuiTestCase {
// TODO(b/204991468): Use a real PanelExpansionStateManager object once this bug is fixed. (The
// event-dispatch-on-registration pattern caused some of these unit tests to fail.)
@Mock
- private PanelExpansionStateManager mPanelExpansionStateManager;
- @Mock
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
private static class AnimatorListener implements Animator.AnimatorListener {
@@ -237,7 +234,6 @@ public class ScrimControllerTest extends SysuiTestCase {
new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
- mPanelExpansionStateManager,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
@@ -860,7 +856,6 @@ public class ScrimControllerTest extends SysuiTestCase {
new FakeHandler(mLooper.getLooper()), mKeyguardUpdateMonitor,
mDockManager, mConfigurationController, new FakeExecutor(new FakeSystemClock()),
mScreenOffAnimationController,
- mPanelExpansionStateManager,
mKeyguardUnlockAnimationController,
mStatusBarKeyguardViewManager);
mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible);
@@ -1558,6 +1553,15 @@ public class ScrimControllerTest extends SysuiTestCase {
mScrimInFront.shouldBlendWithMainColor());
}
+ @Test
+ public void applyState_unlocked_bouncerShowing() {
+ mScrimController.transitionTo(ScrimState.UNLOCKED);
+ mScrimController.setBouncerHiddenFraction(0.99f);
+ mScrimController.setRawPanelExpansionFraction(0f);
+ finishAnimationsImmediately();
+ assertScrimAlpha(mScrimBehind, 0);
+ }
+
private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) {
mScrimController.setRawPanelExpansionFraction(expansion);
finishAnimationsImmediately();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
new file mode 100644
index 000000000000..d84010dc7771
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarBoundsProviderTest.kt
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper.RunWithLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.StatusBarBoundsProvider.BoundsChangeListener
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@RunWithLooper(setAsMainLooper = true)
+@SmallTest
+class StatusBarBoundsProviderTest : SysuiTestCase() {
+
+ companion object {
+ private val START_SIDE_BOUNDS = Rect(50, 100, 150, 200)
+ private val END_SIDE_BOUNDS = Rect(250, 300, 350, 400)
+ }
+
+ @Mock private lateinit var boundsChangeListener: BoundsChangeListener
+
+ private lateinit var boundsProvider: StatusBarBoundsProvider
+
+ private lateinit var startSideContent: View
+ private lateinit var endSideContent: View
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ startSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(START_SIDE_BOUNDS) }
+ endSideContent = spy(FrameLayout(context)).apply { setBoundsOnScreen(END_SIDE_BOUNDS) }
+
+ boundsProvider =
+ StatusBarBoundsProvider(setOf(boundsChangeListener), startSideContent, endSideContent)
+ }
+
+ @Test
+ fun visibleStartSideBounds_returnsBoundsFromStartSideContentView() {
+ assertThat(boundsProvider.visibleStartSideBounds).isEqualTo(START_SIDE_BOUNDS)
+ }
+
+ @Test
+ fun visibleEndSideBounds_returnsBoundsFromEndSideContentView() {
+ assertThat(boundsProvider.visibleEndSideBounds).isEqualTo(END_SIDE_BOUNDS)
+ }
+
+ @Test
+ fun startBoundsChange_afterStart_notifiesListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun startBoundsChange_beforeStart_doesNotNotifyListener() {
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun startBoundsChange_afterStop_doesNotNotifyListener() {
+ boundsProvider.start()
+ boundsProvider.stop()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun startLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { left += 1 }
+
+ startSideContent.layout(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endBoundsChange_afterStart_notifiesListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endBoundsChange_beforeStart_doesNotNotifyListener() {
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endBoundsChange_afterStop_doesNotNotifyListener() {
+ boundsProvider.start()
+ boundsProvider.stop()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.setBoundsOnScreen(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+
+ @Test
+ fun endLayoutChange_afterStart_boundsOnScreenSame_doesNotNotifyListener() {
+ boundsProvider.start()
+ val newBounds = Rect(START_SIDE_BOUNDS).apply { right += 1 }
+
+ endSideContent.layout(newBounds)
+
+ verify(boundsChangeListener, never()).onStatusBarBoundsChanged()
+ }
+}
+
+private fun View.setBoundsOnScreen(bounds: Rect) {
+ doAnswer { invocation ->
+ val boundsOutput = invocation.arguments[0] as Rect
+ boundsOutput.set(bounds)
+ return@doAnswer Unit
+ }
+ .`when`(this)
+ .getBoundsOnScreen(any())
+ layout(bounds.left, bounds.top, bounds.right, bounds.bottom)
+}
+
+private fun View.layout(rect: Rect) {
+ layout(rect.left, rect.top, rect.right, rect.bottom)
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java
index 4c20b61083f7..f0a4f3f2bf7a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarIconListTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconListTest.java
@@ -1,4 +1,20 @@
-package com.android.systemui.statusbar;
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone;
import static com.android.systemui.statusbar.phone.StatusBarIconController.TAG_PRIMARY;
@@ -9,13 +25,10 @@ import static junit.framework.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import android.test.suitebuilder.annotation.SmallTest;
-
+import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.phone.StatusBarIconHolder;
-import com.android.systemui.statusbar.phone.StatusBarIconList;
import com.android.systemui.statusbar.phone.StatusBarIconList.Slot;
import org.junit.Test;
@@ -33,28 +46,39 @@ public class StatusBarIconListTest extends SysuiTestCase {
@Test
public void testGetExistingSlot() {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
- assertEquals(1, statusBarIconList.getSlotIndex("bbb"));
- assertEquals(2, statusBarIconList.getSlotIndex("ccc"));
+
+ List<Slot> slots = statusBarIconList.getSlots();
+ assertEquals(3, slots.size());
+ assertEquals("aaa", slots.get(0).getName());
+ assertEquals("bbb", slots.get(1).getName());
+ assertEquals("ccc", slots.get(2).getName());
}
@Test
public void testGetNonexistingSlot() {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
- assertEquals(0, statusBarIconList.getSlotIndex("aaa"));
- assertEquals(3, statusBarIconList.size());
- assertEquals(0, statusBarIconList.getSlotIndex("zzz")); // new content added in front
- assertEquals(1, statusBarIconList.getSlotIndex("aaa")); // slid back
- assertEquals(4, statusBarIconList.size());
+
+ statusBarIconList.getSlot("zzz");
+
+ List<Slot> slots = statusBarIconList.getSlots();
+ assertEquals(4, slots.size());
+ // new content added in front, so zzz should be first and aaa should slide back to second
+ assertEquals("zzz", slots.get(0).getName());
+ assertEquals("aaa", slots.get(1).getName());
}
@Test
public void testAddSlotSlidesIcons() {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
- statusBarIconList.setIcon(0, sbHolder);
- statusBarIconList.getSlotIndex("zzz"); // new content added in front
- assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY));
- assertEquals(sbHolder, statusBarIconList.getIcon(1, TAG_PRIMARY));
+ statusBarIconList.setIcon("aaa", sbHolder);
+
+ statusBarIconList.getSlot("zzz");
+
+ List<Slot> slots = statusBarIconList.getSlots();
+ // new content added in front, so the holder we set on "aaa" should show up at index 1
+ assertNull(slots.get(0).getHolderForTag(TAG_PRIMARY));
+ assertEquals(sbHolder, slots.get(1).getHolderForTag(TAG_PRIMARY));
}
@Test
@@ -62,11 +86,13 @@ public class StatusBarIconListTest extends SysuiTestCase {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
- statusBarIconList.setIcon(0, sbHolderA);
- statusBarIconList.setIcon(1, sbHolderB);
- assertEquals(sbHolderA, statusBarIconList.getIcon(0, TAG_PRIMARY));
- assertEquals(sbHolderB, statusBarIconList.getIcon(1, TAG_PRIMARY));
- assertNull(statusBarIconList.getIcon(2, TAG_PRIMARY)); // icon not set
+
+ statusBarIconList.setIcon("aaa", sbHolderA);
+ statusBarIconList.setIcon("bbb", sbHolderB);
+
+ assertEquals(sbHolderA, statusBarIconList.getIconHolder("aaa", TAG_PRIMARY));
+ assertEquals(sbHolderB, statusBarIconList.getIconHolder("bbb", TAG_PRIMARY));
+ assertNull(statusBarIconList.getIconHolder("ccc", TAG_PRIMARY)); // icon not set
}
@Test
@@ -74,24 +100,31 @@ public class StatusBarIconListTest extends SysuiTestCase {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
StatusBarIconHolder sbHolderA = mock(StatusBarIconHolder.class);
StatusBarIconHolder sbHolderB = mock(StatusBarIconHolder.class);
- statusBarIconList.setIcon(0, sbHolderA);
- statusBarIconList.setIcon(1, sbHolderB);
- statusBarIconList.removeIcon(0, TAG_PRIMARY);
- assertNull(statusBarIconList.getIcon(0, TAG_PRIMARY)); // icon not set
+
+ statusBarIconList.setIcon("aaa", sbHolderA);
+ statusBarIconList.setIcon("bbb", sbHolderB);
+
+ statusBarIconList.removeIcon("aaa", TAG_PRIMARY);
+
+ assertNull(statusBarIconList.getIconHolder("aaa", TAG_PRIMARY)); // icon not set
}
@Test
public void testGetViewIndex_NoMultiples() {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
- statusBarIconList.setIcon(2, sbHolder);
- // Icon for item 2 is 0th child view.
- assertEquals(0, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
- statusBarIconList.setIcon(0, sbHolder);
- // Icon for item 0 is 0th child view,
- assertEquals(0, statusBarIconList.getViewIndex(0, TAG_PRIMARY));
- // and item 2 is now 1st child view.
- assertEquals(1, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
+
+ statusBarIconList.setIcon("ccc", sbHolder);
+
+ // Since only "ccc" has a holder set, it should be first
+ assertEquals(0, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
+
+ // Now, also set a holder for "aaa"
+ statusBarIconList.setIcon("aaa", sbHolder);
+
+ // Then "aaa" gets the first view index and "ccc" gets the second
+ assertEquals(0, statusBarIconList.getViewIndex("aaa", TAG_PRIMARY));
+ assertEquals(1, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
}
@Test
@@ -99,7 +132,7 @@ public class StatusBarIconListTest extends SysuiTestCase {
StatusBarIconList statusBarIconList = new StatusBarIconList(STATUS_BAR_SLOTS);
StatusBarIconHolder sbHolder = mock(StatusBarIconHolder.class);
- statusBarIconList.setIcon(2, sbHolder); // item 2, one icon 0th child
+ statusBarIconList.setIcon("ccc", sbHolder);
// All of these can be added to the same slot
// no tag bc it defaults to 0
@@ -111,23 +144,23 @@ public class StatusBarIconListTest extends SysuiTestCase {
int sb4Tag = 2;
when(sbHolder4.getTag()).thenReturn(sb4Tag);
- // Put a holder at slot 1, verify that it is first
- statusBarIconList.setIcon(1, sbHolder2);
- assertEquals(0, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
-
- // Put another holder at slot 1, verify it's index 0 and the rest come after
- statusBarIconList.setIcon(1, sbHolder3);
- assertEquals(0, statusBarIconList.getViewIndex(1, sb3Tag));
- assertEquals(1, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
- // First icon should be at the end
- assertEquals(2, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
-
- // Put another one in there just for good measure
- statusBarIconList.setIcon(1, sbHolder4);
- assertEquals(0, statusBarIconList.getViewIndex(1, sb4Tag));
- assertEquals(1, statusBarIconList.getViewIndex(1, sb3Tag));
- assertEquals(2, statusBarIconList.getViewIndex(1, TAG_PRIMARY));
- assertEquals(3, statusBarIconList.getViewIndex(2, TAG_PRIMARY));
+ // Put a holder for "bbb", verify that it is first
+ statusBarIconList.setIcon("bbb", sbHolder2);
+ assertEquals(0, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY));
+
+ // Put another holder for "bbb" at slot 1, verify its index 0 and the rest come after
+ statusBarIconList.setIcon("bbb", sbHolder3);
+ assertEquals(0, statusBarIconList.getViewIndex("bbb", sb3Tag));
+ assertEquals(1, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY));
+ // "ccc" should appear at the end
+ assertEquals(2, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
+
+ // Put another one in "bbb" just for good measure
+ statusBarIconList.setIcon("bbb", sbHolder4);
+ assertEquals(0, statusBarIconList.getViewIndex("bbb", sb4Tag));
+ assertEquals(1, statusBarIconList.getViewIndex("bbb", sb3Tag));
+ assertEquals(2, statusBarIconList.getViewIndex("bbb", TAG_PRIMARY));
+ assertEquals(3, statusBarIconList.getViewIndex("ccc", TAG_PRIMARY));
}
/**
@@ -172,4 +205,4 @@ public class StatusBarIconListTest extends SysuiTestCase {
return true;
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index cd081e526c80..79fce82a1bcd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -47,6 +47,7 @@ import com.android.systemui.dock.DockManager;
import com.android.systemui.dreams.DreamOverlayStateController;
import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationShadeWindowController;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -61,7 +62,9 @@ import com.google.common.truth.Truth;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
@@ -97,6 +100,7 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
@Mock private LatencyTracker mLatencyTracker;
private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+ private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
@Before
public void setUp() {
@@ -136,6 +140,11 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mBypassController);
when(mKeyguardStateController.isOccluded()).thenReturn(false);
mStatusBarKeyguardViewManager.show(null);
+ ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback> callbackArgumentCaptor =
+ ArgumentCaptor.forClass(KeyguardBouncer.BouncerExpansionCallback.class);
+ verify(mKeyguardBouncerFactory).create(any(ViewGroup.class),
+ callbackArgumentCaptor.capture());
+ mBouncerExpansionCallback = callbackArgumentCaptor.getValue();
}
@Test
@@ -267,21 +276,18 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
// Should be false to start, so no invocations
mStatusBarKeyguardViewManager.setOccluded(false /* occluded */, false /* animated */);
- verify(mKeyguardUpdateMonitor, never()).onKeyguardOccludedChanged(anyBoolean());
verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
clearInvocations(mKeyguardUpdateMonitor);
clearInvocations(mKeyguardStateController);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardUpdateMonitor).onKeyguardOccludedChanged(true);
verify(mKeyguardStateController).notifyKeyguardState(true, true);
clearInvocations(mKeyguardUpdateMonitor);
clearInvocations(mKeyguardStateController);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardUpdateMonitor, never()).onKeyguardOccludedChanged(anyBoolean());
verify(mKeyguardStateController, never()).notifyKeyguardState(anyBoolean(), anyBoolean());
}
@@ -291,7 +297,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.show(null);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardUpdateMonitor).onKeyguardOccludedChanged(true);
verify(mKeyguardStateController).notifyKeyguardState(true, true);
}
@@ -301,7 +306,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
mStatusBarKeyguardViewManager.show(null);
mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
- verify(mKeyguardUpdateMonitor).onKeyguardOccludedChanged(true);
verify(mKeyguardStateController).notifyKeyguardState(true, true);
}
@@ -451,4 +455,24 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase {
return new PanelExpansionChangeEvent(
fraction, expanded, tracking, /* dragDownPxAmount= */ 0f);
}
+
+ @Test
+ public void testReportBouncerOnDreamWhenVisible() {
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ Mockito.clearInvocations(mCentralSurfaces);
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(true);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(true);
+ }
+
+ @Test
+ public void testReportBouncerOnDreamWhenNotVisible() {
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ Mockito.clearInvocations(mCentralSurfaces);
+ when(mDreamOverlayStateController.isOverlayActive()).thenReturn(true);
+ mBouncerExpansionCallback.onVisibilityChanged(false);
+ verify(mCentralSurfaces).setBouncerShowingOverDream(false);
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index ecea14c6a522..7046150ba373 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -59,18 +59,17 @@ import com.android.systemui.assist.AssistManager;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowViewController;
import com.android.systemui.statusbar.NotificationClickNotifier;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
import com.android.systemui.statusbar.NotificationPresenter;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -127,8 +126,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
@Mock
private ShadeControllerImpl mShadeController;
@Mock
- private NotifPipelineFlags mNotifPipelineFlags;
- @Mock
private NotifPipeline mNotifPipeline;
@Mock
private NotificationVisibilityProvider mVisibilityProvider;
@@ -148,15 +145,13 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
private ActivityLaunchAnimator mActivityLaunchAnimator;
@Mock
private InteractionJankMonitor mJankMonitor;
- private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
- private NotificationTestHelper mNotificationTestHelper;
+ private final FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
private ExpandableNotificationRow mNotificationRow;
private ExpandableNotificationRow mBubbleNotificationRow;
private final Answer<Void> mCallOnDismiss = answerVoid(
(OnDismissAction dismissAction, Runnable cancel,
Boolean afterKeyguardGone) -> dismissAction.onDismiss());
- private ArrayList<NotificationEntry> mActiveNotifications;
@Before
public void setUp() throws Exception {
@@ -165,29 +160,28 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
when(mContentIntent.getCreatorUserHandle()).thenReturn(UserHandle.of(1));
when(mContentIntent.getIntent()).thenReturn(mContentIntentInner);
- mNotificationTestHelper = new NotificationTestHelper(
+ NotificationTestHelper notificationTestHelper = new NotificationTestHelper(
mContext,
mDependency,
TestableLooper.get(this));
// Create standard notification with contentIntent
- mNotificationRow = mNotificationTestHelper.createRow();
+ mNotificationRow = notificationTestHelper.createRow();
StatusBarNotification sbn = mNotificationRow.getEntry().getSbn();
sbn.getNotification().contentIntent = mContentIntent;
sbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
// Create bubble notification row with contentIntent
- mBubbleNotificationRow = mNotificationTestHelper.createBubble();
+ mBubbleNotificationRow = notificationTestHelper.createBubble();
StatusBarNotification bubbleSbn = mBubbleNotificationRow.getEntry().getSbn();
bubbleSbn.getNotification().contentIntent = mContentIntent;
bubbleSbn.getNotification().flags |= Notification.FLAG_AUTO_CANCEL;
- mActiveNotifications = new ArrayList<>();
- mActiveNotifications.add(mNotificationRow.getEntry());
- mActiveNotifications.add(mBubbleNotificationRow.getEntry());
- when(mEntryManager.getVisibleNotifications()).thenReturn(mActiveNotifications);
+ ArrayList<NotificationEntry> activeNotifications = new ArrayList<>();
+ activeNotifications.add(mNotificationRow.getEntry());
+ activeNotifications.add(mBubbleNotificationRow.getEntry());
+ when(mEntryManager.getVisibleNotifications()).thenReturn(activeNotifications);
when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
when(mOnUserInteractionCallback.registerFutureDismissal(eq(mNotificationRow.getEntry()),
anyInt())).thenReturn(mFutureDismissalRunnable);
when(mVisibilityProvider.obtain(anyString(), anyBoolean()))
@@ -207,23 +201,19 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mNotificationActivityStarter =
new StatusBarNotificationActivityStarter(
getContext(),
- mock(CommandQueue.class),
mHandler,
mUiBgExecutor,
- mEntryManager,
mNotifPipeline,
mVisibilityProvider,
headsUpManager,
mActivityStarter,
mClickNotifier,
- mock(StatusBarStateController.class),
mStatusBarKeyguardViewManager,
mock(KeyguardManager.class),
mock(IDreamManager.class),
Optional.of(mBubblesManager),
() -> mAssistManager,
mRemoteInputManager,
- mock(NotificationGroupManagerLegacy.class),
mock(NotificationLockscreenUserManager.class),
mShadeController,
mKeyguardStateController,
@@ -231,7 +221,6 @@ public class StatusBarNotificationActivityStarterTest extends SysuiTestCase {
mock(LockPatternUtils.class),
mock(StatusBarRemoteInputCallback.class),
mActivityIntentHelper,
- mNotifPipelineFlags,
mock(MetricsLogger.class),
mock(StatusBarNotificationActivityStarterLogger.class),
mOnUserInteractionCallback,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 1a3dd3a7a2a5..fdb29770cbb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -16,12 +16,14 @@ package com.android.systemui.statusbar.phone;
import static android.view.Display.DEFAULT_DISPLAY;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.Notification;
+import android.app.PendingIntent;
import android.app.StatusBarManager;
import android.metrics.LogMaker;
import android.support.test.metricshelper.MetricsAsserts;
@@ -33,12 +35,13 @@ import androidx.test.filters.SmallTest;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.internal.logging.testing.FakeMetricsLogger;
-import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.systemui.ForegroundServiceNotificationListener;
import com.android.systemui.InitController;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.plugins.ActivityStarter;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
+import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.KeyguardIndicationController;
import com.android.systemui.statusbar.LockscreenShadeTransitionController;
@@ -50,7 +53,6 @@ import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
@@ -61,7 +63,6 @@ import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import org.junit.Before;
@@ -81,6 +82,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
private FakeMetricsLogger mMetricsLogger;
private ShadeController mShadeController = mock(ShadeController.class);
private CentralSurfaces mCentralSurfaces = mock(CentralSurfaces.class);
+ private KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class);
+ private NotifPipelineFlags mNotifPipelineFlags = mock(NotifPipelineFlags.class);
private InitController mInitController = new InitController();
@Before
@@ -115,7 +118,7 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(ScrimController.class),
mock(NotificationShadeWindowController.class),
mock(DynamicPrivacyController.class),
- mock(KeyguardStateController.class),
+ mKeyguardStateController,
mock(KeyguardIndicationController.class),
mCentralSurfaces,
mock(ShadeControllerImpl.class),
@@ -125,16 +128,13 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
mock(NotificationLockscreenUserManager.class),
mock(SysuiStatusBarStateController.class),
mock(NotifShadeEventSource.class),
- mock(NotificationEntryManager.class),
mock(NotificationMediaManager.class),
mock(NotificationGutsManager.class),
- mock(KeyguardUpdateMonitor.class),
lockscreenGestureLogger,
mInitController,
mNotificationInterruptStateProvider,
mock(NotificationRemoteInputManager.class),
- mock(ConfigurationController.class),
- mock(NotifPipelineFlags.class),
+ mNotifPipelineFlags,
mock(NotificationRemoteInputManager.Callback.class),
mock(NotificationListContainer.class));
mInitController.executePostInitTasks();
@@ -145,6 +145,19 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
}
@Test
+ public void testNoSuppressHeadsUp_default() {
+ Notification n = new Notification.Builder(getContext(), "a").build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
public void testSuppressHeadsUp_disabledStatusBar() {
Notification n = new Notification.Builder(getContext(), "a").build();
NotificationEntry entry = new NotificationEntryBuilder()
@@ -180,6 +193,63 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase {
}
@Test
+ public void testNoSuppressHeadsUp_FSI_occludedKeygaurd() {
+ when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(true);
+ when(mCentralSurfaces.isOccluded()).thenReturn(true);
+ assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
+ public void testSuppressHeadsUp_FSI_nonOccludedKeygaurd() {
+ when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(false);
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mCentralSurfaces.isOccluded()).thenReturn(false);
+ assertTrue(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
+ public void testNoSuppressHeadsUp_FSI_nonOccludedKeygaurd_withNewFlag() {
+ when(mNotifPipelineFlags.fullScreenIntentRequiresKeyguard()).thenReturn(true);
+ Notification n = new Notification.Builder(getContext(), "a")
+ .setFullScreenIntent(mock(PendingIntent.class), true)
+ .build();
+ NotificationEntry entry = new NotificationEntryBuilder()
+ .setPkg("a")
+ .setOpPkg("a")
+ .setTag("a")
+ .setNotification(n)
+ .build();
+
+ when(mKeyguardStateController.isShowing()).thenReturn(true);
+ when(mKeyguardStateController.isOccluded()).thenReturn(false);
+ when(mCentralSurfaces.isOccluded()).thenReturn(false);
+ assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(entry));
+ }
+
+ @Test
public void testSuppressInterruptions_vrMode() {
Notification n = new Notification.Builder(getContext(), "a").build();
NotificationEntry entry = new NotificationEntryBuilder()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
new file mode 100644
index 000000000000..fa7b2599c108
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -0,0 +1,250 @@
+package com.android.systemui.statusbar.phone
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.view.Display
+import android.view.InsetsVisibilities
+import android.view.WindowInsetsController
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
+import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
+import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
+import android.view.WindowInsetsController.Appearance
+import androidx.test.filters.SmallTest
+import com.android.internal.statusbar.LetterboxDetails
+import com.android.internal.view.AppearanceRegion
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.any
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.reset
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class SystemBarAttributesListenerTest : SysuiTestCase() {
+
+ @Mock private lateinit var dumpManager: DumpManager
+ @Mock private lateinit var lightBarController: LightBarController
+ @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
+ @Mock private lateinit var letterboxAppearanceCalculator: LetterboxAppearanceCalculator
+ @Mock private lateinit var featureFlags: FeatureFlags
+ @Mock private lateinit var centralSurfaces: CentralSurfaces
+
+ private lateinit var sysBarAttrsListener: SystemBarAttributesListener
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+
+ whenever(
+ letterboxAppearanceCalculator.getLetterboxAppearance(
+ anyInt(), anyObject(), anyObject()))
+ .thenReturn(TEST_LETTERBOX_APPEARANCE)
+
+ sysBarAttrsListener =
+ SystemBarAttributesListener(
+ centralSurfaces,
+ featureFlags,
+ letterboxAppearanceCalculator,
+ statusBarStateController,
+ lightBarController,
+ dumpManager)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_forwardsAppearanceToCentralSurfaces() {
+ val appearance = APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
+
+ changeSysBarAttrs(appearance)
+
+ verify(centralSurfaces).setAppearance(appearance)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToCentralSurfaces() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
+
+ verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_noLetterbox_forwardsOriginalAppearanceToCtrlSrfcs() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, arrayOf<LetterboxDetails>())
+
+ verify(centralSurfaces).setAppearance(TEST_APPEARANCE)
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_forwardsAppearanceToStatusBarStateController() {
+ changeSysBarAttrs(TEST_APPEARANCE)
+
+ verify(statusBarStateController)
+ .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToStatusBarStateCtrl() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_LETTERBOX_DETAILS)
+
+ verify(statusBarStateController)
+ .setSystemBarAttributes(
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_forwardsAppearanceToLightBarController() {
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS)
+
+ verify(lightBarController)
+ .onStatusBarAppearanceChanged(
+ eq(TEST_APPEARANCE_REGIONS), anyBoolean(), anyInt(), anyBoolean())
+ }
+
+ @Test
+ fun onSysBarAttrsChanged_flagTrue_forwardsLetterboxAppearanceToLightBarController() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+
+ verify(lightBarController)
+ .onStatusBarAppearanceChanged(
+ eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean())
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToStatusBarStateController() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verify(statusBarStateController)
+ .setSystemBarAttributes(
+ eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToLightBarController() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verify(lightBarController)
+ .onStatusBarAppearanceChanged(
+ eq(TEST_LETTERBOX_APPEARANCE.appearanceRegions),
+ anyBoolean(),
+ anyInt(),
+ anyBoolean())
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_forwardsLetterboxAppearanceToCentralSurfaces() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verify(centralSurfaces).setAppearance(TEST_LETTERBOX_APPEARANCE.appearance)
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_previousCallEmptyLetterbox_doesNothing() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(true)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, arrayOf())
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
+ }
+
+ @Test
+ fun onStatusBarBoundsChanged_flagFalse_doesNothing() {
+ whenever(featureFlags.isEnabled(Flags.STATUS_BAR_LETTERBOX_APPEARANCE)).thenReturn(false)
+ changeSysBarAttrs(TEST_APPEARANCE, TEST_APPEARANCE_REGIONS, TEST_LETTERBOX_DETAILS)
+ reset(centralSurfaces, lightBarController, statusBarStateController)
+
+ sysBarAttrsListener.onStatusBarBoundsChanged()
+
+ verifyZeroInteractions(centralSurfaces, lightBarController, statusBarStateController)
+ }
+
+ private fun changeSysBarAttrs(@Appearance appearance: Int) {
+ changeSysBarAttrs(appearance, arrayOf<LetterboxDetails>())
+ }
+
+ private fun changeSysBarAttrs(
+ @Appearance appearance: Int,
+ letterboxDetails: Array<LetterboxDetails>
+ ) {
+ changeSysBarAttrs(appearance, arrayOf(), letterboxDetails)
+ }
+
+ private fun changeSysBarAttrs(
+ @Appearance appearance: Int,
+ appearanceRegions: Array<AppearanceRegion>
+ ) {
+ changeSysBarAttrs(appearance, appearanceRegions, arrayOf())
+ }
+
+ private fun changeSysBarAttrs(
+ @Appearance appearance: Int,
+ appearanceRegions: Array<AppearanceRegion>,
+ letterboxDetails: Array<LetterboxDetails>
+ ) {
+ sysBarAttrsListener.onSystemBarAttributesChanged(
+ Display.DEFAULT_DISPLAY,
+ appearance,
+ appearanceRegions,
+ /* navbarColorManagedByIme= */ false,
+ WindowInsetsController.BEHAVIOR_DEFAULT,
+ InsetsVisibilities(),
+ "package name",
+ letterboxDetails)
+ }
+
+ companion object {
+ private const val TEST_APPEARANCE =
+ APPEARANCE_LIGHT_STATUS_BARS or APPEARANCE_LIGHT_NAVIGATION_BARS
+ private val TEST_APPEARANCE_REGION = AppearanceRegion(TEST_APPEARANCE, Rect(0, 0, 150, 300))
+ private val TEST_APPEARANCE_REGIONS = arrayOf(TEST_APPEARANCE_REGION)
+ private val TEST_LETTERBOX_DETAILS =
+ arrayOf(
+ LetterboxDetails(
+ /* letterboxInnerBounds= */ Rect(0, 0, 0, 0),
+ /* letterboxFullBounds= */ Rect(0, 0, 0, 0),
+ /* appAppearance= */ 0))
+ private val TEST_LETTERBOX_APPEARANCE =
+ LetterboxAppearance(/* appearance= */ APPEARANCE_LOW_PROFILE_BARS, arrayOf())
+ }
+}
+
+private fun <T> anyObject(): T {
+ return Mockito.anyObject<T>()
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
index 011279721fd2..746c92e485b7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt
@@ -27,6 +27,7 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.systemui.SysuiTestCase
import com.android.systemui.keyguard.KeyguardViewMediator
import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.shade.NotificationPanelViewController
import com.android.systemui.statusbar.LightRevealScrim
import com.android.systemui.statusbar.StatusBarStateControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -39,12 +40,12 @@ import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
import org.mockito.Mockito
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.never
import org.mockito.Mockito.spy
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@@ -184,4 +185,4 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() {
controller.startAnimation()
assertFalse(controller.isAnimationPlaying())
}
-} \ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 6abc687f0ebb..56804d58d241 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -41,10 +41,13 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiBaseFragmentTest;
+import com.android.systemui.dump.DumpManager;
import com.android.systemui.flags.FeatureFlags;
import com.android.systemui.log.LogBuffer;
import com.android.systemui.log.LogcatEchoTracker;
+import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.DisableFlagsLogger;
import com.android.systemui.statusbar.OperatorNameViewController;
@@ -52,7 +55,6 @@ import com.android.systemui.statusbar.connectivity.NetworkController;
import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler;
import com.android.systemui.statusbar.phone.HeadsUpAppearanceController;
import com.android.systemui.statusbar.phone.NotificationIconAreaController;
-import com.android.systemui.statusbar.phone.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.StatusBarHideIconsForBouncerManager;
import com.android.systemui.statusbar.phone.StatusBarIconController;
import com.android.systemui.statusbar.phone.StatusBarLocationPublisher;
@@ -106,6 +108,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
private NotificationPanelViewController mNotificationPanelViewController;
@Mock
private StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
+ @Mock
+ private DumpManager mDumpManager;
public CollapsedStatusBarFragmentTest() {
super(CollapsedStatusBarFragment.class);
@@ -114,6 +118,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
@Before
public void setup() {
injectLeakCheckedDependencies(ALL_SUPPORTED_CLASSES);
+ mDependency.injectMockDependency(DarkIconDispatcher.class);
}
@Test
@@ -122,7 +127,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
@@ -132,11 +137,11 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_SYSTEM_INFO, 0, false);
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
}
@Test
@@ -232,7 +237,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
@@ -244,7 +249,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
}
@@ -255,12 +260,12 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
// Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.GONE, getClockView().getVisibility());
}
@@ -271,14 +276,14 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
when(mNotificationPanelViewController.hasCustomClock()).thenReturn(true);
// Make sure they start out as visible
- assertEquals(View.VISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.VISIBLE, getClockView().getVisibility());
fragment.onDozingChanged(true);
// When this callback is triggered, we want to make sure the clock and system info
// visibilities are recalculated. Since dozing=true, they shouldn't be visible.
- assertEquals(View.INVISIBLE, getSystemIconAreaView().getVisibility());
+ assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
assertEquals(View.GONE, getClockView().getVisibility());
}
@@ -384,7 +389,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
),
mOperatorNameViewControllerFactory,
mSecureSettings,
- mExecutor);
+ mExecutor,
+ mDumpManager);
}
private void setUpDaggerComponent() {
@@ -417,7 +423,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest {
return mFragment.getView().findViewById(R.id.clock);
}
- private View getSystemIconAreaView() {
- return mFragment.getView().findViewById(R.id.system_icon_area);
+ private View getEndSideContentView() {
+ return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
index fec2123b304a..fda80a2f9ed8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BatteryControllerTest.java
@@ -28,6 +28,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.Intent;
+import android.os.BatteryManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.PowerSaveState;
@@ -196,4 +197,26 @@ public class BatteryControllerTest extends SysuiTestCase {
TestableLooper.get(this).processAllMessages();
// Should not throw an exception
}
+
+ @Test
+ public void batteryStateChanged_withChargingSourceDock_isChargingSourceDockTrue() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_CHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_DOCK);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertTrue(mBatteryController.isChargingSourceDock());
+ }
+
+ @Test
+ public void batteryStateChanged_withChargingSourceNotDock_isChargingSourceDockFalse() {
+ Intent intent = new Intent(Intent.ACTION_BATTERY_CHANGED);
+ intent.putExtra(BatteryManager.EXTRA_STATUS, BatteryManager.BATTERY_STATUS_DISCHARGING);
+ intent.putExtra(BatteryManager.EXTRA_PLUGGED, BatteryManager.BATTERY_PLUGGED_WIRELESS);
+
+ mBatteryController.onReceive(getContext(), intent);
+
+ Assert.assertFalse(mBatteryController.isChargingSourceDock());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
new file mode 100644
index 000000000000..db0029af4ee2
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/FlashlightControllerImplTest.kt
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy
+
+import android.content.pm.PackageManager
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraManager
+import android.hardware.camera2.impl.CameraMetadataNative
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.settings.FakeSettings
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FlashlightControllerImplTest : SysuiTestCase() {
+
+ @Mock
+ private lateinit var dumpManager: DumpManager
+
+ @Mock
+ private lateinit var cameraManager: CameraManager
+
+ @Mock
+ private lateinit var broadcastSender: BroadcastSender
+
+ @Mock
+ private lateinit var packageManager: PackageManager
+
+ private lateinit var fakeSettings: FakeSettings
+ private lateinit var fakeSystemClock: FakeSystemClock
+ private lateinit var backgroundExecutor: FakeExecutor
+ private lateinit var controller: FlashlightControllerImpl
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+
+ fakeSystemClock = FakeSystemClock()
+ backgroundExecutor = FakeExecutor(fakeSystemClock)
+ fakeSettings = FakeSettings()
+
+ `when`(packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_FLASH))
+ .thenReturn(true)
+
+ controller = FlashlightControllerImpl(
+ dumpManager,
+ cameraManager,
+ backgroundExecutor,
+ fakeSettings,
+ broadcastSender,
+ packageManager
+ )
+ }
+
+ @Test
+ fun testNoCameraManagerInteractionDirectlyOnConstructor() {
+ verifyZeroInteractions(cameraManager)
+ }
+
+ @Test
+ fun testCameraManagerInitAfterConstructionOnExecutor() {
+ injectCamera()
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager).registerTorchCallback(eq(backgroundExecutor), any())
+ }
+
+ @Test
+ fun testNoCallbackIfNoFlashCamera() {
+ injectCamera(flash = false)
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
+ }
+
+ @Test
+ fun testNoCallbackIfNoBackCamera() {
+ injectCamera(facing = CameraCharacteristics.LENS_FACING_FRONT)
+ backgroundExecutor.runAllReady()
+
+ verify(cameraManager, never()).registerTorchCallback(any<Executor>(), any())
+ }
+
+ @Test
+ fun testSetFlashlightInBackgroundExecutor() {
+ val id = injectCamera()
+ backgroundExecutor.runAllReady()
+
+ clearInvocations(cameraManager)
+ val enable = !controller.isEnabled
+ controller.setFlashlight(enable)
+ verifyNoMoreInteractions(cameraManager)
+
+ backgroundExecutor.runAllReady()
+ verify(cameraManager).setTorchMode(id, enable)
+ }
+
+ private fun injectCamera(
+ flash: Boolean = true,
+ facing: Int = CameraCharacteristics.LENS_FACING_BACK
+ ): String {
+ val cameraID = "ID"
+ val camera = CameraCharacteristics(CameraMetadataNative().apply {
+ set(CameraCharacteristics.FLASH_INFO_AVAILABLE, flash)
+ set(CameraCharacteristics.LENS_FACING, facing)
+ })
+ `when`(cameraManager.cameraIdList).thenReturn(arrayOf(cameraID))
+ `when`(cameraManager.getCameraCharacteristics(cameraID)).thenReturn(camera)
+ return cameraID
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
index 424a40058997..b8e25ab43691 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java
@@ -106,7 +106,7 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest {
public void testHunRemovedLogging() {
mAlertEntry.mEntry = mEntry;
mHeadsUpManager.onAlertEntryRemoved(mAlertEntry);
- verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry.getKey()));
+ verify(mLogger, times(1)).logNotificationActuallyRemoved(eq(mEntry));
}
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
index 6bd8b98f70e1..359a780d2a94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/UserSwitcherControllerTest.kt
@@ -17,6 +17,7 @@
package com.android.systemui.statusbar.policy
import android.app.IActivityManager
+import android.app.NotificationManager
import android.app.admin.DevicePolicyManager
import android.content.BroadcastReceiver
import android.content.Context
@@ -38,9 +39,12 @@ import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.logging.testing.UiEventLoggerFake
import com.android.internal.util.LatencyTracker
import com.android.internal.util.UserIcons
+import com.android.systemui.GuestResetOrExitSessionReceiver
import com.android.systemui.GuestResumeSessionReceiver
+import com.android.systemui.GuestSessionNotification
import com.android.systemui.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogCuj
import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.broadcast.BroadcastSender
@@ -50,7 +54,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.QSUserSwitcherEvent
import com.android.systemui.qs.user.UserSwitchDialogController
import com.android.systemui.settings.UserTracker
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView
+import com.android.systemui.shade.NotificationShadeWindowView
import com.android.systemui.telephony.TelephonyListenerManager
import com.android.systemui.util.concurrency.FakeExecutor
import com.android.systemui.util.mockito.any
@@ -69,12 +73,13 @@ import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.Mock
-import org.mockito.Mockito.`when`
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.eq
import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -102,6 +107,11 @@ class UserSwitcherControllerTest : SysuiTestCase() {
@Mock private lateinit var threadedRenderer: ThreadedRenderer
@Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
@Mock private lateinit var globalSettings: GlobalSettings
+ @Mock private lateinit var guestSessionNotification: GuestSessionNotification
+ @Mock private lateinit var guestResetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
+ private lateinit var resetSessionDialogFactory:
+ GuestResumeSessionReceiver.ResetSessionDialog.Factory
+ private lateinit var guestResumeSessionReceiver: GuestResumeSessionReceiver
private lateinit var testableLooper: TestableLooper
private lateinit var bgExecutor: FakeExecutor
private lateinit var longRunningExecutor: FakeExecutor
@@ -133,9 +143,28 @@ class UserSwitcherControllerTest : SysuiTestCase() {
com.android.internal.R.bool.config_guestUserAutoCreated, false)
mContext.addMockSystemService(Context.FACE_SERVICE, mock(FaceManager::class.java))
+ mContext.addMockSystemService(Context.NOTIFICATION_SERVICE,
+ mock(NotificationManager::class.java))
mContext.addMockSystemService(Context.FINGERPRINT_SERVICE,
mock(FingerprintManager::class.java))
+ resetSessionDialogFactory = object : GuestResumeSessionReceiver.ResetSessionDialog.Factory {
+ override fun create(userId: Int): GuestResumeSessionReceiver.ResetSessionDialog {
+ return GuestResumeSessionReceiver.ResetSessionDialog(
+ mContext,
+ mock(UserSwitcherController::class.java),
+ uiEventLogger,
+ userId
+ )
+ }
+ }
+
+ guestResumeSessionReceiver = GuestResumeSessionReceiver(userTracker,
+ secureSettings,
+ broadcastDispatcher,
+ guestSessionNotification,
+ resetSessionDialogFactory)
+
`when`(userManager.canAddMoreUsers(eq(UserManager.USER_TYPE_FULL_SECONDARY)))
.thenReturn(true)
`when`(notificationShadeWindowView.context).thenReturn(context)
@@ -198,7 +227,9 @@ class UserSwitcherControllerTest : SysuiTestCase() {
interactionJankMonitor,
latencyTracker,
dumpManager,
- dialogLaunchAnimator)
+ dialogLaunchAnimator,
+ guestResumeSessionReceiver,
+ guestResetOrExitSessionReceiver)
userSwitcherController.init(notificationShadeWindowView)
}
@@ -239,6 +270,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, null)
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(interactionJankMonitor).begin(any())
verify(latencyTracker).onActionStart(LatencyTracker.ACTION_USER_SWITCH)
@@ -264,6 +297,8 @@ class UserSwitcherControllerTest : SysuiTestCase() {
`when`(userManager.createGuest(any())).thenReturn(guestInfo)
userSwitcherController.onUserListItemClicked(emptyGuestUserRecord, dialogShower)
+ bgExecutor.runAllReady()
+ uiExecutor.runAllReady()
testableLooper.processAllMessages()
verify(dialogShower).dismiss()
}
@@ -288,7 +323,10 @@ class UserSwitcherControllerTest : SysuiTestCase() {
.getButton(DialogInterface.BUTTON_POSITIVE).performClick()
testableLooper.processAllMessages()
assertEquals(1, uiEventLogger.numLogs())
- assertEquals(QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id, uiEventLogger.eventId(0))
+ assertTrue(
+ QSUserSwitcherEvent.QS_USER_GUEST_REMOVE.id == uiEventLogger.eventId(0) ||
+ QSUserSwitcherEvent.QS_USER_SWITCH.id == uiEventLogger.eventId(0)
+ )
}
@Test
@@ -330,7 +368,10 @@ class UserSwitcherControllerTest : SysuiTestCase() {
userSwitcherController.onUserListItemClicked(currentGuestUserRecord, dialogShower)
assertNotNull(userSwitcherController.mExitGuestDialog)
testableLooper.processAllMessages()
- verify(dialogShower).showDialog(userSwitcherController.mExitGuestDialog)
+ verify(dialogShower)
+ .showDialog(
+ userSwitcherController.mExitGuestDialog,
+ DialogCuj(InteractionJankMonitor.CUJ_USER_DIALOG_OPEN, "exit_guest_mode"))
}
@Test
@@ -350,7 +391,7 @@ class UserSwitcherControllerTest : SysuiTestCase() {
userSwitcherController.onUserListItemClicked(currentGuestUserRecord, null)
assertNotNull(userSwitcherController.mExitGuestDialog)
userSwitcherController.mExitGuestDialog
- .getButton(DialogInterface.BUTTON_NEGATIVE).performClick()
+ .getButton(DialogInterface.BUTTON_NEUTRAL).performClick()
testableLooper.processAllMessages()
assertEquals(0, uiEventLogger.numLogs())
}
@@ -548,4 +589,24 @@ class UserSwitcherControllerTest : SysuiTestCase() {
broadcastReceiverCaptor.value.onReceive(context, intent)
verify(cb).onUserSwitched()
}
+
+ @Test
+ fun onUserItemClicked_guest_runsOnBgThread() {
+ val dialogShower = mock(UserSwitchDialogController.DialogShower::class.java)
+ val guestUserRecord = UserSwitcherController.UserRecord(
+ null,
+ picture,
+ true /* guest */,
+ false /* current */,
+ false /* isAddUser */,
+ false /* isRestricted */,
+ true /* isSwitchToEnabled */,
+ false /* isAddSupervisedUser */)
+
+ userSwitcherController.onUserListItemClicked(guestUserRecord, dialogShower)
+ assertTrue(bgExecutor.numPending() > 0)
+ verify(userManager, never()).createGuest(context)
+ bgExecutor.runAllReady()
+ verify(userManager).createGuest(context)
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
index 8076b4eda883..39e4e6446d02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateLoggingProviderTest.kt
@@ -25,28 +25,20 @@ import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
-import com.android.systemui.unfold.updates.FoldStateProvider
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
+import com.android.systemui.unfold.util.TestFoldStateProvider
import com.android.systemui.util.time.FakeSystemClock
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
-import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@SmallTest
class FoldStateLoggingProviderTest : SysuiTestCase() {
- @Captor
- private lateinit var foldUpdatesListener: ArgumentCaptor<FoldStateProvider.FoldUpdatesListener>
-
- @Mock private lateinit var foldStateProvider: FoldStateProvider
-
+ private val testFoldStateProvider = TestFoldStateProvider()
private val fakeClock = FakeSystemClock()
private lateinit var foldStateLoggingProvider: FoldStateLoggingProvider
@@ -65,12 +57,10 @@ class FoldStateLoggingProviderTest : SysuiTestCase() {
MockitoAnnotations.initMocks(this)
foldStateLoggingProvider =
- FoldStateLoggingProviderImpl(foldStateProvider, fakeClock).apply {
+ FoldStateLoggingProviderImpl(testFoldStateProvider, fakeClock).apply {
addCallback(foldStateLoggingListener)
init()
}
-
- verify(foldStateProvider).addCallback(foldUpdatesListener.capture())
}
@Test
@@ -183,10 +173,10 @@ class FoldStateLoggingProviderTest : SysuiTestCase() {
fun uninit_removesCallback() {
foldStateLoggingProvider.uninit()
- verify(foldStateProvider).removeCallback(foldUpdatesListener.value)
+ assertThat(testFoldStateProvider.hasListeners).isFalse()
}
private fun sendFoldUpdate(@FoldUpdate foldUpdate: Int) {
- foldUpdatesListener.value.onFoldUpdate(foldUpdate)
+ testFoldStateProvider.sendFoldUpdate(foldUpdate)
}
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
new file mode 100644
index 000000000000..ab450e2506f9
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfigTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.config
+
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+
+/**
+ * A test that checks that we load correct resources in
+ * ResourceUnfoldTransitionConfig as we use strings there instead of R constants.
+ * Internal Android resource constants are not available in public APIs,
+ * so we can't use them there directly.
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class ResourceUnfoldTransitionConfigTest : SysuiTestCase() {
+
+ private val config = ResourceUnfoldTransitionConfig()
+
+ @Test
+ fun testIsEnabled() {
+ assertThat(config.isEnabled).isEqualTo(mContext.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionEnabled))
+ }
+
+ @Test
+ fun testHingeAngleEnabled() {
+ assertThat(config.isHingeAngleEnabled).isEqualTo(mContext.resources
+ .getBoolean(com.android.internal.R.bool.config_unfoldTransitionHingeAngle))
+ }
+
+ @Test
+ fun testHalfFoldedTimeout() {
+ assertThat(config.halfFoldedTimeoutMillis).isEqualTo(mContext.resources
+ .getInteger(com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout))
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
index 1f1f88b07ea1..87fca1f23f1a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/DeviceFoldStateProviderTest.kt
@@ -16,82 +16,69 @@
package com.android.systemui.unfold.updates
-import android.app.ActivityManager
-import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD
-import android.app.WindowConfiguration.ActivityType
-import android.hardware.devicestate.DeviceStateManager
-import android.hardware.devicestate.DeviceStateManager.FoldStateListener
import android.os.Handler
import android.testing.AndroidTestingRunner
import androidx.core.util.Consumer
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.system.ActivityManagerActivityTypeProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
-import com.android.systemui.unfold.util.FoldableDeviceStates
-import com.android.systemui.unfold.util.FoldableTestUtils
import com.android.systemui.util.mockito.any
import com.google.common.truth.Truth.assertThat
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
-import org.mockito.ArgumentCaptor
-import org.mockito.Captor
import org.mockito.Mock
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
import org.mockito.MockitoAnnotations
+import java.util.concurrent.Executor
+import org.mockito.Mockito.`when` as whenever
@RunWith(AndroidTestingRunner::class)
@SmallTest
class DeviceFoldStateProviderTest : SysuiTestCase() {
- @Mock private lateinit var hingeAngleProvider: HingeAngleProvider
-
- @Mock private lateinit var screenStatusProvider: ScreenStatusProvider
-
- @Mock private lateinit var deviceStateManager: DeviceStateManager
-
- @Mock private lateinit var activityManager: ActivityManager
-
- @Mock private lateinit var handler: Handler
-
- @Captor private lateinit var foldStateListenerCaptor: ArgumentCaptor<FoldStateListener>
+ @Mock
+ private lateinit var activityTypeProvider: ActivityManagerActivityTypeProvider
- @Captor private lateinit var screenOnListenerCaptor: ArgumentCaptor<ScreenListener>
+ @Mock
+ private lateinit var handler: Handler
- @Captor private lateinit var hingeAngleCaptor: ArgumentCaptor<Consumer<Float>>
+ private val foldProvider = TestFoldProvider()
+ private val screenOnStatusProvider = TestScreenOnStatusProvider()
+ private val testHingeAngleProvider = TestHingeAngleProvider()
private lateinit var foldStateProvider: DeviceFoldStateProvider
private val foldUpdates: MutableList<Int> = arrayListOf()
private val hingeAngleUpdates: MutableList<Float> = arrayListOf()
- private lateinit var deviceStates: FoldableDeviceStates
-
private var scheduledRunnable: Runnable? = null
private var scheduledRunnableDelay: Long? = null
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
- overrideResource(
- com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout,
- HALF_OPENED_TIMEOUT_MILLIS.toInt())
- deviceStates = FoldableTestUtils.findDeviceStates(context)
+
+ val config = object : UnfoldTransitionConfig by ResourceUnfoldTransitionConfig() {
+ override val halfFoldedTimeoutMillis: Int
+ get() = HALF_OPENED_TIMEOUT_MILLIS.toInt()
+ }
foldStateProvider =
DeviceFoldStateProvider(
- context,
- hingeAngleProvider,
- screenStatusProvider,
- deviceStateManager,
- activityManager,
+ config,
+ testHingeAngleProvider,
+ screenOnStatusProvider,
+ foldProvider,
+ activityTypeProvider,
context.mainExecutor,
- handler)
+ handler
+ )
foldStateProvider.addCallback(
object : FoldStateProvider.FoldUpdatesListener {
@@ -105,10 +92,6 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
})
foldStateProvider.start()
- verify(deviceStateManager).registerCallback(any(), foldStateListenerCaptor.capture())
- verify(screenStatusProvider).addCallback(screenOnListenerCaptor.capture())
- verify(hingeAngleProvider).addCallback(hingeAngleCaptor.capture())
-
whenever(handler.postDelayed(any<Runnable>(), any())).then { invocationOnMock ->
scheduledRunnable = invocationOnMock.getArgument<Runnable>(0)
scheduledRunnableDelay = invocationOnMock.getArgument<Long>(1)
@@ -125,7 +108,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
// By default, we're on launcher.
- setupForegroundActivityType(ACTIVITY_TYPE_HOME)
+ setupForegroundActivityType(isHomeActivity = true)
}
@Test
@@ -146,14 +129,14 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
fun testOnFolded_stopsHingeAngleProvider() {
setFoldState(folded = true)
- verify(hingeAngleProvider).stop()
+ assertThat(testHingeAngleProvider.isStarted).isFalse()
}
@Test
fun testOnUnfolded_startsHingeAngleProvider() {
setFoldState(folded = false)
- verify(hingeAngleProvider).start()
+ assertThat(testHingeAngleProvider.isStarted).isTrue()
}
@Test
@@ -310,7 +293,7 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
@Test
fun startClosingEvent_whileNotOnLauncher_doesNotTriggerBeforeThreshold() {
- setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
+ setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(180)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
@@ -319,8 +302,28 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
@Test
+ fun startClosingEvent_whileActivityTypeNotAvailable_triggerBeforeThreshold() {
+ setupForegroundActivityType(isHomeActivity = null)
+ sendHingeAngleEvent(180)
+
+ sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
+ fun startClosingEvent_whileOnLauncher_doesTriggerBeforeThreshold() {
+ setupForegroundActivityType(isHomeActivity = true)
+ sendHingeAngleEvent(180)
+
+ sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES + 1)
+
+ assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
+ }
+
+ @Test
fun startClosingEvent_whileNotOnLauncher_triggersAfterThreshold() {
- setupForegroundActivityType(ACTIVITY_TYPE_STANDARD)
+ setupForegroundActivityType(isHomeActivity = false)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES)
sendHingeAngleEvent(START_CLOSING_ON_APPS_THRESHOLD_DEGREES - 1)
@@ -328,9 +331,8 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
assertThat(foldUpdates).containsExactly(FOLD_UPDATE_START_CLOSING)
}
- private fun setupForegroundActivityType(@ActivityType type: Int) {
- val taskInfo = RunningTaskInfo().apply { topActivityType = type }
- whenever(activityManager.getRunningTasks(1)).thenReturn(listOf(taskInfo))
+ private fun setupForegroundActivityType(isHomeActivity: Boolean?) {
+ whenever(activityTypeProvider.isHomeActivity).thenReturn(isHomeActivity)
}
private fun simulateTimeout(waitTime: Long = HALF_OPENED_TIMEOUT_MILLIS) {
@@ -348,16 +350,72 @@ class DeviceFoldStateProviderTest : SysuiTestCase() {
}
private fun setFoldState(folded: Boolean) {
- val state = if (folded) deviceStates.folded else deviceStates.unfolded
- foldStateListenerCaptor.value.onStateChanged(state)
+ foldProvider.notifyFolded(folded)
}
private fun fireScreenOnEvent() {
- screenOnListenerCaptor.value.onScreenTurnedOn()
+ screenOnStatusProvider.notifyScreenTurnedOn()
}
private fun sendHingeAngleEvent(angle: Int) {
- hingeAngleCaptor.value.accept(angle.toFloat())
+ testHingeAngleProvider.notifyAngle(angle.toFloat())
+ }
+
+ private class TestFoldProvider : FoldProvider {
+ private val callbacks = arrayListOf<FoldCallback>()
+
+ override fun registerCallback(callback: FoldCallback, executor: Executor) {
+ callbacks += callback
+ }
+
+ override fun unregisterCallback(callback: FoldCallback) {
+ callbacks -= callback
+ }
+
+ fun notifyFolded(isFolded: Boolean) {
+ callbacks.forEach { it.onFoldUpdated(isFolded) }
+ }
+ }
+
+ private class TestScreenOnStatusProvider : ScreenStatusProvider {
+ private val callbacks = arrayListOf<ScreenListener>()
+
+ override fun addCallback(listener: ScreenListener) {
+ callbacks += listener
+ }
+
+ override fun removeCallback(listener: ScreenListener) {
+ callbacks -= listener
+ }
+
+ fun notifyScreenTurnedOn() {
+ callbacks.forEach { it.onScreenTurnedOn() }
+ }
+ }
+
+ private class TestHingeAngleProvider : HingeAngleProvider {
+ private val callbacks = arrayListOf<Consumer<Float>>()
+ var isStarted: Boolean = false
+
+ override fun start() {
+ isStarted = true;
+ }
+
+ override fun stop() {
+ isStarted = false;
+ }
+
+ override fun addCallback(listener: Consumer<Float>) {
+ callbacks += listener
+ }
+
+ override fun removeCallback(listener: Consumer<Float>) {
+ callbacks -= listener
+ }
+
+ fun notifyAngle(angle: Float) {
+ callbacks.forEach { it.accept(angle) }
+ }
}
companion object {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
index a3f17aa5ba55..b2cedbf8d606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/NaturalRotationUnfoldProgressProviderTest.kt
@@ -20,18 +20,18 @@ import android.view.IRotationWatcher
import android.view.IWindowManager
import android.view.Surface
import androidx.test.filters.SmallTest
-import com.android.systemui.unfold.UnfoldTransitionProgressProvider
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.util.mockito.any
-import com.android.systemui.SysuiTestCase
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mock
-import org.mockito.Mockito.verify
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
@RunWith(AndroidTestingRunner::class)
@@ -41,16 +41,13 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Mock
lateinit var windowManager: IWindowManager
- @Mock
- lateinit var sourceProvider: UnfoldTransitionProgressProvider
+ private val sourceProvider = TestUnfoldTransitionProvider()
@Mock
lateinit var transitionListener: TransitionProgressListener
lateinit var progressProvider: NaturalRotationUnfoldProgressProvider
- private val sourceProviderListenerCaptor =
- ArgumentCaptor.forClass(TransitionProgressListener::class.java)
private val rotationWatcherCaptor =
ArgumentCaptor.forClass(IRotationWatcher.Stub::class.java)
@@ -66,7 +63,6 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
progressProvider.init()
- verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
verify(windowManager).watchRotation(rotationWatcherCaptor.capture(), any())
progressProvider.addCallback(transitionListener)
@@ -76,7 +72,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
fun testNaturalRotation0_sendTransitionStartedEvent_eventReceived() {
onRotationChanged(Surface.ROTATION_0)
- source.onTransitionStarted()
+ sourceProvider.onTransitionStarted()
verify(transitionListener).onTransitionStarted()
}
@@ -85,7 +81,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
fun testNaturalRotation0_sendTransitionProgressEvent_eventReceived() {
onRotationChanged(Surface.ROTATION_0)
- source.onTransitionProgress(0.5f)
+ sourceProvider.onTransitionProgress(0.5f)
verify(transitionListener).onTransitionProgress(0.5f)
}
@@ -94,7 +90,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
fun testNotNaturalRotation90_sendTransitionStartedEvent_eventNotReceived() {
onRotationChanged(Surface.ROTATION_90)
- source.onTransitionStarted()
+ sourceProvider.onTransitionStarted()
verify(transitionListener, never()).onTransitionStarted()
}
@@ -103,7 +99,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
fun testNaturalRotation90_sendTransitionProgressEvent_eventNotReceived() {
onRotationChanged(Surface.ROTATION_90)
- source.onTransitionProgress(0.5f)
+ sourceProvider.onTransitionProgress(0.5f)
verify(transitionListener, never()).onTransitionProgress(0.5f)
}
@@ -111,7 +107,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Test
fun testRotationBecameUnnaturalDuringTransition_sendsTransitionFinishedEvent() {
onRotationChanged(Surface.ROTATION_0)
- source.onTransitionStarted()
+ sourceProvider.onTransitionStarted()
clearInvocations(transitionListener)
onRotationChanged(Surface.ROTATION_90)
@@ -122,7 +118,7 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
@Test
fun testRotationBecameNaturalDuringTransition_sendsTransitionStartedEvent() {
onRotationChanged(Surface.ROTATION_90)
- source.onTransitionStarted()
+ sourceProvider.onTransitionStarted()
clearInvocations(transitionListener)
onRotationChanged(Surface.ROTATION_0)
@@ -133,7 +129,4 @@ class NaturalRotationUnfoldProgressProviderTest : SysuiTestCase() {
private fun onRotationChanged(rotation: Int) {
rotationWatcherCaptor.value.onRotationChanged(rotation)
}
-
- private val source: TransitionProgressListener
- get() = sourceProviderListenerCaptor.value
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
index db7a85166807..fc2a78a5ee7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/ScaleAwareUnfoldProgressProviderTest.kt
@@ -21,6 +21,7 @@ import android.database.ContentObserver
import android.testing.AndroidTestingRunner
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
+import com.android.systemui.unfold.TestUnfoldTransitionProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.util.mockito.any
@@ -41,15 +42,11 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
lateinit var contentResolver: ContentResolver
@Mock
- lateinit var sourceProvider: UnfoldTransitionProgressProvider
-
- @Mock
lateinit var sinkProvider: TransitionProgressListener
- lateinit var progressProvider: ScaleAwareTransitionProgressProvider
+ private val sourceProvider = TestUnfoldTransitionProvider()
- private val sourceProviderListenerCaptor =
- ArgumentCaptor.forClass(TransitionProgressListener::class.java)
+ lateinit var progressProvider: ScaleAwareTransitionProgressProvider
private val animatorDurationScaleListenerCaptor =
ArgumentCaptor.forClass(ContentObserver::class.java)
@@ -63,7 +60,6 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
contentResolver
)
- verify(sourceProvider).addCallback(sourceProviderListenerCaptor.capture())
verify(contentResolver).registerContentObserver(any(), any(),
animatorDurationScaleListenerCaptor.capture())
@@ -74,7 +70,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
fun onTransitionStarted_animationsEnabled_eventReceived() {
setAnimationsEnabled(true)
- source.onTransitionStarted()
+ sourceProvider.onTransitionStarted()
verify(sinkProvider).onTransitionStarted()
}
@@ -83,7 +79,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
fun onTransitionStarted_animationsNotEnabled_eventNotReceived() {
setAnimationsEnabled(false)
- source.onTransitionStarted()
+ sourceProvider.onTransitionStarted()
verifyNoMoreInteractions(sinkProvider)
}
@@ -92,7 +88,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
fun onTransitionEnd_animationsEnabled_eventReceived() {
setAnimationsEnabled(true)
- source.onTransitionFinished()
+ sourceProvider.onTransitionFinished()
verify(sinkProvider).onTransitionFinished()
}
@@ -101,7 +97,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
fun onTransitionEnd_animationsNotEnabled_eventNotReceived() {
setAnimationsEnabled(false)
- source.onTransitionFinished()
+ sourceProvider.onTransitionFinished()
verifyNoMoreInteractions(sinkProvider)
}
@@ -110,7 +106,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
fun onTransitionProgress_animationsEnabled_eventReceived() {
setAnimationsEnabled(true)
- source.onTransitionProgress(42f)
+ sourceProvider.onTransitionProgress(42f)
verify(sinkProvider).onTransitionProgress(42f)
}
@@ -119,7 +115,7 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
fun onTransitionProgress_animationsNotEnabled_eventNotReceived() {
setAnimationsEnabled(false)
- source.onTransitionProgress(42f)
+ sourceProvider.onTransitionProgress(42f)
verifyNoMoreInteractions(sinkProvider)
}
@@ -133,7 +129,4 @@ class ScaleAwareUnfoldProgressProviderTest : SysuiTestCase() {
ValueAnimator.setDurationScale(durationScale)
animatorDurationScaleListenerCaptor.value.dispatchChange(/* selfChange= */false)
}
-
- private val source: TransitionProgressListener
- get() = sourceProviderListenerCaptor.value
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
index 8f851ec60981..a064e8c81076 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/util/TestFoldStateProvider.kt
@@ -24,6 +24,8 @@ import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
class TestFoldStateProvider : FoldStateProvider {
private val listeners: MutableList<FoldUpdatesListener> = arrayListOf()
+ val hasListeners: Boolean
+ get() = listeners.isNotEmpty()
override fun start() {
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
new file mode 100644
index 000000000000..a85ae7df546b
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserCreatorTest.kt
@@ -0,0 +1,73 @@
+package com.android.systemui.user
+
+import android.content.pm.UserInfo
+import android.graphics.Bitmap
+import android.os.UserManager
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.function.Consumer
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+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
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class UserCreatorTest : SysuiTestCase() {
+ companion object {
+ const val USER_NAME = "abc"
+ }
+
+ @Mock
+ private lateinit var userCreator: UserCreator
+ @Mock
+ private lateinit var userManager: UserManager
+ private lateinit var mainExecutor: FakeExecutor
+ private lateinit var bgExecutor: FakeExecutor
+ private lateinit var user: UserInfo
+
+ @Before
+ fun setUp() {
+ MockitoAnnotations.initMocks(this)
+ mainExecutor = FakeExecutor(FakeSystemClock())
+ bgExecutor = FakeExecutor(FakeSystemClock())
+ userCreator = UserCreator(context, userManager, mainExecutor, bgExecutor)
+ user = Mockito.mock(UserInfo::class.java)
+ Mockito.`when`(userManager.createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0))
+ .thenReturn(user)
+ }
+
+ @Test
+ fun testCreateUser_threadingOrder() {
+ val successCallback = Mockito.mock(Consumer::class.java)
+ val errorCallback = Mockito.mock(Runnable::class.java)
+
+ userCreator.createUser(
+ USER_NAME,
+ null,
+ successCallback as Consumer<UserInfo?>,
+ errorCallback)
+
+ verify(userManager, never()).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ bgExecutor.runAllReady()
+ verify(successCallback, never()).accept(user)
+ mainExecutor.runAllReady()
+ verify(userManager, never()).setUserIcon(anyInt(), any(Bitmap::class.java))
+ bgExecutor.runAllReady()
+
+ verify(userManager).createUser(USER_NAME, UserManager.USER_TYPE_FULL_SECONDARY, 0)
+ verify(userManager).setUserIcon(anyInt(), any(Bitmap::class.java))
+ verify(successCallback).accept(user)
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
index eaad69c6b9d2..66367ecfc95c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/UserSwitcherActivityTest.kt
@@ -23,6 +23,7 @@ import android.view.LayoutInflater
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.classifier.FalsingCollector
import com.android.systemui.plugins.FalsingManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.UserSwitcherController
@@ -46,6 +47,8 @@ class UserSwitcherActivityTest : SysuiTestCase() {
@Mock
private lateinit var layoutInflater: LayoutInflater
@Mock
+ private lateinit var falsingCollector: FalsingCollector
+ @Mock
private lateinit var falsingManager: FalsingManager
@Mock
private lateinit var userManager: UserManager
@@ -59,6 +62,7 @@ class UserSwitcherActivityTest : SysuiTestCase() {
userSwitcherController,
broadcastDispatcher,
layoutInflater,
+ falsingCollector,
falsingManager,
userManager,
userTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
index c7bcdefdc4c3..900d7928fb44 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/ChannelsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/NotificationChannelsTest.java
@@ -28,7 +28,6 @@ import android.util.ArraySet;
import androidx.test.runner.AndroidJUnit4;
import com.android.systemui.SysuiTestCase;
-import com.android.systemui.util.NotificationChannels;
import org.junit.Before;
import org.junit.Test;
@@ -41,7 +40,7 @@ import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
-public class ChannelsTest extends SysuiTestCase {
+public class NotificationChannelsTest extends SysuiTestCase {
private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
@Before
@@ -55,9 +54,10 @@ public class ChannelsTest extends SysuiTestCase {
NotificationChannels.ALERTS,
NotificationChannels.SCREENSHOTS_HEADSUP,
NotificationChannels.STORAGE,
- NotificationChannels.GENERAL,
+ NotificationChannels.INSTANT,
NotificationChannels.BATTERY,
- NotificationChannels.HINTS
+ NotificationChannels.HINTS,
+ NotificationChannels.SETUP
));
NotificationChannels.createAll(mContext);
ArgumentCaptor<List> captor = ArgumentCaptor.forClass(List.class);
@@ -67,4 +67,11 @@ public class ChannelsTest extends SysuiTestCase {
list.forEach((chan) -> assertTrue(ALL_CHANNELS.contains(chan.getId())));
}
+ @Test
+ public void testChannelCleanup() {
+ new NotificationChannels(mContext).start();
+ ArgumentCaptor<String> captor = ArgumentCaptor.forClass(String.class);
+ verify(mMockNotificationManager).deleteNotificationChannel(captor.capture());
+ assertEquals(NotificationChannels.GENERAL, captor.getValue());
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
index 5118637ea710..125b3627b342 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/condition/ConditionMonitorTest.java
@@ -65,7 +65,12 @@ public class ConditionMonitorTest extends SysuiTestCase {
mCondition3 = spy(new FakeCondition());
mConditions = new HashSet<>(Arrays.asList(mCondition1, mCondition2, mCondition3));
- mConditionMonitor = new Monitor(mExecutor, mConditions, null /*callbacks*/);
+ mConditionMonitor = new Monitor(mExecutor);
+ }
+
+ public Monitor.Subscription.Builder getDefaultBuilder(Monitor.Callback callback) {
+ return new Monitor.Subscription.Builder(callback)
+ .addConditions(mConditions);
}
@Test
@@ -74,10 +79,20 @@ public class ConditionMonitorTest extends SysuiTestCase {
final Condition regularCondition = Mockito.mock(Condition.class);
final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
- final Monitor monitor = new Monitor(
- mExecutor,
- new HashSet<>(Arrays.asList(overridingCondition, regularCondition)),
- new HashSet<>(Arrays.asList(callback)));
+ final Monitor.Callback referenceCallback = Mockito.mock(Monitor.Callback.class);
+
+ final Monitor monitor = new Monitor(mExecutor);
+
+ monitor.addSubscription(getDefaultBuilder(callback)
+ .addCondition(overridingCondition)
+ .addCondition(regularCondition)
+ .build());
+
+ monitor.addSubscription(getDefaultBuilder(referenceCallback)
+ .addCondition(regularCondition)
+ .build());
+
+ mExecutor.runAllReady();
when(overridingCondition.isOverridingCondition()).thenReturn(true);
when(overridingCondition.isConditionMet()).thenReturn(true);
@@ -92,7 +107,9 @@ public class ConditionMonitorTest extends SysuiTestCase {
mExecutor.runAllReady();
verify(callback).onConditionsChanged(eq(true));
+ verify(referenceCallback).onConditionsChanged(eq(false));
Mockito.clearInvocations(callback);
+ Mockito.clearInvocations(referenceCallback);
when(regularCondition.isConditionMet()).thenReturn(true);
when(overridingCondition.isConditionMet()).thenReturn(false);
@@ -101,12 +118,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
mExecutor.runAllReady();
verify(callback).onConditionsChanged(eq(false));
-
- clearInvocations(callback);
- monitor.removeCondition(overridingCondition);
- mExecutor.runAllReady();
-
- verify(callback).onConditionsChanged(eq(true));
+ verify(referenceCallback, never()).onConditionsChanged(anyBoolean());
}
/**
@@ -120,11 +132,14 @@ public class ConditionMonitorTest extends SysuiTestCase {
final Condition regularCondition = Mockito.mock(Condition.class);
final Monitor.Callback callback = Mockito.mock(Monitor.Callback.class);
- final Monitor monitor = new Monitor(
- mExecutor,
- new HashSet<>(Arrays.asList(overridingCondition, overridingCondition2,
- regularCondition)),
- new HashSet<>(Arrays.asList(callback)));
+ final Monitor monitor = new Monitor(mExecutor);
+
+ monitor.addSubscription(getDefaultBuilder(callback)
+ .addCondition(overridingCondition)
+ .addCondition(overridingCondition2)
+ .build());
+
+ mExecutor.runAllReady();
when(overridingCondition.isOverridingCondition()).thenReturn(true);
when(overridingCondition.isConditionMet()).thenReturn(true);
@@ -144,17 +159,35 @@ public class ConditionMonitorTest extends SysuiTestCase {
Mockito.clearInvocations(callback);
}
+ // Ensure that updating a callback that is removed doesn't result in an exception due to the
+ // absence of the condition.
+ @Test
+ public void testUpdateRemovedCallback() {
+ final Monitor.Callback callback1 =
+ mock(Monitor.Callback.class);
+ final Monitor.Subscription.Token subscription1 =
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
+ ArgumentCaptor<Condition.Callback> monitorCallback =
+ ArgumentCaptor.forClass(Condition.Callback.class);
+ mExecutor.runAllReady();
+ verify(mCondition1).addCallback(monitorCallback.capture());
+ // This will execute first before the handler for onConditionChanged.
+ mConditionMonitor.removeSubscription(subscription1);
+ monitorCallback.getValue().onConditionChanged(mCondition1);
+ mExecutor.runAllReady();
+ }
+
@Test
public void addCallback_addFirstCallback_addCallbackToAllConditions() {
final Monitor.Callback callback1 =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback1);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition).addCallback(any()));
final Monitor.Callback callback2 =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback2);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback2).build());
mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition, times(1)).addCallback(any()));
}
@@ -163,7 +196,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
public void addCallback_addFirstCallback_reportWithDefaultValue() {
final Monitor.Callback callback =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
mExecutor.runAllReady();
verify(callback).onConditionsChanged(false);
}
@@ -174,67 +207,65 @@ public class ConditionMonitorTest extends SysuiTestCase {
mock(Monitor.Callback.class);
final Condition condition = mock(Condition.class);
when(condition.isConditionMet()).thenReturn(true);
- final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
- new HashSet<>(Arrays.asList(callback1)));
+ final Monitor monitor = new Monitor(mExecutor);
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback1)
+ .addCondition(condition)
+ .build());
final Monitor.Callback callback2 =
mock(Monitor.Callback.class);
- monitor.addCallback(callback2);
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback2)
+ .addCondition(condition)
+ .build());
mExecutor.runAllReady();
verify(callback2).onConditionsChanged(eq(true));
}
@Test
public void addCallback_noConditions_reportAllConditionsMet() {
- final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
+ final Monitor monitor = new Monitor(mExecutor);
final Monitor.Callback callback = mock(Monitor.Callback.class);
- monitor.addCallback(callback);
+ monitor.addSubscription(new Monitor.Subscription.Builder(callback).build());
mExecutor.runAllReady();
verify(callback).onConditionsChanged(true);
}
@Test
- public void addCallback_withMultipleInstancesOfTheSameCallback_registerOnlyOne() {
- final Monitor monitor = new Monitor(mExecutor, new HashSet<>(), null /*callbacks*/);
- final Monitor.Callback callback = mock(Monitor.Callback.class);
-
- // Adds the same instance multiple times.
- monitor.addCallback(callback);
- monitor.addCallback(callback);
- monitor.addCallback(callback);
+ public void removeCallback_noFailureOnDoubleRemove() {
+ final Condition condition = mock(Condition.class);
+ final Monitor monitor = new Monitor(mExecutor);
+ final Monitor.Callback callback =
+ mock(Monitor.Callback.class);
+ final Monitor.Subscription.Token token = monitor.addSubscription(
+ new Monitor.Subscription.Builder(callback).addCondition(condition).build()
+ );
+ monitor.removeSubscription(token);
+ mExecutor.runAllReady();
+ // Ensure second removal doesn't cause an exception.
+ monitor.removeSubscription(token);
mExecutor.runAllReady();
-
- // Callback should only be triggered once.
- verify(callback, times(1)).onConditionsChanged(true);
}
@Test
public void removeCallback_shouldNoLongerReceiveUpdate() {
final Condition condition = mock(Condition.class);
- final Monitor monitor = new Monitor(mExecutor, new HashSet<>(Arrays.asList(condition)),
- null);
+ final Monitor monitor = new Monitor(mExecutor);
final Monitor.Callback callback =
mock(Monitor.Callback.class);
- monitor.addCallback(callback);
- monitor.removeCallback(callback);
+ final Monitor.Subscription.Token token = monitor.addSubscription(
+ new Monitor.Subscription.Builder(callback).addCondition(condition).build()
+ );
+ monitor.removeSubscription(token);
mExecutor.runAllReady();
clearInvocations(callback);
final ArgumentCaptor<Condition.Callback> conditionCallbackCaptor =
ArgumentCaptor.forClass(Condition.Callback.class);
verify(condition).addCallback(conditionCallbackCaptor.capture());
- final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue();
- when(condition.isConditionMet()).thenReturn(true);
- conditionCallback.onConditionChanged(condition);
- mExecutor.runAllReady();
- verify(callback, never()).onConditionsChanged(true);
-
- when(condition.isConditionMet()).thenReturn(false);
- conditionCallback.onConditionChanged(condition);
- mExecutor.runAllReady();
- verify(callback, never()).onConditionsChanged(false);
+ final Condition.Callback conditionCallback = conditionCallbackCaptor.getValue();
+ verify(condition).removeCallback(conditionCallback);
}
@Test
@@ -243,14 +274,16 @@ public class ConditionMonitorTest extends SysuiTestCase {
mock(Monitor.Callback.class);
final Monitor.Callback callback2 =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback1);
- mConditionMonitor.addCallback(callback2);
+ final Monitor.Subscription.Token subscription1 =
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback1).build());
+ final Monitor.Subscription.Token subscription2 =
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback2).build());
- mConditionMonitor.removeCallback(callback1);
+ mConditionMonitor.removeSubscription(subscription1);
mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition, never()).removeCallback(any()));
- mConditionMonitor.removeCallback(callback2);
+ mConditionMonitor.removeSubscription(subscription2);
mExecutor.runAllReady();
mConditions.forEach(condition -> verify(condition).removeCallback(any()));
}
@@ -259,7 +292,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
public void updateCallbacks_allConditionsMet_reportTrue() {
final Monitor.Callback callback =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
clearInvocations(callback);
mCondition1.fakeUpdateCondition(true);
@@ -274,7 +307,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
public void updateCallbacks_oneConditionStoppedMeeting_reportFalse() {
final Monitor.Callback callback =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
mCondition1.fakeUpdateCondition(true);
mCondition2.fakeUpdateCondition(true);
@@ -290,7 +323,7 @@ public class ConditionMonitorTest extends SysuiTestCase {
public void updateCallbacks_shouldOnlyUpdateWhenValueChanges() {
final Monitor.Callback callback =
mock(Monitor.Callback.class);
- mConditionMonitor.addCallback(callback);
+ mConditionMonitor.addSubscription(getDefaultBuilder(callback).build());
mExecutor.runAllReady();
verify(callback).onConditionsChanged(false);
clearInvocations(callback);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
index 22d7273dcd20..046ad1293521 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/ObservableServiceConnectionTest.java
@@ -145,4 +145,28 @@ public class ObservableServiceConnectionTest extends SysuiTestCase {
connection.unbind();
verify(mContext, never()).unbindService(eq(connection));
}
+
+ @Test
+ public void testUnbind() {
+ ObservableServiceConnection<Foo> connection = new ObservableServiceConnection<>(mContext,
+ mIntent, mExecutor, mTransformer);
+ connection.addCallback(mCallback);
+ connection.onServiceDisconnected(mComponentName);
+
+ // Disconnects before binds should be ignored.
+ verify(mCallback, never()).onDisconnected(eq(connection), anyInt());
+
+ when(mContext.bindService(eq(mIntent), anyInt(), eq(mExecutor), eq(connection)))
+ .thenReturn(true);
+ connection.bind();
+
+ mExecutor.runAllReady();
+
+ connection.unbind();
+
+ mExecutor.runAllReady();
+
+ verify(mCallback).onDisconnected(eq(connection),
+ eq(ObservableServiceConnection.DISCONNECT_REASON_UNBIND));
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
index 53d4a96b0640..db0139c9b0d1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/service/PersistentConnectionManagerTest.java
@@ -16,6 +16,7 @@
package com.android.systemui.util.service;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.testing.AndroidTestingRunner;
@@ -120,6 +121,24 @@ public class PersistentConnectionManagerTest extends SysuiTestCase {
}
/**
+ * Ensures manual unbind does not reconnect.
+ */
+ @Test
+ public void testStopDoesNotReconnect() {
+ mConnectionManager.start();
+ ArgumentCaptor<ObservableServiceConnection.Callback<Proxy>> connectionCallbackCaptor =
+ ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class);
+
+ verify(mConnection).addCallback(connectionCallbackCaptor.capture());
+ verify(mConnection).bind();
+ Mockito.clearInvocations(mConnection);
+ mConnectionManager.stop();
+ mFakeExecutor.advanceClockToNext();
+ mFakeExecutor.runAllReady();
+ verify(mConnection, never()).bind();
+ }
+
+ /**
* Ensures rebind on package change.
*/
@Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 7d4e27fa2580..9866013093cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2018 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -21,12 +21,9 @@ import static android.app.PendingIntent.FLAG_MUTABLE;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL;
import static com.google.common.truth.Truth.assertThat;
@@ -62,7 +59,6 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.hardware.display.AmbientDisplayConfiguration;
-import android.hardware.face.FaceManager;
import android.os.Handler;
import android.os.PowerManager;
import android.os.UserHandle;
@@ -75,6 +71,7 @@ import android.testing.TestableLooper;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
+import android.view.ViewTreeObserver;
import android.view.WindowManager;
import androidx.test.filters.SmallTest;
@@ -88,20 +85,19 @@ import com.android.systemui.dump.DumpManager;
import com.android.systemui.keyguard.KeyguardViewMediator;
import com.android.systemui.model.SysUiState;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.NotificationShadeWindowView;
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationRemoveInterceptor;
import com.android.systemui.statusbar.RankingBuilder;
import com.android.systemui.statusbar.SysuiStatusBarStateController;
import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.NotificationFilter;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
@@ -110,7 +106,6 @@ import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
import com.android.systemui.statusbar.phone.DozeParameters;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
import com.android.systemui.statusbar.phone.ShadeController;
import com.android.systemui.statusbar.policy.BatteryController;
@@ -118,7 +113,6 @@ import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.R;
import com.android.wm.shell.ShellTaskOrganizer;
import com.android.wm.shell.TaskViewTransitions;
import com.android.wm.shell.WindowManagerShellWrapper;
@@ -139,8 +133,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
-
-import com.google.common.collect.ImmutableList;
+import com.android.wm.shell.sysui.ShellController;
import org.junit.Before;
import org.junit.Ignore;
@@ -155,22 +148,17 @@ import java.util.HashMap;
import java.util.List;
import java.util.Optional;
-/**
- * Tests the NotificationEntryManager setup with BubbleController.
- * The {@link NotifPipeline} setup with BubbleController is tested in
- * {@link NewNotifPipelineBubblesTest}.
- */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper(setAsMainLooper = true)
public class BubblesTest extends SysuiTestCase {
@Mock
- private NotificationEntryManager mNotificationEntryManager;
- @Mock
private CommonNotifCollection mCommonNotifCollection;
@Mock
private NotificationGroupManagerLegacy mNotificationGroupManager;
@Mock
+ private BubblesManager.NotifCallback mNotifCallback;
+ @Mock
private WindowManager mWindowManager;
@Mock
private IActivityManager mActivityManager;
@@ -183,8 +171,6 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private ZenModeConfig mZenModeConfig;
@Mock
- private FaceManager mFaceManager;
- @Mock
private NotificationLockscreenUserManager mLockscreenUserManager;
@Mock
private SysuiStatusBarStateController mStatusBarStateController;
@@ -196,15 +182,17 @@ public class BubblesTest extends SysuiTestCase {
private FloatingContentCoordinator mFloatingContentCoordinator;
@Mock
private BubbleDataRepository mDataRepository;
+ @Mock
+ private NotificationShadeWindowView mNotificationShadeWindowView;
+ @Mock
+ private AuthController mAuthController;
private SysUiState mSysUiState;
private boolean mSysUiStateBubblesExpanded;
private boolean mSysUiStateBubblesManageMenuExpanded;
@Captor
- private ArgumentCaptor<NotificationEntryListener> mEntryListenerCaptor;
- @Captor
- private ArgumentCaptor<NotificationRemoveInterceptor> mRemoveInterceptorCaptor;
+ private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
@Captor
private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
@Captor
@@ -212,27 +200,23 @@ public class BubblesTest extends SysuiTestCase {
@Captor
private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-
private BubblesManager mBubblesManager;
- // TODO(178618782): Move tests on the controller directly to the shell
private TestableBubbleController mBubbleController;
private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
- private NotificationEntryListener mEntryListener;
- private NotificationRemoveInterceptor mRemoveInterceptor;
-
+ private NotifCollectionListener mEntryListener;
private NotificationTestHelper mNotificationTestHelper;
private NotificationEntry mRow;
private NotificationEntry mRow2;
- private NotificationEntry mRow3;
private ExpandableNotificationRow mNonBubbleNotifRow;
private BubbleEntry mBubbleEntry;
private BubbleEntry mBubbleEntry2;
- private BubbleEntry mBubbleEntry3;
private BubbleEntry mBubbleEntryUser11;
private BubbleEntry mBubbleEntry2User11;
@Mock
+ private ShellController mShellController;
+ @Mock
private Bubbles.BubbleExpandListener mBubbleExpandListener;
@Mock
private PendingIntent mDeleteIntent;
@@ -245,12 +229,8 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private NotifPipeline mNotifPipeline;
@Mock
- private NotifPipelineFlags mNotifPipelineFlags;
- @Mock
private DumpManager mDumpManager;
@Mock
- private NotificationShadeWindowView mNotificationShadeWindowView;
- @Mock
private IStatusBarService mStatusBarService;
@Mock
private NotificationVisibilityProvider mVisibilityProvider;
@@ -269,8 +249,6 @@ public class BubblesTest extends SysuiTestCase {
@Mock
private ScreenOffAnimationController mScreenOffAnimationController;
@Mock
- private AuthController mAuthController;
- @Mock
private TaskViewTransitions mTaskViewTransitions;
@Mock
private Optional<OneHandedController> mOneHandedOptional;
@@ -290,8 +268,9 @@ public class BubblesTest extends SysuiTestCase {
// For the purposes of this test, just run everything synchronously
ShellExecutor syncExecutor = new SyncExecutor();
- mContext.addMockSystemService(FaceManager.class, mFaceManager);
when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
+ when(mNotificationShadeWindowView.getViewTreeObserver())
+ .thenReturn(mock(ViewTreeObserver.class));
mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
@@ -308,11 +287,9 @@ public class BubblesTest extends SysuiTestCase {
TestableLooper.get(this));
mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
- mRow3 = mNotificationTestHelper.createBubble(mDeleteIntent);
mNonBubbleNotifRow = mNotificationTestHelper.createRow();
mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
- mBubbleEntry3 = BubblesManager.notifToBubbleEntry(mRow3);
UserHandle handle = mock(UserHandle.class);
when(handle.getIdentifier()).thenReturn(11);
@@ -321,9 +298,6 @@ public class BubblesTest extends SysuiTestCase {
mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
mNotificationTestHelper.createBubble(handle));
- // Return non-null notification data from the CommonNotifCollection
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-
mZenModeConfig.suppressedVisualEffects = 0;
when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
@@ -336,7 +310,6 @@ public class BubblesTest extends SysuiTestCase {
(sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
});
- // TODO: Fix
mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
mPositioner.setMaxBubbles(5);
mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
@@ -348,6 +321,7 @@ public class BubblesTest extends SysuiTestCase {
mock(AmbientDisplayConfiguration.class),
mock(NotificationFilter.class),
mock(StatusBarStateController.class),
+ mock(KeyguardStateController.class),
mock(BatteryController.class),
mock(HeadsUpManager.class),
mock(NotificationInterruptLogger.class),
@@ -355,11 +329,10 @@ public class BubblesTest extends SysuiTestCase {
mock(NotifPipelineFlags.class),
mock(KeyguardNotificationVisibilityProvider.class)
);
-
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(false);
when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
mBubbleController = new TestableBubbleController(
mContext,
+ mShellController,
mBubbleData,
mFloatingContentCoordinator,
mDataRepository,
@@ -388,7 +361,6 @@ public class BubblesTest extends SysuiTestCase {
mNotificationShadeWindowController,
mock(KeyguardStateController.class),
mShadeController,
- mConfigurationController,
mStatusBarService,
mock(INotificationManager.class),
mVisibilityProvider,
@@ -396,23 +368,22 @@ public class BubblesTest extends SysuiTestCase {
mZenModeController,
mLockscreenUserManager,
mNotificationGroupManager,
- mNotificationEntryManager,
mCommonNotifCollection,
mNotifPipeline,
mSysUiState,
- mNotifPipelineFlags,
mDumpManager,
syncExecutor);
+ mBubblesManager.addNotifCallback(mNotifCallback);
- // XXX: Does *this* need to be changed?
// Get a reference to the BubbleController's entry listener
- verify(mNotificationEntryManager, atLeastOnce())
- .addNotificationEntryListener(mEntryListenerCaptor.capture());
- mEntryListener = mEntryListenerCaptor.getValue();
- // And the remove interceptor
- verify(mNotificationEntryManager, atLeastOnce())
- .addNotificationRemoveInterceptor(mRemoveInterceptorCaptor.capture());
- mRemoveInterceptor = mRemoveInterceptorCaptor.getValue();
+ verify(mNotifPipeline, atLeastOnce())
+ .addCollectionListener(mNotifListenerCaptor.capture());
+ mEntryListener = mNotifListenerCaptor.getValue();
+ }
+
+ @Test
+ public void instantiateController_registerConfigChangeListener() {
+ verify(mShellController, times(1)).addConfigurationChangeListener(any());
}
@Test
@@ -433,90 +404,75 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testRemoveBubble() {
mBubbleController.updateBubble(mBubbleEntry);
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()));
+ assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertTrue(mBubbleController.hasBubbles());
- verify(mNotificationEntryManager).updateNotifications(any());
+ verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
mBubbleController.removeBubble(
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- verify(mNotificationEntryManager, times(2)).updateNotifications(anyString());
+ verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
@Test
- public void testPromoteBubble_autoExpand() throws Exception {
- mBubbleController.updateBubble(mBubbleEntry2);
+ public void testRemoveBubble_withDismissedNotif_inOverflow() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
-
- Bubble b = mBubbleData.getOverflowBubbleWithKey(mRow.getKey());
- assertThat(mBubbleData.getOverflowBubbles()).isEqualTo(ImmutableList.of(b));
- verify(mNotificationEntryManager, never()).performRemoveNotification(
- eq(mRow.getSbn()), any(), anyInt());
- assertThat(mRow.isBubble()).isFalse();
-
- Bubble b2 = mBubbleData.getBubbleInStackWithKey(mRow2.getKey());
- assertThat(mBubbleData.getSelectedBubble()).isEqualTo(b2);
- mBubbleController.promoteBubbleFromOverflow(b);
+ assertTrue(mBubbleController.hasBubbles());
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- assertThat(b.isBubble()).isTrue();
- assertThat(b.shouldAutoExpand()).isTrue();
- int flags = Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE
- | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION;
- verify(mStatusBarService, times(1)).onNotificationBubbleChanged(
- eq(b.getKey()), eq(true), eq(flags));
- }
+ // Make it look like dismissed notif
+ mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
- @Test
- public void testCancelOverflowBubble() {
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleController.updateBubble(mBubbleEntry, /* suppressFlyout */
- false, /* showInShade */ true);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
+ // Now remove the bubble
mBubbleController.removeBubble(
mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
- mBubbleController.removeBubble(
- mRow.getKey(), DISMISS_NOTIF_CANCEL);
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(mRow.getSbn()), any(), anyInt());
- assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
- assertFalse(mRow.isBubble());
+ // We don't remove the notification since the bubble is still in overflow.
+ verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt());
+ assertFalse(mBubbleController.hasBubbles());
}
@Test
- public void testUserChange_doesNotRemoveNotif() {
+ public void testRemoveBubble_withDismissedNotif_notInOverflow() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
+ when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
+
assertTrue(mBubbleController.hasBubbles());
+ assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
+
+ // Make it look like dismissed notif
+ mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
+ // Now remove the bubble
mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_CHANGED);
- verify(mNotificationEntryManager, never()).performRemoveNotification(
- eq(mRow.getSbn()), any(), anyInt());
+ mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
+ assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
+
+ // Since the notif is dismissed and not in overflow, once the bubble is removed,
+ // removeNotification gets called to really remove the notif
+ verify(mNotifCallback, times(1)).removeNotification(eq(mRow),
+ any(), anyInt());
assertFalse(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- assertTrue(mRow.isBubble());
}
@Test
public void testDismissStack() {
mBubbleController.updateBubble(mBubbleEntry);
- verify(mNotificationEntryManager, times(1)).updateNotifications(any());
+ verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
mBubbleController.updateBubble(mBubbleEntry2);
- verify(mNotificationEntryManager, times(2)).updateNotifications(any());
+ verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
assertTrue(mBubbleController.hasBubbles());
mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- verify(mNotificationEntryManager, times(3)).updateNotifications(any());
+ verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
@@ -528,7 +484,7 @@ public class BubblesTest extends SysuiTestCase {
assertStackCollapsed();
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// We should have bubbles & their notifs should not be suppressed
@@ -536,7 +492,6 @@ public class BubblesTest extends SysuiTestCase {
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
// Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
mBubbleData.setExpanded(true);
assertStackExpanded();
verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
@@ -556,8 +511,8 @@ public class BubblesTest extends SysuiTestCase {
@Ignore("Currently broken.")
public void testCollapseAfterChangingExpandedBubble() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
- mEntryListener.onPendingEntryAdded(mRow2);
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.updateBubble(mBubbleEntry2);
@@ -593,6 +548,7 @@ public class BubblesTest extends SysuiTestCase {
verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
true, mRow.getKey());
+
// Collapse
mBubbleController.collapseStack();
assertStackCollapsed();
@@ -602,7 +558,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testExpansionRemovesShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// We should have bubbles & their notifs should not be suppressed
@@ -627,7 +583,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// We should have bubbles & their notifs should not be suppressed
@@ -649,7 +605,7 @@ public class BubblesTest extends SysuiTestCase {
assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
// Send update
- mEntryListener.onPreEntryUpdated(mRow);
+ mEntryListener.onEntryUpdated(mRow);
// Nothing should have changed
// Notif is suppressed after expansion
@@ -661,8 +617,8 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testRemoveLastExpanded_collapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
- mEntryListener.onPendingEntryAdded(mRow2);
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
mBubbleController.updateBubble(mBubbleEntry);
mBubbleController.updateBubble(mBubbleEntry2);
@@ -707,7 +663,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testRemoveLastExpandedEmptyOverflow_collapses() {
// Mark it as a bubble and add it explicitly
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Expand
@@ -732,6 +688,7 @@ public class BubblesTest extends SysuiTestCase {
assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
}
+
@Test
public void testAutoExpand_fails_noFlag() {
assertStackCollapsed();
@@ -739,7 +696,7 @@ public class BubblesTest extends SysuiTestCase {
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Expansion shouldn't change
@@ -755,7 +712,7 @@ public class BubblesTest extends SysuiTestCase {
Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
// Add the auto expand bubble
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Expansion should change
@@ -771,7 +728,7 @@ public class BubblesTest extends SysuiTestCase {
Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
// Add the suppress notif bubble
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
// Notif should be suppressed because we were foreground
@@ -805,22 +762,8 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void testExpandStackAndSelectBubble_removedFirst() {
- mEntryListener.onPendingEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Simulate notification cancellation.
- mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_APP_CANCEL);
-
- mBubbleController.expandStackAndSelectBubble(mBubbleEntry);
-
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
mTestableLooper.processAllMessages();
@@ -829,8 +772,8 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testAddNotif_notBubble() {
- mEntryListener.onPendingEntryAdded(mNonBubbleNotifRow.getEntry());
- mEntryListener.onPreEntryUpdated(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
+ mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
assertThat(mBubbleController.hasBubbles()).isFalse();
}
@@ -868,48 +811,33 @@ public class BubblesTest extends SysuiTestCase {
NotificationListenerService.Ranking ranking = new RankingBuilder(
mRow.getRanking()).setCanBubble(false).build();
mRow.setRanking(ranking);
- mEntryListener.onPreEntryUpdated(mRow);
+ mEntryListener.onEntryUpdated(mRow);
assertFalse(mBubbleController.hasBubbles());
verify(mDeleteIntent, never()).send();
}
@Test
- public void testRemoveBubble_succeeds_appCancel() {
- mEntryListener.onPendingEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
-
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_APP_CANCEL);
-
- // Cancels always remove so no need to intercept
- assertFalse(intercepted);
- }
-
- @Test
public void testRemoveBubble_entryListenerRemove() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
// Removes the notification
- mEntryListener.onEntryRemoved(mRow, null, false, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
assertFalse(mBubbleController.hasBubbles());
}
@Test
- public void removeBubble_clearAllIntercepted() {
- mEntryListener.onPendingEntryAdded(mRow);
+ public void removeBubble_intercepted() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL_ALL);
+ boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
// Intercept!
assertTrue(intercepted);
@@ -918,99 +846,51 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void removeBubble_userDismissNotifIntercepted() {
- mEntryListener.onPendingEntryAdded(mRow);
+ public void removeBubble_dismissIntoOverflow_intercepted() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
-
- // Intercept!
- assertTrue(intercepted);
- // Should update show in shade state
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- }
-
- @Test
- public void removeNotif_inOverflow_intercepted() {
- // Get bubble with notif in shade.
- mEntryListener.onPendingEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Dismiss the bubble into overflow.
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
+ // Dismiss the bubble
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
assertFalse(mBubbleController.hasBubbles());
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
+ // Dismiss the notification
+ boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
- // Notif is no longer a bubble, but still in overflow, so we intercept removal.
+ // Intercept dismissal since bubble is going into overflow
assertTrue(intercepted);
}
@Test
- public void removeNotif_notInOverflow_notIntercepted() {
- // Get bubble with notif in shade.
- mEntryListener.onPendingEntryAdded(mRow);
+ public void removeBubble_notIntercepted() {
+ mEntryListener.onEntryAdded(mRow);
mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_NO_LONGER_BUBBLE);
+ // Dismiss the bubble
+ mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
assertFalse(mBubbleController.hasBubbles());
- boolean intercepted = mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
+ // Dismiss the notification
+ boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
- // Notif is no longer a bubble, so we should not intercept removal.
+ // Not a bubble anymore so we don't intercept dismissal.
assertFalse(intercepted);
}
@Test
- public void testOverflowBubble_maxReached_notInShade_bubbleRemoved() {
- mBubbleController.updateBubble(
- mBubbleEntry, /* suppressFlyout */ false, /* showInShade */ false);
- mBubbleController.updateBubble(
- mBubbleEntry2, /* suppressFlyout */ false, /* showInShade */ false);
- mBubbleController.updateBubble(
- mBubbleEntry3, /* suppressFlyout */ false, /* showInShade */ false);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
- when(mCommonNotifCollection.getEntry(mRow3.getKey())).thenReturn(mRow3);
- assertEquals(mBubbleData.getBubbles().size(), 3);
-
- mBubbleData.setMaxOverflowBubbles(1);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertEquals(mBubbleData.getBubbles().size(), 2);
- assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
-
- mBubbleController.removeBubble(
- mRow2.getKey(), Bubbles.DISMISS_USER_GESTURE);
- // Overflow max of 1 is reached; mRow is oldest, so it gets removed
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(mRow.getSbn()), any(), eq(REASON_CANCEL));
- assertEquals(mBubbleData.getBubbles().size(), 1);
- assertEquals(mBubbleData.getOverflowBubbles().size(), 1);
- }
-
- @Test
public void testNotifyShadeSuppressionChange_notificationDismiss() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- mRemoveInterceptor.onNotificationRemoveRequested(
- mRow.getKey(), mRow, REASON_CANCEL);
+ mBubblesManager.handleDismissalInterception(mRow);
// Should update show in shade state
assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
@@ -1022,7 +902,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testNotifyShadeSuppressionChange_bubbleExpanded() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow);
assertTrue(mBubbleController.hasBubbles());
assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
@@ -1042,9 +922,9 @@ public class BubblesTest extends SysuiTestCase {
// GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+ mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
- mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -1054,10 +934,10 @@ public class BubblesTest extends SysuiTestCase {
// THEN the summary and bubbled child are suppressed from the shade
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
groupedBubble.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
+ groupedBubble.getEntry().getSbn().getGroupKey()));
assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
groupedBubble.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
+ groupedBubble.getEntry().getSbn().getGroupKey()));
assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
}
@@ -1066,7 +946,7 @@ public class BubblesTest extends SysuiTestCase {
// GIVEN a group summary with a bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
+ mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
@@ -1076,7 +956,7 @@ public class BubblesTest extends SysuiTestCase {
mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
// WHEN the summary is cancelled by the app
- mEntryListener.onEntryRemoved(groupSummary.getEntry(), null, false, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
// THEN the summary and its children are removed from bubble data
assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
@@ -1085,14 +965,14 @@ public class BubblesTest extends SysuiTestCase {
}
@Test
- public void testSummaryDismissal_marksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
+ public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
throws Exception {
// GIVEN a group summary with two (non-bubble) children and one bubble child
ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
+ mEntryListener.onEntryAdded(groupedBubble.getEntry());
when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
.thenReturn(groupedBubble.getEntry());
- mEntryListener.onPendingEntryAdded(groupedBubble.getEntry());
groupSummary.addChildNotification(groupedBubble);
// WHEN the summary is dismissed
@@ -1100,16 +980,15 @@ public class BubblesTest extends SysuiTestCase {
// THEN only the NON-bubble children are dismissed
List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(childrenRows.get(0).getEntry().getSbn()), any(),
- eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotificationEntryManager, times(1)).performRemoveNotification(
- eq(childrenRows.get(1).getEntry().getSbn()), any(),
- eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotificationEntryManager, never()).performRemoveNotification(
- eq(groupedBubble.getEntry().getSbn()), any(), anyInt());
-
- // THEN the bubble child is suppressed from the shade
+ verify(mNotifCallback, times(1)).removeNotification(
+ eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+ verify(mNotifCallback, times(1)).removeNotification(
+ eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
+ verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()),
+ any(), anyInt());
+
+ // THEN the bubble child still exists as a bubble and is suppressed from the shade
+ assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
groupedBubble.getEntry().getKey(),
groupedBubble.getEntry().getSbn().getGroupKey()));
@@ -1117,34 +996,17 @@ public class BubblesTest extends SysuiTestCase {
groupedBubble.getEntry().getKey(),
groupedBubble.getEntry().getSbn().getGroupKey()));
- // THEN the summary is removed from GroupManager
- verify(mNotificationGroupManager, times(1)).onEntryRemoved(groupSummary.getEntry());
+ // THEN the summary is also suppressed from the shade
+ assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
+ groupSummary.getEntry().getKey(),
+ groupSummary.getEntry().getSbn().getGroupKey()));
+ assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
+ groupSummary.getEntry().getKey(),
+ groupSummary.getEntry().getSbn().getGroupKey()));
}
/**
- * Verifies that when a non visually interruptive update occurs for a bubble in the overflow,
- * the that bubble does not get promoted from the overflow.
- */
- @Test
- public void test_notVisuallyInterruptive_updateOverflowBubble_notAdded() {
- // Setup
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- assertTrue(mBubbleController.hasBubbles());
-
- // Overflow it
- mBubbleData.dismissBubbleWithKey(mRow.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse();
- assertThat(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey())).isTrue();
-
- // Test
- mBubbleController.updateBubble(mBubbleEntry);
- assertThat(mBubbleData.hasBubbleInStackWithKey(mRow.getKey())).isFalse();
- }
-
- /**
* Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
* test the loading from the repository which would be a nice thing to add.
*/
@@ -1185,15 +1047,17 @@ public class BubblesTest extends SysuiTestCase {
*/
@Test
public void testOverflowLoadedOnce() {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
+ // XXX
+ when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
+ when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
+
+ mEntryListener.onEntryAdded(mRow);
+ mEntryListener.onEntryAdded(mRow2);
mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbles().isEmpty()).isFalse();
+ assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleController.removeBubble(mBubbleEntry.getKey(), DISMISS_NOTIF_CANCEL);
- mBubbleController.removeBubble(mBubbleEntry2.getKey(), DISMISS_NOTIF_CANCEL);
+ mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
+ mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
@@ -1376,6 +1240,7 @@ public class BubblesTest extends SysuiTestCase {
assertStackCollapsed();
}
+
@Test
public void testRegisterUnregisterBroadcastListener() {
spyOn(mContext);
@@ -1455,7 +1320,7 @@ public class BubblesTest extends SysuiTestCase {
@Test
public void testSetShouldAutoExpand_notifiesFlagChanged() {
- mEntryListener.onPendingEntryAdded(mRow);
+ mBubbleController.updateBubble(mBubbleEntry);
assertTrue(mBubbleController.hasBubbles());
Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
@@ -1551,7 +1416,7 @@ public class BubblesTest extends SysuiTestCase {
}
/**
- * Sets the bubble metadata flags for this entry. These ]flags are normally set by
+ * Sets the bubble metadata flags for this entry. These flags are normally set by
* NotificationManagerService when the notification is sent, however, these tests do not
* go through that path so we set them explicitly when testing.
*/
@@ -1570,12 +1435,15 @@ public class BubblesTest extends SysuiTestCase {
private Notification.BubbleMetadata getMetadata() {
Intent target = new Intent(mContext, BubblesTestActivity.class);
PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, FLAG_MUTABLE);
-
- return new Notification.BubbleMetadata.Builder(bubbleIntent,
- Icon.createWithResource(mContext, R.drawable.bubble_ic_create_bubble))
+ return new Notification.BubbleMetadata.Builder(
+ bubbleIntent,
+ Icon.createWithResource(
+ mContext,
+ com.android.wm.shell.R.drawable.bubble_ic_create_bubble))
.build();
}
+
/**
* Asserts that the bubble stack is expanded and also validates the cached state is updated.
*/
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
deleted file mode 100644
index a6327b9daed5..000000000000
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java
+++ /dev/null
@@ -1,1401 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.wmshell;
-
-import static android.app.Notification.FLAG_BUBBLE;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
-import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
-
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
-
-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.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.app.IActivityManager;
-import android.app.INotificationManager;
-import android.app.Notification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.LauncherApps;
-import android.content.pm.UserInfo;
-import android.hardware.display.AmbientDisplayConfiguration;
-import android.os.Handler;
-import android.os.PowerManager;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.service.dreams.IDreamManager;
-import android.service.notification.NotificationListenerService;
-import android.service.notification.ZenModeConfig;
-import android.testing.AndroidTestingRunner;
-import android.testing.TestableLooper;
-import android.util.Pair;
-import android.util.SparseArray;
-import android.view.View;
-import android.view.WindowManager;
-
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.colorextraction.ColorExtractor;
-import com.android.internal.statusbar.IStatusBarService;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.biometrics.AuthController;
-import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.model.SysUiState;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shared.system.QuickStepContract;
-import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.RankingBuilder;
-import com.android.systemui.statusbar.SysuiStatusBarStateController;
-import com.android.systemui.statusbar.notification.NotifPipelineFlags;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
-import com.android.systemui.statusbar.notification.NotificationFilter;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
-import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy;
-import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
-import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.KeyguardNotificationVisibilityProvider;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptLogger;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
-import com.android.systemui.statusbar.phone.DozeParameters;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowControllerImpl;
-import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
-import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
-import com.android.systemui.statusbar.phone.ShadeController;
-import com.android.systemui.statusbar.policy.BatteryController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.statusbar.policy.ZenModeController;
-import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.TaskViewTransitions;
-import com.android.wm.shell.WindowManagerShellWrapper;
-import com.android.wm.shell.bubbles.Bubble;
-import com.android.wm.shell.bubbles.BubbleData;
-import com.android.wm.shell.bubbles.BubbleDataRepository;
-import com.android.wm.shell.bubbles.BubbleEntry;
-import com.android.wm.shell.bubbles.BubbleLogger;
-import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.bubbles.Bubbles;
-import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.draganddrop.DragAndDropController;
-import com.android.wm.shell.onehanded.OneHandedController;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Optional;
-
-/**
- * Tests the NotifPipeline setup with BubbleController.
- * The NotificationEntryManager setup with BubbleController is tested in
- * {@link BubblesTest}.
- */
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-public class NewNotifPipelineBubblesTest extends SysuiTestCase {
- @Mock
- private NotificationEntryManager mNotificationEntryManager;
- @Mock
- private CommonNotifCollection mCommonNotifCollection;
- @Mock
- private NotificationGroupManagerLegacy mNotificationGroupManager;
- @Mock
- private BubblesManager.NotifCallback mNotifCallback;
- @Mock
- private WindowManager mWindowManager;
- @Mock
- private IActivityManager mActivityManager;
- @Mock
- private DozeParameters mDozeParameters;
- @Mock
- private ConfigurationController mConfigurationController;
- @Mock
- private ZenModeController mZenModeController;
- @Mock
- private ZenModeConfig mZenModeConfig;
- @Mock
- private NotificationLockscreenUserManager mLockscreenUserManager;
- @Mock
- private SysuiStatusBarStateController mStatusBarStateController;
- @Mock
- private KeyguardViewMediator mKeyguardViewMediator;
- @Mock
- private KeyguardBypassController mKeyguardBypassController;
- @Mock
- private FloatingContentCoordinator mFloatingContentCoordinator;
- @Mock
- private BubbleDataRepository mDataRepository;
- @Mock
- private NotificationShadeWindowView mNotificationShadeWindowView;
- @Mock
- private AuthController mAuthController;
-
- private SysUiState mSysUiState;
- private boolean mSysUiStateBubblesExpanded;
- private boolean mSysUiStateBubblesManageMenuExpanded;
-
- @Captor
- private ArgumentCaptor<NotifCollectionListener> mNotifListenerCaptor;
- @Captor
- private ArgumentCaptor<List<Bubble>> mBubbleListCaptor;
- @Captor
- private ArgumentCaptor<IntentFilter> mFilterArgumentCaptor;
- @Captor
- private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-
- private BubblesManager mBubblesManager;
- private TestableBubbleController mBubbleController;
- private NotificationShadeWindowControllerImpl mNotificationShadeWindowController;
- private NotifCollectionListener mEntryListener;
- private NotificationTestHelper mNotificationTestHelper;
- private NotificationEntry mRow;
- private NotificationEntry mRow2;
- private ExpandableNotificationRow mNonBubbleNotifRow;
- private BubbleEntry mBubbleEntry;
- private BubbleEntry mBubbleEntry2;
-
- private BubbleEntry mBubbleEntryUser11;
- private BubbleEntry mBubbleEntry2User11;
-
- @Mock
- private Bubbles.BubbleExpandListener mBubbleExpandListener;
- @Mock
- private PendingIntent mDeleteIntent;
- @Mock
- private SysuiColorExtractor mColorExtractor;
- @Mock
- ColorExtractor.GradientColors mGradientColors;
- @Mock
- private ShadeController mShadeController;
- @Mock
- private NotifPipeline mNotifPipeline;
- @Mock
- private NotifPipelineFlags mNotifPipelineFlags;
- @Mock
- private DumpManager mDumpManager;
- @Mock
- private IStatusBarService mStatusBarService;
- @Mock
- private NotificationVisibilityProvider mVisibilityProvider;
- @Mock
- private LauncherApps mLauncherApps;
- @Mock
- private WindowManagerShellWrapper mWindowManagerShellWrapper;
- @Mock
- private BubbleLogger mBubbleLogger;
- @Mock
- private TaskStackListenerImpl mTaskStackListener;
- @Mock
- private ShellTaskOrganizer mShellTaskOrganizer;
- @Mock
- private KeyguardStateController mKeyguardStateController;
- @Mock
- private ScreenOffAnimationController mScreenOffAnimationController;
- @Mock
- private TaskViewTransitions mTaskViewTransitions;
- @Mock
- private Optional<OneHandedController> mOneHandedOptional;
-
- private TestableBubblePositioner mPositioner;
-
- private BubbleData mBubbleData;
-
- private TestableLooper mTestableLooper;
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mTestableLooper = TestableLooper.get(this);
-
- // For the purposes of this test, just run everything synchronously
- ShellExecutor syncExecutor = new SyncExecutor();
-
- when(mColorExtractor.getNeutralColors()).thenReturn(mGradientColors);
-
- mNotificationShadeWindowController = new NotificationShadeWindowControllerImpl(mContext,
- mWindowManager, mActivityManager, mDozeParameters, mStatusBarStateController,
- mConfigurationController, mKeyguardViewMediator, mKeyguardBypassController,
- mColorExtractor, mDumpManager, mKeyguardStateController,
- mScreenOffAnimationController, mAuthController);
- mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView);
- mNotificationShadeWindowController.attach();
-
- // Need notifications for bubbles
- mNotificationTestHelper = new NotificationTestHelper(
- mContext,
- mDependency,
- TestableLooper.get(this));
- mRow = mNotificationTestHelper.createBubble(mDeleteIntent);
- mRow2 = mNotificationTestHelper.createBubble(mDeleteIntent);
- mNonBubbleNotifRow = mNotificationTestHelper.createRow();
- mBubbleEntry = BubblesManager.notifToBubbleEntry(mRow);
- mBubbleEntry2 = BubblesManager.notifToBubbleEntry(mRow2);
-
- UserHandle handle = mock(UserHandle.class);
- when(handle.getIdentifier()).thenReturn(11);
- mBubbleEntryUser11 = BubblesManager.notifToBubbleEntry(
- mNotificationTestHelper.createBubble(handle));
- mBubbleEntry2User11 = BubblesManager.notifToBubbleEntry(
- mNotificationTestHelper.createBubble(handle));
-
- mZenModeConfig.suppressedVisualEffects = 0;
- when(mZenModeController.getConfig()).thenReturn(mZenModeConfig);
-
- mSysUiState = new SysUiState();
- mSysUiState.addCallback(sysUiFlags -> {
- mSysUiStateBubblesManageMenuExpanded =
- (sysUiFlags
- & QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
- mSysUiStateBubblesExpanded =
- (sysUiFlags & QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED) != 0;
- });
-
- mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
- mPositioner.setMaxBubbles(5);
- mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
-
- TestableNotificationInterruptStateProviderImpl interruptionStateProvider =
- new TestableNotificationInterruptStateProviderImpl(mContext.getContentResolver(),
- mock(PowerManager.class),
- mock(IDreamManager.class),
- mock(AmbientDisplayConfiguration.class),
- mock(NotificationFilter.class),
- mock(StatusBarStateController.class),
- mock(BatteryController.class),
- mock(HeadsUpManager.class),
- mock(NotificationInterruptLogger.class),
- mock(Handler.class),
- mock(NotifPipelineFlags.class),
- mock(KeyguardNotificationVisibilityProvider.class)
- );
- when(mNotifPipelineFlags.isNewPipelineEnabled()).thenReturn(true);
- when(mShellTaskOrganizer.getExecutor()).thenReturn(syncExecutor);
- mBubbleController = new TestableBubbleController(
- mContext,
- mBubbleData,
- mFloatingContentCoordinator,
- mDataRepository,
- mStatusBarService,
- mWindowManager,
- mWindowManagerShellWrapper,
- mock(UserManager.class),
- mLauncherApps,
- mBubbleLogger,
- mTaskStackListener,
- mShellTaskOrganizer,
- mPositioner,
- mock(DisplayController.class),
- mOneHandedOptional,
- mock(DragAndDropController.class),
- syncExecutor,
- mock(Handler.class),
- mTaskViewTransitions,
- mock(SyncTransactionQueue.class));
- mBubbleController.setExpandListener(mBubbleExpandListener);
- spyOn(mBubbleController);
-
- mBubblesManager = new BubblesManager(
- mContext,
- mBubbleController.asBubbles(),
- mNotificationShadeWindowController,
- mock(KeyguardStateController.class),
- mShadeController,
- mConfigurationController,
- mStatusBarService,
- mock(INotificationManager.class),
- mVisibilityProvider,
- interruptionStateProvider,
- mZenModeController,
- mLockscreenUserManager,
- mNotificationGroupManager,
- mNotificationEntryManager,
- mCommonNotifCollection,
- mNotifPipeline,
- mSysUiState,
- mNotifPipelineFlags,
- mDumpManager,
- syncExecutor);
- mBubblesManager.addNotifCallback(mNotifCallback);
-
- // Get a reference to the BubbleController's entry listener
- verify(mNotifPipeline, atLeastOnce())
- .addCollectionListener(mNotifListenerCaptor.capture());
- mEntryListener = mNotifListenerCaptor.getValue();
- }
-
- @Test
- public void testAddBubble() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testHasBubbles() {
- assertFalse(mBubbleController.hasBubbles());
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testRemoveBubble() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- assertTrue(mBubbleController.hasBubbles());
- verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
-
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
-
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testRemoveBubble_withDismissedNotif_inOverflow() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Make it look like dismissed notif
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
-
- // Now remove the bubble
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertTrue(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
-
- // We don't remove the notification since the bubble is still in overflow.
- verify(mNotifCallback, never()).removeNotification(eq(mRow), any(), anyInt());
- assertFalse(mBubbleController.hasBubbles());
- }
-
- @Test
- public void testRemoveBubble_withDismissedNotif_notInOverflow() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Make it look like dismissed notif
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()).setSuppressNotification(true);
-
- // Now remove the bubble
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
- assertFalse(mBubbleData.hasOverflowBubbleWithKey(mRow.getKey()));
-
- // Since the notif is dismissed and not in overflow, once the bubble is removed,
- // removeNotification gets called to really remove the notif
- verify(mNotifCallback, times(1)).removeNotification(eq(mRow),
- any(), anyInt());
- assertFalse(mBubbleController.hasBubbles());
- }
-
- @Test
- public void testDismissStack() {
- mBubbleController.updateBubble(mBubbleEntry);
- verify(mNotifCallback, times(1)).invalidateNotifications(anyString());
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- mBubbleController.updateBubble(mBubbleEntry2);
- verify(mNotifCallback, times(2)).invalidateNotifications(anyString());
- assertNotNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
- assertTrue(mBubbleController.hasBubbles());
-
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- verify(mNotifCallback, times(3)).invalidateNotifications(anyString());
- assertNull(mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- assertNull(mBubbleData.getBubbleInStackWithKey(mRow2.getKey()));
-
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testExpandCollapseStack() {
- assertStackCollapsed();
-
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Expand the stack
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Make sure the notif is suppressed
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // Collapse
- mBubbleController.collapseStack();
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
- assertStackCollapsed();
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- @Ignore("Currently broken.")
- public void testCollapseAfterChangingExpandedBubble() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mEntryListener.onEntryAdded(mRow2);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry2);
-
- // Expand
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
- true, mRow2.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Last added is the one that is expanded
- assertEquals(mRow2.getKey(), mBubbleData.getSelectedBubble().getKey());
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry2);
-
- // Switch which bubble is expanded
- mBubbleData.setSelectedBubble(mBubbleData.getBubbleInStackWithKey(
- mRow.getKey()));
- mBubbleData.setExpanded(true);
- assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey());
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // collapse for previous bubble
- verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
- false, mRow2.getKey());
- // expand for selected bubble
- verify(mBubbleExpandListener, atLeastOnce()).onBubbleExpandChanged(
- true, mRow.getKey());
-
-
- // Collapse
- mBubbleController.collapseStack();
- assertStackCollapsed();
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testExpansionRemovesShowInShadeAndDot() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mTestableLooper.processAllMessages();
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Expand
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Notif is suppressed after expansion
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Notif shouldn't show dot after expansion
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- }
-
- @Test
- public void testUpdateWhileExpanded_DoesntChangeShowInShadeAndDot() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // We should have bubbles & their notifs should not be suppressed
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mTestableLooper.processAllMessages();
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Expand
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Notif is suppressed after expansion
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Notif shouldn't show dot after expansion
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Send update
- mEntryListener.onEntryUpdated(mRow);
-
- // Nothing should have changed
- // Notif is suppressed after expansion
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Notif shouldn't show dot after expansion
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- }
-
- @Test
- public void testRemoveLastExpanded_collapses() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mEntryListener.onEntryAdded(mRow2);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
-
- // Expand
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
-
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow2.getKey());
-
- // Last added is the one that is expanded
- assertEquals(mRow2.getKey(), mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey());
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry2);
-
- // Dismiss currently expanded
- mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow2.getKey());
-
- // Make sure first bubble is selected
- assertEquals(mRow.getKey(), mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey());
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- // Dismiss that one
- mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey(),
- Bubbles.DISMISS_USER_GESTURE);
-
- // We should be collapsed
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
- assertFalse(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testRemoveLastExpandedEmptyOverflow_collapses() {
- // Mark it as a bubble and add it explicitly
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expand
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
-
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- assertStackExpanded();
- verify(mBubbleExpandListener).onBubbleExpandChanged(true, mRow.getKey());
-
- // Block the bubble so it won't be in the overflow
- mBubbleController.removeBubble(
- mBubbleData.getBubbleInStackWithKey(
- stackView.getExpandedBubble().getKey()).getKey(),
- Bubbles.DISMISS_BLOCKED);
-
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
-
- // We should be collapsed
- verify(mBubbleExpandListener).onBubbleExpandChanged(false, mRow.getKey());
- assertFalse(mBubbleController.hasBubbles());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
-
- @Test
- public void testAutoExpand_fails_noFlag() {
- assertStackCollapsed();
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, false /* enableFlag */);
-
- // Add the auto expand bubble
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expansion shouldn't change
- verify(mBubbleExpandListener, never()).onBubbleExpandChanged(false /* expanded */,
- mRow.getKey());
- assertStackCollapsed();
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testAutoExpand_succeeds_withFlag() {
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE, true /* enableFlag */);
-
- // Add the auto expand bubble
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expansion should change
- verify(mBubbleExpandListener).onBubbleExpandChanged(true /* expanded */,
- mRow.getKey());
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testSuppressNotif_onInitialNotif() {
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
-
- // Add the suppress notif bubble
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Notif should be suppressed because we were foreground
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Dot + flyout is hidden because notif is suppressed
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testSuppressNotif_onUpdateNotif() {
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Should not be suppressed
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
- // Should show dot
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
-
- // Update to suppress notif
- setMetadataFlags(mRow,
- Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION, true /* enableFlag */);
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Notif should be suppressed
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- // Dot + flyout is hidden because notif is suppressed
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- assertFalse(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showFlyout());
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testMarkNewNotificationAsShowInShade() {
- mEntryListener.onEntryAdded(mRow);
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mTestableLooper.processAllMessages();
- assertTrue(mBubbleData.getBubbleInStackWithKey(mRow.getKey()).showDot());
- }
-
- @Test
- public void testAddNotif_notBubble() {
- mEntryListener.onEntryAdded(mNonBubbleNotifRow.getEntry());
- mEntryListener.onEntryUpdated(mNonBubbleNotifRow.getEntry());
-
- assertThat(mBubbleController.hasBubbles()).isFalse();
- }
-
- @Test
- public void testDeleteIntent_removeBubble_aged() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_AGED);
- verify(mDeleteIntent, never()).send();
- }
-
- @Test
- public void testDeleteIntent_removeBubble_user() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.removeBubble(
- mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- verify(mDeleteIntent, times(1)).send();
- }
-
- @Test
- public void testDeleteIntent_dismissStack() throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- verify(mDeleteIntent, times(2)).send();
- }
-
- @Test
- public void testRemoveBubble_noLongerBubbleAfterUpdate()
- throws PendingIntent.CanceledException {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- mRow.getSbn().getNotification().flags &= ~FLAG_BUBBLE;
- NotificationListenerService.Ranking ranking = new RankingBuilder(
- mRow.getRanking()).setCanBubble(false).build();
- mRow.setRanking(ranking);
- mEntryListener.onEntryUpdated(mRow);
-
- assertFalse(mBubbleController.hasBubbles());
- verify(mDeleteIntent, never()).send();
- }
-
- @Test
- public void testRemoveBubble_entryListenerRemove() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
-
- // Removes the notification
- mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
- assertFalse(mBubbleController.hasBubbles());
- }
-
- @Test
- public void removeBubble_intercepted() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
- // Intercept!
- assertTrue(intercepted);
- // Should update show in shade state
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
- }
-
- @Test
- public void removeBubble_dismissIntoOverflow_intercepted() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Dismiss the bubble
- mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_USER_GESTURE);
- assertFalse(mBubbleController.hasBubbles());
-
- // Dismiss the notification
- boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
- // Intercept dismissal since bubble is going into overflow
- assertTrue(intercepted);
- }
-
- @Test
- public void removeBubble_notIntercepted() {
- mEntryListener.onEntryAdded(mRow);
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- // Dismiss the bubble
- mBubbleController.removeBubble(mRow.getKey(), Bubbles.DISMISS_NOTIF_CANCEL);
- assertFalse(mBubbleController.hasBubbles());
-
- // Dismiss the notification
- boolean intercepted = mBubblesManager.handleDismissalInterception(mRow);
-
- // Not a bubble anymore so we don't intercept dismissal.
- assertFalse(intercepted);
- }
-
- @Test
- public void testNotifyShadeSuppressionChange_notificationDismiss() {
- mEntryListener.onEntryAdded(mRow);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mBubblesManager.handleDismissalInterception(mRow);
-
- // Should update show in shade state
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleMetadataFlagChanged(
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- }
-
- @Test
- public void testNotifyShadeSuppressionChange_bubbleExpanded() {
- mEntryListener.onEntryAdded(mRow);
-
- assertTrue(mBubbleController.hasBubbles());
- assertBubbleNotificationNotSuppressedFromShade(mBubbleEntry);
-
- mBubbleData.setExpanded(true);
-
- // Once a bubble is expanded the notif is suppressed
- assertBubbleNotificationSuppressedFromShade(mBubbleEntry);
-
- // Should notify delegate that shade state changed
- verify(mBubbleController).onBubbleMetadataFlagChanged(
- mBubbleData.getBubbleInStackWithKey(mRow.getKey()));
- }
-
- @Test
- public void testBubbleSummaryDismissal_suppressesSummaryAndBubbleFromShade() throws Exception {
- // GIVEN a group summary with a bubble child
- ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
- ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onEntryAdded(groupedBubble.getEntry());
- when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
- .thenReturn(groupedBubble.getEntry());
- groupSummary.addChildNotification(groupedBubble);
- assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-
- // WHEN the summary is dismissed
- mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
- // THEN the summary and bubbled child are suppressed from the shade
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleData.isSummarySuppressed(groupSummary.getEntry().getSbn().getGroupKey()));
- }
-
- @Test
- public void testAppRemovesSummary_removesAllBubbleChildren() throws Exception {
- // GIVEN a group summary with a bubble child
- ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(0);
- ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onEntryAdded(groupedBubble.getEntry());
- when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
- .thenReturn(groupedBubble.getEntry());
- groupSummary.addChildNotification(groupedBubble);
- assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
-
- // GIVEN the summary is dismissed
- mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
- // WHEN the summary is cancelled by the app
- mEntryListener.onEntryRemoved(groupSummary.getEntry(), REASON_APP_CANCEL);
-
- // THEN the summary and its children are removed from bubble data
- assertFalse(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
- assertFalse(mBubbleData.isSummarySuppressed(
- groupSummary.getEntry().getSbn().getGroupKey()));
- }
-
- @Test
- public void testSummaryDismissalMarksBubblesHiddenFromShadeAndDismissesNonBubbledChildren()
- throws Exception {
- // GIVEN a group summary with two (non-bubble) children and one bubble child
- ExpandableNotificationRow groupSummary = mNotificationTestHelper.createGroup(2);
- ExpandableNotificationRow groupedBubble = mNotificationTestHelper.createBubbleInGroup();
- mEntryListener.onEntryAdded(groupedBubble.getEntry());
- when(mCommonNotifCollection.getEntry(groupedBubble.getEntry().getKey()))
- .thenReturn(groupedBubble.getEntry());
- groupSummary.addChildNotification(groupedBubble);
-
- // WHEN the summary is dismissed
- mBubblesManager.handleDismissalInterception(groupSummary.getEntry());
-
- // THEN only the NON-bubble children are dismissed
- List<ExpandableNotificationRow> childrenRows = groupSummary.getAttachedChildren();
- verify(mNotifCallback, times(1)).removeNotification(
- eq(childrenRows.get(0).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotifCallback, times(1)).removeNotification(
- eq(childrenRows.get(1).getEntry()), any(), eq(REASON_GROUP_SUMMARY_CANCELED));
- verify(mNotifCallback, never()).removeNotification(eq(groupedBubble.getEntry()),
- any(), anyInt());
-
- // THEN the bubble child still exists as a bubble and is suppressed from the shade
- assertTrue(mBubbleData.hasBubbleInStackWithKey(groupedBubble.getEntry().getKey()));
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- groupedBubble.getEntry().getKey(),
- groupedBubble.getEntry().getSbn().getGroupKey()));
-
- // THEN the summary is also suppressed from the shade
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- groupSummary.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- groupSummary.getEntry().getKey(),
- groupSummary.getEntry().getSbn().getGroupKey()));
- }
-
-
- /**
- * Verifies that when the user changes, the bubbles in the overflow list is cleared. Doesn't
- * test the loading from the repository which would be a nice thing to add.
- */
- @Test
- public void testOnUserChanged_overflowState() {
- int firstUserId = mBubbleEntry.getStatusBarNotification().getUser().getIdentifier();
- int secondUserId = mBubbleEntryUser11.getStatusBarNotification().getUser().getIdentifier();
-
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleController.updateBubble(mBubbleEntry2);
- assertTrue(mBubbleController.hasBubbles());
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-
- // Verify these are in the overflow
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry.getKey())).isNotNull();
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2.getKey())).isNotNull();
-
- // Switch users
- mBubbleController.onUserChanged(secondUserId);
- assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-
- // Give this user some bubbles
- mBubbleController.updateBubble(mBubbleEntryUser11);
- mBubbleController.updateBubble(mBubbleEntry2User11);
- assertTrue(mBubbleController.hasBubbles());
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
-
- // Verify these are in the overflow
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntryUser11.getKey())).isNotNull();
- assertThat(mBubbleData.getOverflowBubbleWithKey(mBubbleEntry2User11.getKey())).isNotNull();
-
- // Would have loaded bubbles twice because of user switch
- verify(mDataRepository, times(2)).loadBubbles(anyInt(), any());
- }
-
- /**
- * Verifies we only load the overflow data once.
- */
- @Test
- public void testOverflowLoadedOnce() {
- // XXX
- when(mCommonNotifCollection.getEntry(mRow.getKey())).thenReturn(mRow);
- when(mCommonNotifCollection.getEntry(mRow2.getKey())).thenReturn(mRow2);
-
- mEntryListener.onEntryAdded(mRow);
- mEntryListener.onEntryAdded(mRow2);
- mBubbleData.dismissAll(Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.getOverflowBubbles()).isNotEmpty();
-
- mEntryListener.onEntryRemoved(mRow, REASON_APP_CANCEL);
- mEntryListener.onEntryRemoved(mRow2, REASON_APP_CANCEL);
- assertThat(mBubbleData.getOverflowBubbles()).isEmpty();
-
- verify(mDataRepository, times(1)).loadBubbles(anyInt(), any());
- }
-
- /**
- * Verifies that shortcut deletions triggers that bubble being removed from XML.
- */
- @Test
- public void testDeleteShortcutsDeletesXml() throws Exception {
- ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- BubbleEntry shortcutBubbleEntry = BubblesManager.notifToBubbleEntry(row.getEntry());
- mBubbleController.updateBubble(shortcutBubbleEntry);
-
- mBubbleData.dismissBubbleWithKey(shortcutBubbleEntry.getKey(),
- Bubbles.DISMISS_SHORTCUT_REMOVED);
-
- verify(mDataRepository, atLeastOnce()).removeBubbles(anyInt(), mBubbleListCaptor.capture());
- assertThat(mBubbleListCaptor.getValue().get(0).getKey()).isEqualTo(
- shortcutBubbleEntry.getKey());
- }
-
- @Test
- public void testShowManageMenuChangesSysuiState() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- // Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Show the menu
- stackView.showManageMenu(true);
- assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
- }
-
- @Test
- public void testHideManageMenuChangesSysuiState() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- // Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Show the menu
- stackView.showManageMenu(true);
- assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-
- // Hide the menu
- stackView.showManageMenu(false);
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testCollapseBubbleManageMenuChangesSysuiState() {
- mBubbleController.updateBubble(mBubbleEntry);
- assertTrue(mBubbleController.hasBubbles());
-
- // Expand the stack
- BubbleStackView stackView = mBubbleController.getStackView();
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- assertSysuiStates(true /* stackExpanded */, false /* mangeMenuExpanded */);
-
- // Show the menu
- stackView.showManageMenu(true);
- assertSysuiStates(true /* stackExpanded */, true /* mangeMenuExpanded */);
-
- // Collapse the stack
- mBubbleData.setExpanded(false);
-
- assertSysuiStates(false /* stackExpanded */, false /* mangeMenuExpanded */);
- }
-
- @Test
- public void testNotificationChannelModified_channelUpdated_removesOverflowBubble()
- throws Exception {
- // Setup
- ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- NotificationEntry entry = row.getEntry();
- entry.getChannel().setConversationId(
- row.getEntry().getChannel().getParentChannelId(),
- "shortcutId");
- mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
- assertTrue(mBubbleController.hasBubbles());
-
- // Overflow it
- mBubbleData.dismissBubbleWithKey(entry.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue();
-
- // Test
- entry.getChannel().setDeleted(true);
- mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(),
- entry.getSbn().getUser(),
- entry.getChannel(),
- NOTIFICATION_CHANNEL_OR_GROUP_UPDATED);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
- }
-
- @Test
- public void testNotificationChannelModified_channelDeleted_removesOverflowBubble()
- throws Exception {
- // Setup
- ExpandableNotificationRow row = mNotificationTestHelper.createShortcutBubble("shortcutId");
- NotificationEntry entry = row.getEntry();
- entry.getChannel().setConversationId(
- row.getEntry().getChannel().getParentChannelId(),
- "shortcutId");
- mBubbleController.updateBubble(BubblesManager.notifToBubbleEntry(row.getEntry()));
- assertTrue(mBubbleController.hasBubbles());
-
- // Overflow it
- mBubbleData.dismissBubbleWithKey(entry.getKey(),
- Bubbles.DISMISS_USER_GESTURE);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isTrue();
-
- // Test
- entry.getChannel().setDeleted(true);
- mBubbleController.onNotificationChannelModified(entry.getSbn().getPackageName(),
- entry.getSbn().getUser(),
- entry.getChannel(),
- NOTIFICATION_CHANNEL_OR_GROUP_DELETED);
- assertThat(mBubbleData.hasOverflowBubbleWithKey(entry.getKey())).isFalse();
- }
-
- @Test
- public void testStackViewOnBackPressed_updatesBubbleDataExpandState() {
- mBubbleController.updateBubble(mBubbleEntry);
-
- // Expand the stack
- mBubbleData.setExpanded(true);
- assertStackExpanded();
-
- // Hit back
- BubbleStackView stackView = mBubbleController.getStackView();
- stackView.onBackPressed();
-
- // Make sure we're collapsed
- assertStackCollapsed();
- }
-
-
- @Test
- public void testRegisterUnregisterBroadcastListener() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
- assertThat(mFilterArgumentCaptor.getValue().getAction(0)).isEqualTo(
- Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- assertThat(mFilterArgumentCaptor.getValue().getAction(1)).isEqualTo(
- Intent.ACTION_SCREEN_OFF);
-
- mBubbleData.dismissBubbleWithKey(mBubbleEntry.getKey(), REASON_APP_CANCEL);
- // TODO: not certain why this isn't called normally when tests are run, perhaps because
- // it's after an animation in BSV. This calls BubbleController#removeFromWindowManagerMaybe
- mBubbleController.onAllBubblesAnimatedOut();
-
- verify(mContext).unregisterReceiver(eq(mBroadcastReceiverArgumentCaptor.getValue()));
- }
-
- @Test
- public void testBroadcastReceiverCloseDialogs_notGestureNav() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
- Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
-
- assertStackExpanded();
- }
-
- @Test
- public void testBroadcastReceiverCloseDialogs_reasonGestureNav() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
-
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
- Intent i = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
- i.putExtra("reason", "gestureNav");
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
- assertStackCollapsed();
- }
-
- @Test
- public void testBroadcastReceiver_screenOff() {
- spyOn(mContext);
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
-
- verify(mContext).registerReceiver(mBroadcastReceiverArgumentCaptor.capture(),
- mFilterArgumentCaptor.capture());
-
- Intent i = new Intent(Intent.ACTION_SCREEN_OFF);
- mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, i);
- assertStackCollapsed();
- }
-
- @Test
- public void testOnStatusBarStateChanged() {
- mBubbleController.updateBubble(mBubbleEntry);
- mBubbleData.setExpanded(true);
- assertStackExpanded();
- BubbleStackView stackView = mBubbleController.getStackView();
- assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
-
- mBubbleController.onStatusBarStateChanged(false);
-
- assertStackCollapsed();
- assertThat(stackView.getVisibility()).isEqualTo(View.INVISIBLE);
-
- mBubbleController.onStatusBarStateChanged(true);
- assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE);
- }
-
- @Test
- public void testSetShouldAutoExpand_notifiesFlagChanged() {
- mBubbleController.updateBubble(mBubbleEntry);
-
- assertTrue(mBubbleController.hasBubbles());
- Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey());
- assertThat(b.shouldAutoExpand()).isFalse();
-
- // Set it to the same thing
- b.setShouldAutoExpand(false);
-
- // Verify it doesn't notify
- verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any());
-
- // Set it to something different
- b.setShouldAutoExpand(true);
- verify(mBubbleController).onBubbleMetadataFlagChanged(b);
- }
-
- @Test
- public void testUpdateBubble_skipsDndSuppressListNotifs() {
- mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
- mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
- mRow.shouldSuppressPeek());
- mBubbleEntry.getBubbleMetadata().setFlags(
- Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
-
- mBubbleController.updateBubble(mBubbleEntry);
-
- Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey());
- assertThat(b.shouldAutoExpand()).isFalse();
- assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull();
- }
-
- @Test
- public void testOnRankingUpdate_DndSuppressListNotif() {
- // It's in the stack
- mBubbleController.updateBubble(mBubbleEntry);
- assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue();
-
- // Set current user profile
- SparseArray<UserInfo> userInfos = new SparseArray<>();
- userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(),
- mock(UserInfo.class));
- mBubbleController.onCurrentProfilesChanged(userInfos);
-
- // Send ranking update that the notif is suppressed from the list.
- HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>();
- mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(),
- mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */,
- mRow.shouldSuppressPeek());
- Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true);
- entryDataByKey.put(mBubbleEntry.getKey(), pair);
-
- NotificationListenerService.RankingMap rankingMap =
- mock(NotificationListenerService.RankingMap.class);
- when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() });
- mBubbleController.onRankingUpdated(rankingMap, entryDataByKey);
-
- // Should no longer be in the stack
- assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse();
- }
-
- /**
- * Sets the bubble metadata flags for this entry. These flags are normally set by
- * NotificationManagerService when the notification is sent, however, these tests do not
- * go through that path so we set them explicitly when testing.
- */
- private void setMetadataFlags(NotificationEntry entry, int flag, boolean enableFlag) {
- Notification.BubbleMetadata bubbleMetadata =
- entry.getSbn().getNotification().getBubbleMetadata();
- int flags = bubbleMetadata.getFlags();
- if (enableFlag) {
- flags |= flag;
- } else {
- flags &= ~flag;
- }
- bubbleMetadata.setFlags(flags);
- }
-
- /**
- * Asserts that the bubble stack is expanded and also validates the cached state is updated.
- */
- private void assertStackExpanded() {
- assertTrue(mBubbleController.isStackExpanded());
- assertTrue(mBubbleController.getImplCachedState().isStackExpanded());
- }
-
- /**
- * Asserts that the bubble stack is collapsed and also validates the cached state is updated.
- */
- private void assertStackCollapsed() {
- assertFalse(mBubbleController.isStackExpanded());
- assertFalse(mBubbleController.getImplCachedState().isStackExpanded());
- }
-
- /**
- * Asserts that a bubble notification is suppressed from the shade and also validates the cached
- * state is updated.
- */
- private void assertBubbleNotificationSuppressedFromShade(BubbleEntry entry) {
- assertTrue(mBubbleController.isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- assertTrue(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- }
-
- /**
- * Asserts that a bubble notification is not suppressed from the shade and also validates the
- * cached state is updated.
- */
- private void assertBubbleNotificationNotSuppressedFromShade(BubbleEntry entry) {
- assertFalse(mBubbleController.isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- assertFalse(mBubbleController.getImplCachedState().isBubbleNotificationSuppressedFromShade(
- entry.getKey(), entry.getGroupKey()));
- }
-
- /**
- * Asserts that the system ui states associated to bubbles are in the correct state.
- */
- private void assertSysuiStates(boolean stackExpanded, boolean manageMenuExpanded) {
- assertThat(mSysUiStateBubblesExpanded).isEqualTo(stackExpanded);
- assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded);
- }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
index 17e5778f7aab..f901c327b76e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableBubbleController.java
@@ -38,6 +38,7 @@ import com.android.wm.shell.common.SyncTransactionQueue;
import com.android.wm.shell.common.TaskStackListenerImpl;
import com.android.wm.shell.draganddrop.DragAndDropController;
import com.android.wm.shell.onehanded.OneHandedController;
+import com.android.wm.shell.sysui.ShellController;
import java.util.Optional;
@@ -48,6 +49,7 @@ public class TestableBubbleController extends BubbleController {
// Let's assume surfaces can be synchronized immediately.
TestableBubbleController(Context context,
+ ShellController shellController,
BubbleData data,
FloatingContentCoordinator floatingContentCoordinator,
BubbleDataRepository dataRepository,
@@ -67,11 +69,12 @@ public class TestableBubbleController extends BubbleController {
Handler shellMainHandler,
TaskViewTransitions taskViewTransitions,
SyncTransactionQueue syncQueue) {
- super(context, data, Runnable::run, floatingContentCoordinator, dataRepository,
- statusBarService, windowManager, windowManagerShellWrapper, userManager,
- launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer, positioner,
- displayController, oneHandedOptional, dragAndDropController, shellMainExecutor,
- shellMainHandler, new SyncExecutor(), taskViewTransitions, syncQueue);
+ super(context, shellController, data, Runnable::run, floatingContentCoordinator,
+ dataRepository, statusBarService, windowManager, windowManagerShellWrapper,
+ userManager, launcherApps, bubbleLogger, taskStackListener, shellTaskOrganizer,
+ positioner, displayController, oneHandedOptional, dragAndDropController,
+ shellMainExecutor, shellMainHandler, new SyncExecutor(), taskViewTransitions,
+ syncQueue);
setInflateSynchronously(true);
initialize();
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
index a7f0dc22e849..d80ea154e77a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/TestableNotificationInterruptStateProviderImpl.java
@@ -30,6 +30,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte
import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProviderImpl;
import com.android.systemui.statusbar.policy.BatteryController;
import com.android.systemui.statusbar.policy.HeadsUpManager;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
public class TestableNotificationInterruptStateProviderImpl
extends NotificationInterruptStateProviderImpl {
@@ -41,6 +42,7 @@ public class TestableNotificationInterruptStateProviderImpl
AmbientDisplayConfiguration ambientDisplayConfiguration,
NotificationFilter filter,
StatusBarStateController statusBarStateController,
+ KeyguardStateController keyguardStateController,
BatteryController batteryController,
HeadsUpManager headsUpManager,
NotificationInterruptLogger logger,
@@ -54,6 +56,7 @@ public class TestableNotificationInterruptStateProviderImpl
filter,
batteryController,
statusBarStateController,
+ keyguardStateController,
headsUpManager,
logger,
mainHandler,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
index 185942e6fbc8..9c2136675dfa 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/WMShellTest.java
@@ -24,27 +24,22 @@ import android.test.suitebuilder.annotation.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.KeyguardUpdateMonitorCallback;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.keyguard.ScreenLifecycle;
import com.android.systemui.keyguard.WakefulnessLifecycle;
import com.android.systemui.model.SysUiState;
-import com.android.systemui.navigationbar.NavigationModeController;
import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.statusbar.policy.ConfigurationController;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.statusbar.policy.UserInfoController;
import com.android.systemui.tracing.ProtoTracer;
-import com.android.wm.shell.ShellCommandHandler;
import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.compatui.CompatUI;
-import com.android.wm.shell.draganddrop.DragAndDrop;
-import com.android.wm.shell.hidedisplaycutout.HideDisplayCutout;
import com.android.wm.shell.onehanded.OneHanded;
import com.android.wm.shell.onehanded.OneHandedEventCallback;
import com.android.wm.shell.onehanded.OneHandedTransitionCallback;
import com.android.wm.shell.pip.Pip;
import com.android.wm.shell.splitscreen.SplitScreen;
+import com.android.wm.shell.sysui.ShellInterface;
import org.junit.Before;
import org.junit.Test;
@@ -65,36 +60,30 @@ import java.util.Optional;
public class WMShellTest extends SysuiTestCase {
WMShell mWMShell;
+ @Mock ShellInterface mShellInterface;
@Mock CommandQueue mCommandQueue;
@Mock ConfigurationController mConfigurationController;
@Mock KeyguardStateController mKeyguardStateController;
@Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
- @Mock NavigationModeController mNavigationModeController;
@Mock ScreenLifecycle mScreenLifecycle;
@Mock SysUiState mSysUiState;
@Mock Pip mPip;
@Mock SplitScreen mSplitScreen;
@Mock OneHanded mOneHanded;
- @Mock HideDisplayCutout mHideDisplayCutout;
@Mock WakefulnessLifecycle mWakefulnessLifecycle;
@Mock ProtoTracer mProtoTracer;
- @Mock ShellCommandHandler mShellCommandHandler;
- @Mock CompatUI mCompatUI;
@Mock UserInfoController mUserInfoController;
@Mock ShellExecutor mSysUiMainExecutor;
- @Mock DragAndDrop mDragAndDrop;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- mWMShell = new WMShell(mContext, Optional.of(mPip),
- Optional.of(mSplitScreen), Optional.of(mOneHanded), Optional.of(mHideDisplayCutout),
- Optional.of(mShellCommandHandler), Optional.of(mCompatUI),
- Optional.of(mDragAndDrop),
- mCommandQueue, mConfigurationController, mKeyguardStateController,
- mKeyguardUpdateMonitor, mNavigationModeController, mScreenLifecycle, mSysUiState,
- mProtoTracer, mWakefulnessLifecycle, mUserInfoController, mSysUiMainExecutor);
+ mWMShell = new WMShell(mContext, mShellInterface, Optional.of(mPip),
+ Optional.of(mSplitScreen), Optional.of(mOneHanded), mCommandQueue,
+ mConfigurationController, mKeyguardStateController, mKeyguardUpdateMonitor,
+ mScreenLifecycle, mSysUiState, mProtoTracer, mWakefulnessLifecycle,
+ mUserInfoController, mSysUiMainExecutor);
}
@Test
@@ -105,35 +94,12 @@ public class WMShellTest extends SysuiTestCase {
}
@Test
- public void initSplitScreen_registersCallbacks() {
- mWMShell.initSplitScreen(mSplitScreen);
-
- verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
- }
-
- @Test
public void initOneHanded_registersCallbacks() {
mWMShell.initOneHanded(mOneHanded);
- verify(mKeyguardUpdateMonitor).registerCallback(any(KeyguardUpdateMonitorCallback.class));
verify(mCommandQueue).addCallback(any(CommandQueue.Callbacks.class));
verify(mScreenLifecycle).addObserver(any(ScreenLifecycle.Observer.class));
verify(mOneHanded).registerTransitionCallback(any(OneHandedTransitionCallback.class));
verify(mOneHanded).registerEventCallback(any(OneHandedEventCallback.class));
}
-
- @Test
- public void initHideDisplayCutout_registersCallbacks() {
- mWMShell.initHideDisplayCutout(mHideDisplayCutout);
-
- verify(mConfigurationController).addCallback(
- any(ConfigurationController.ConfigurationListener.class));
- }
-
- @Test
- public void initCompatUI_registersCallbacks() {
- mWMShell.initCompatUi(mCompatUI);
-
- verify(mKeyguardStateController).addCallback(any(KeyguardStateController.Callback.class));
- }
}
diff --git a/packages/SystemUI/unfold/Android.bp b/packages/SystemUI/unfold/Android.bp
new file mode 100644
index 000000000000..108295b90e58
--- /dev/null
+++ b/packages/SystemUI/unfold/Android.bp
@@ -0,0 +1,39 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+ name: "SystemUIUnfoldLib",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/**/*.aidl",
+ ],
+ static_libs: [
+ "androidx.dynamicanimation_dynamicanimation",
+ "dagger2",
+ "jsr330",
+ ],
+ java_version: "1.8",
+ min_sdk_version: "current",
+ plugins: ["dagger2-compiler"],
+}
diff --git a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml b/packages/SystemUI/unfold/AndroidManifest.xml
index 63289a3f75d9..ee8afe1aff5b 100644
--- a/libs/WindowManager/Shell/res/color/unfold_transition_background.xml
+++ b/packages/SystemUI/unfold/AndroidManifest.xml
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
+<!--
+ Copyright (C) 2017 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -13,7 +14,9 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- Matches taskbar color -->
- <item android:color="@android:color/system_neutral2_500" android:lStar="35" />
-</selector>
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.systemui.unfold">
+
+
+</manifest>
diff --git a/packages/SystemUI/unfold/lint-baseline.xml b/packages/SystemUI/unfold/lint-baseline.xml
new file mode 100644
index 000000000000..449ed2e60853
--- /dev/null
+++ b/packages/SystemUI/unfold/lint-baseline.xml
@@ -0,0 +1,3 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<issues format="6" by="lint 7.1.0-dev" type="baseline" client="" name="" variant="all" version="7.1.0-dev">
+</issues>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
index 9e5aeb84b624..a5ec0a454412 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedComponent.kt
@@ -16,16 +16,16 @@
package com.android.systemui.unfold
-import android.app.ActivityManager
import android.content.ContentResolver
import android.content.Context
import android.hardware.SensorManager
-import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBackground
+import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import com.android.systemui.unfold.util.UnfoldTransitionATracePrefix
import dagger.BindsInstance
import dagger.Component
@@ -51,12 +51,12 @@ internal interface UnfoldSharedComponent {
@BindsInstance context: Context,
@BindsInstance config: UnfoldTransitionConfig,
@BindsInstance screenStatusProvider: ScreenStatusProvider,
- @BindsInstance deviceStateManager: DeviceStateManager,
- @BindsInstance activityManager: ActivityManager,
+ @BindsInstance foldProvider: FoldProvider,
+ @BindsInstance activityTypeProvider: CurrentActivityTypeProvider,
@BindsInstance sensorManager: SensorManager,
- @BindsInstance @Main handler: Handler,
- @BindsInstance @Main executor: Executor,
- @BindsInstance @UiBackground backgroundExecutor: Executor,
+ @BindsInstance @UnfoldMain handler: Handler,
+ @BindsInstance @UnfoldMain executor: Executor,
+ @BindsInstance @UnfoldBackground backgroundExecutor: Executor,
@BindsInstance @UnfoldTransitionATracePrefix tracingTagPrefix: String,
@BindsInstance contentResolver: ContentResolver = context.contentResolver
): UnfoldSharedComponent
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index c612995241ef..8f4ee4dc9838 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -17,8 +17,8 @@
package com.android.systemui.unfold
import android.hardware.SensorManager
-import com.android.systemui.dagger.qualifiers.UiBackground
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldBackground
import com.android.systemui.unfold.progress.FixedTimingTransitionProgressProvider
import com.android.systemui.unfold.progress.PhysicsBasedUnfoldTransitionProgressProvider
import com.android.systemui.unfold.updates.DeviceFoldStateProvider
@@ -70,7 +70,7 @@ class UnfoldSharedModule {
fun hingeAngleProvider(
config: UnfoldTransitionConfig,
sensorManager: SensorManager,
- @UiBackground executor: Executor
+ @UnfoldBackground executor: Executor
): HingeAngleProvider =
if (config.isHingeAngleEnabled) {
HingeSensorAngleProvider(sensorManager, executor)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
index cc56007c431a..402dd8474bc4 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionFactory.kt
@@ -17,14 +17,13 @@
package com.android.systemui.unfold
-import android.app.ActivityManager
import android.content.Context
import android.hardware.SensorManager
-import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
-import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig
import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.updates.FoldProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import java.util.concurrent.Executor
/**
@@ -39,8 +38,8 @@ fun createUnfoldTransitionProgressProvider(
context: Context,
config: UnfoldTransitionConfig,
screenStatusProvider: ScreenStatusProvider,
- deviceStateManager: DeviceStateManager,
- activityManager: ActivityManager,
+ foldProvider: FoldProvider,
+ activityTypeProvider: CurrentActivityTypeProvider,
sensorManager: SensorManager,
mainHandler: Handler,
mainExecutor: Executor,
@@ -52,8 +51,8 @@ fun createUnfoldTransitionProgressProvider(
context,
config,
screenStatusProvider,
- deviceStateManager,
- activityManager,
+ foldProvider,
+ activityTypeProvider,
sensorManager,
mainHandler,
mainExecutor,
@@ -64,5 +63,3 @@ fun createUnfoldTransitionProgressProvider(
?: throw IllegalStateException(
"Trying to create " +
"UnfoldTransitionProgressProvider when the transition is disabled")
-
-fun createConfig(context: Context): UnfoldTransitionConfig = ResourceUnfoldTransitionConfig(context)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
index 409dc95ab131..d54481c72bfd 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldTransitionProgressProvider.kt
@@ -15,9 +15,9 @@
*/
package com.android.systemui.unfold
-import android.annotation.FloatRange
-import com.android.systemui.statusbar.policy.CallbackController
+import androidx.annotation.FloatRange
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
+import com.android.systemui.unfold.util.CallbackController
/**
* Interface that allows to receive unfold transition progress updates.
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
new file mode 100644
index 000000000000..2044f05664d0
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/ScreenSizeFoldProvider.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.compat
+
+import android.content.Context
+import android.content.res.Configuration
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.FoldProvider.FoldCallback
+import java.util.concurrent.Executor
+
+/**
+ * Fold provider that notifies about fold state based on the screen size
+ * It could be used when no activity context is available
+ * TODO(b/232369816): use Jetpack WM library when non-activity contexts supported b/169740873
+ */
+class ScreenSizeFoldProvider(private val context: Context) : FoldProvider {
+
+ private var callbacks: MutableList<FoldCallback> = arrayListOf()
+ private var lastWidth: Int = 0
+
+ override fun registerCallback(callback: FoldCallback, executor: Executor) {
+ callbacks += callback
+ onConfigurationChange(context.resources.configuration)
+ }
+
+ override fun unregisterCallback(callback: FoldCallback) {
+ callbacks -= callback
+ }
+
+ fun onConfigurationChange(newConfig: Configuration) {
+ if (lastWidth == newConfig.smallestScreenWidthDp) {
+ return
+ }
+
+ if (newConfig.smallestScreenWidthDp > INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP) {
+ callbacks.forEach { it.onFoldUpdated(false) }
+ } else {
+ callbacks.forEach { it.onFoldUpdated(true) }
+ }
+ lastWidth = newConfig.smallestScreenWidthDp
+ }
+}
+
+private const val INNER_SCREEN_SMALLEST_SCREEN_WIDTH_THRESHOLD_DP = 600
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt
new file mode 100644
index 000000000000..c405f3110297
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/compat/SizeScreenStatusProvider.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.compat
+
+import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import java.util.concurrent.Executor
+
+class SizeScreenStatusProvider(
+ private val foldProvider: FoldProvider,
+ private val executor: Executor
+) : ScreenStatusProvider {
+
+ private val listeners: MutableList<ScreenListener> = arrayListOf()
+ private val callback = object : FoldProvider.FoldCallback {
+ override fun onFoldUpdated(isFolded: Boolean) {
+ if (!isFolded) {
+ listeners.forEach { it.onScreenTurnedOn() }
+ }
+ }
+ }
+
+ fun start() {
+ foldProvider.registerCallback(
+ callback,
+ executor
+ )
+ }
+
+ fun stop() {
+ foldProvider.unregisterCallback(callback)
+ }
+
+ override fun addCallback(listener: ScreenListener) {
+ listeners.add(listener)
+ }
+
+ override fun removeCallback(listener: ScreenListener) {
+ listeners.remove(listener)
+ }
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
new file mode 100644
index 000000000000..c51372975a67
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/ResourceUnfoldTransitionConfig.kt
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.config
+
+import android.content.res.Resources
+import javax.inject.Inject
+import javax.inject.Singleton
+
+@Singleton
+class ResourceUnfoldTransitionConfig @Inject constructor() : UnfoldTransitionConfig {
+
+ override val isEnabled: Boolean by lazy {
+ val id = Resources.getSystem()
+ .getIdentifier("config_unfoldTransitionEnabled", "bool", "android")
+ Resources.getSystem().getBoolean(id)
+ }
+
+ override val isHingeAngleEnabled: Boolean by lazy {
+ val id = Resources.getSystem()
+ .getIdentifier("config_unfoldTransitionHingeAngle", "bool", "android")
+ Resources.getSystem().getBoolean(id)
+ }
+
+ override val halfFoldedTimeoutMillis: Int by lazy {
+ val id = Resources.getSystem()
+ .getIdentifier("config_unfoldTransitionHalfFoldedTimeout", "integer", "android")
+ Resources.getSystem().getInteger(id)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
index 5b187b3486c6..765e862aa00d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/config/UnfoldTransitionConfig.kt
@@ -18,4 +18,5 @@ package com.android.systemui.unfold.config
interface UnfoldTransitionConfig {
val isEnabled: Boolean
val isHingeAngleEnabled: Boolean
+ val halfFoldedTimeoutMillis: Int
}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
new file mode 100644
index 000000000000..60747954dac3
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldBackground.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Alternative to [UiBackground] qualifier annotation in unfold module.
+ * It is needed as we can't depend on SystemUI code in this module.
+ */
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnfoldBackground
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt
new file mode 100644
index 000000000000..5553690fb562
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/dagger/UnfoldMain.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.dagger
+
+import javax.inject.Qualifier
+
+/**
+ * Alternative to [Main] qualifier annotation in unfold module.
+ * It is needed as we can't depend on SystemUI code in this module.
+ */
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+annotation class UnfoldMain
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
index 4c85b055aeae..4c85b055aeae 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/FixedTimingTransitionProgressProvider.kt
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
index 04d920cb15d5..2ab28c65f32f 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/PhysicsBasedUnfoldTransitionProgressProvider.kt
@@ -16,7 +16,6 @@
package com.android.systemui.unfold.progress
import android.util.Log
-import android.util.MathUtils.saturate
import androidx.dynamicanimation.animation.DynamicAnimation
import androidx.dynamicanimation.animation.FloatPropertyCompat
import androidx.dynamicanimation.animation.SpringAnimation
@@ -70,6 +69,9 @@ class PhysicsBasedUnfoldTransitionProgressProvider(
springAnimation.animateToFinalPosition(progress)
}
+ private fun saturate(amount: Float, low: Float = 0f, high: Float = 1f): Float =
+ if (amount < low) low else if (amount > high) high else amount
+
override fun onFoldUpdate(@FoldUpdate update: Int) {
when (update) {
FOLD_UPDATE_UNFOLDED_SCREEN_AVAILABLE -> {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
index 14581ccd5c0a..e8038fd7dfa6 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/DeviceFoldStateProvider.kt
@@ -15,46 +15,46 @@
*/
package com.android.systemui.unfold.updates
-import android.annotation.FloatRange
-import android.app.ActivityManager
-import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
-import android.content.Context
-import android.hardware.devicestate.DeviceStateManager
import android.os.Handler
import android.util.Log
+import androidx.annotation.FloatRange
import androidx.annotation.VisibleForTesting
import androidx.core.util.Consumer
-import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.unfold.config.UnfoldTransitionConfig
+import com.android.systemui.unfold.dagger.UnfoldMain
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdate
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
import com.android.systemui.unfold.updates.hinge.FULLY_CLOSED_DEGREES
import com.android.systemui.unfold.updates.hinge.FULLY_OPEN_DEGREES
import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider
+import com.android.systemui.unfold.util.CurrentActivityTypeProvider
import java.util.concurrent.Executor
import javax.inject.Inject
class DeviceFoldStateProvider
@Inject
constructor(
- context: Context,
+ config: UnfoldTransitionConfig,
private val hingeAngleProvider: HingeAngleProvider,
private val screenStatusProvider: ScreenStatusProvider,
- private val deviceStateManager: DeviceStateManager,
- private val activityManager: ActivityManager,
- @Main private val mainExecutor: Executor,
- @Main private val handler: Handler
+ private val foldProvider: FoldProvider,
+ private val activityTypeProvider: CurrentActivityTypeProvider,
+ @UnfoldMain private val mainExecutor: Executor,
+ @UnfoldMain private val handler: Handler
) : FoldStateProvider {
private val outputListeners: MutableList<FoldUpdatesListener> = mutableListOf()
- @FoldUpdate private var lastFoldUpdate: Int? = null
+ @FoldUpdate
+ private var lastFoldUpdate: Int? = null
- @FloatRange(from = 0.0, to = 180.0) private var lastHingeAngle: Float = 0f
+ @FloatRange(from = 0.0, to = 180.0)
+ private var lastHingeAngle: Float = 0f
private val hingeAngleListener = HingeAngleListener()
private val screenListener = ScreenStatusListener()
- private val foldStateListener = FoldStateListener(context)
+ private val foldStateListener = FoldStateListener()
private val timeoutRunnable = TimeoutRunnable()
/**
@@ -62,22 +62,20 @@ constructor(
* [FOLD_UPDATE_START_CLOSING] or [FOLD_UPDATE_START_OPENING] event, if an end state is not
* reached.
*/
- private val halfOpenedTimeoutMillis: Int =
- context.resources.getInteger(
- com.android.internal.R.integer.config_unfoldTransitionHalfFoldedTimeout)
+ private val halfOpenedTimeoutMillis: Int = config.halfFoldedTimeoutMillis
private var isFolded = false
private var isUnfoldHandled = true
override fun start() {
- deviceStateManager.registerCallback(mainExecutor, foldStateListener)
+ foldProvider.registerCallback(foldStateListener, mainExecutor)
screenStatusProvider.addCallback(screenListener)
hingeAngleProvider.addCallback(hingeAngleListener)
}
override fun stop() {
screenStatusProvider.removeCallback(screenListener)
- deviceStateManager.unregisterCallback(foldStateListener)
+ foldProvider.unregisterCallback(foldStateListener)
hingeAngleProvider.removeCallback(hingeAngleListener)
hingeAngleProvider.stop()
}
@@ -92,13 +90,13 @@ constructor(
override val isFinishedOpening: Boolean
get() = !isFolded &&
- (lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
- lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
+ (lastFoldUpdate == FOLD_UPDATE_FINISH_FULL_OPEN ||
+ lastFoldUpdate == FOLD_UPDATE_FINISH_HALF_OPEN)
private val isTransitionInProgress: Boolean
get() =
lastFoldUpdate == FOLD_UPDATE_START_OPENING ||
- lastFoldUpdate == FOLD_UPDATE_START_CLOSING
+ lastFoldUpdate == FOLD_UPDATE_START_CLOSING
private fun onHingeAngle(angle: Float) {
if (DEBUG) {
@@ -136,39 +134,36 @@ constructor(
* apps that support table-top/HALF_FOLDED mode. Only for launcher, there is no threshold.
*/
private fun getClosingThreshold(): Int? {
- val activityType =
- activityManager.getRunningTasks(/* maxNum= */ 1)?.getOrNull(0)?.topActivityType
- ?: return null
+ val isHomeActivity = activityTypeProvider.isHomeActivity ?: return null
if (DEBUG) {
- Log.d(TAG, "activityType=" + activityType)
+ Log.d(TAG, "isHomeActivity=$isHomeActivity")
}
- return if (activityType == ACTIVITY_TYPE_HOME) {
+ return if (isHomeActivity) {
null
} else {
START_CLOSING_ON_APPS_THRESHOLD_DEGREES
}
}
- private inner class FoldStateListener(context: Context) :
- DeviceStateManager.FoldStateListener(
- context,
- { folded: Boolean ->
- isFolded = folded
- lastHingeAngle = FULLY_CLOSED_DEGREES
-
- if (folded) {
- hingeAngleProvider.stop()
- notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
- cancelTimeout()
- isUnfoldHandled = false
- } else {
- notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
- rescheduleAbortAnimationTimeout()
- hingeAngleProvider.start()
- }
- })
+ private inner class FoldStateListener : FoldProvider.FoldCallback {
+ override fun onFoldUpdated(isFolded: Boolean) {
+ this@DeviceFoldStateProvider.isFolded = isFolded
+ lastHingeAngle = FULLY_CLOSED_DEGREES
+
+ if (isFolded) {
+ hingeAngleProvider.stop()
+ notifyFoldUpdate(FOLD_UPDATE_FINISH_CLOSED)
+ cancelTimeout()
+ isUnfoldHandled = false
+ } else {
+ notifyFoldUpdate(FOLD_UPDATE_START_OPENING)
+ rescheduleAbortAnimationTimeout()
+ hingeAngleProvider.start()
+ }
+ }
+ }
private fun notifyFoldUpdate(@FoldUpdate update: Int) {
if (DEBUG) {
@@ -234,7 +229,9 @@ private const val TAG = "DeviceFoldProvider"
private const val DEBUG = false
/** Threshold after which we consider the device fully unfolded. */
-@VisibleForTesting const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
+@VisibleForTesting
+const val FULLY_OPEN_THRESHOLD_DEGREES = 15f
/** Fold animation on top of apps only when the angle exceeds this threshold. */
-@VisibleForTesting const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
+@VisibleForTesting
+const val START_CLOSING_ON_APPS_THRESHOLD_DEGREES = 60
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
new file mode 100644
index 000000000000..6e87beeb295f
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldProvider.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.updates
+
+import java.util.concurrent.Executor
+
+interface FoldProvider {
+ fun registerCallback(callback: FoldCallback, executor: Executor)
+ fun unregisterCallback(callback: FoldCallback)
+
+ interface FoldCallback {
+ fun onFoldUpdated(isFolded: Boolean)
+ }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
index 14a3a70fc6b0..c7a8bf336777 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateProvider.kt
@@ -15,10 +15,10 @@
*/
package com.android.systemui.unfold.updates
-import android.annotation.FloatRange
-import android.annotation.IntDef
-import com.android.systemui.statusbar.policy.CallbackController
+import androidx.annotation.FloatRange
+import androidx.annotation.IntDef
import com.android.systemui.unfold.updates.FoldStateProvider.FoldUpdatesListener
+import com.android.systemui.unfold.util.CallbackController
/**
* Allows to subscribe to main events related to fold/unfold process such as hinge angle update,
@@ -36,7 +36,6 @@ interface FoldStateProvider : CallbackController<FoldUpdatesListener> {
}
@IntDef(
- prefix = ["FOLD_UPDATE_"],
value =
[
FOLD_UPDATE_START_OPENING,
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
new file mode 100644
index 000000000000..e985506bd989
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/EmptyHingeAngleProvider.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.updates.hinge
+
+import androidx.core.util.Consumer
+
+internal object EmptyHingeAngleProvider : HingeAngleProvider {
+ override fun start() {}
+
+ override fun stop() {}
+
+ override fun removeCallback(listener: Consumer<Float>) {}
+
+ override fun addCallback(listener: Consumer<Float>) {}
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
new file mode 100644
index 000000000000..e464c3f81546
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeAngleProvider.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.updates.hinge
+
+import androidx.core.util.Consumer
+import com.android.systemui.unfold.util.CallbackController
+
+/**
+ * Emits device hinge angle values (angle between two integral parts of the device).
+ *
+ * The hinge angle could be from 0 to 360 degrees inclusive. For foldable devices usually 0
+ * corresponds to fully closed (folded) state and 180 degrees corresponds to fully open (flat)
+ * state.
+ */
+interface HingeAngleProvider : CallbackController<Consumer<Float>> {
+ fun start()
+ fun stop()
+}
+
+const val FULLY_OPEN_DEGREES = 180f
+const val FULLY_CLOSED_DEGREES = 0f
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
index c93412b53817..3fc5d610dc2d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/hinge/HingeSensorAngleProvider.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
package com.android.systemui.unfold.updates.hinge
import android.hardware.Sensor
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
index 668c69442cac..d95e050474de 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/screen/ScreenStatusProvider.kt
@@ -15,8 +15,8 @@
*/
package com.android.systemui.unfold.updates.screen
-import com.android.systemui.statusbar.policy.CallbackController
import com.android.systemui.unfold.updates.screen.ScreenStatusProvider.ScreenListener
+import com.android.systemui.unfold.util.CallbackController
interface ScreenStatusProvider : CallbackController<ScreenListener> {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
index 1574c8d37ab1..d8bc01804f14 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ATraceLoggerTransitionProgressListener.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
package com.android.systemui.unfold.util
import android.os.Trace
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt
new file mode 100644
index 000000000000..46ad534722cd
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CallbackController.kt
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.util
+
+interface CallbackController<T> {
+ fun addCallback(listener: T)
+ fun removeCallback(listener: T)
+}
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
new file mode 100644
index 000000000000..d0e6cdc9a3c6
--- /dev/null
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/CurrentActivityTypeProvider.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+package com.android.systemui.unfold.util
+
+interface CurrentActivityTypeProvider {
+ val isHomeActivity: Boolean?
+}
+
+class EmptyCurrentActivityTypeProvider(override val isHomeActivity: Boolean? = null) :
+ CurrentActivityTypeProvider
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
index dfe87921dd42..5c92b3499835 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScaleAwareTransitionProgressProvider.kt
@@ -1,3 +1,17 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
package com.android.systemui.unfold.util
import android.animation.ValueAnimator
diff --git a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
index 8491f832b740..8491f832b740 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/util/ScopedUnfoldTransitionProgressProvider.kt
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index dfa34bb50805..0a4ecb227548 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -286,10 +286,18 @@ message SystemMessage {
// Package: android
NOTE_MTE_OVERRIDE_ENABLED = 69;
+ // Notify the user that this is a guest session with information
+ // about first login and ephemeral state
+ // Package: android
+ NOTE_GUEST_SESSION = 70;
+
// Inform the user of notification permissions changes.
// Package: android
NOTE_REVIEW_NOTIFICATION_PERMISSIONS = 71;
+ // Notify the user to setup their dock
+ NOTE_SETUP_DOCK = 72;
+
// ADD_NEW_IDS_ABOVE_THIS_LINE
// Legacy IDs with arbitrary values appear below
// Legacy IDs existed as stable non-conflicting constants prior to the O release
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index 966d887d11f7..dc39b01cf6b6 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -184,7 +184,12 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
mPanningScalingState.mScrollGestureDetector.onTouchEvent(event);
mPanningScalingState.mScaleGestureDetector.onTouchEvent(event);
- stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+ try {
+ stateHandler.onMotionEvent(event, rawEvent, policyFlags);
+ } catch (GestureException e) {
+ Slog.e(mLogTag, "Error processing motion event", e);
+ clearAndTransitionToStateDetecting();
+ }
}
@Override
@@ -281,7 +286,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
}
interface State {
- void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags);
+ void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+ throws GestureException;
default void clear() {}
@@ -439,7 +445,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
private boolean mLastMoveOutsideMagnifiedRegion;
@Override
- public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+ public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
+ throws GestureException {
final int action = event.getActionMasked();
switch (action) {
case ACTION_POINTER_DOWN: {
@@ -449,7 +456,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
break;
case ACTION_MOVE: {
if (event.getPointerCount() != 1) {
- throw new IllegalStateException("Should have one pointer down.");
+ throw new GestureException("Should have one pointer down.");
}
final float eventX = event.getX();
final float eventY = event.getY();
@@ -475,7 +482,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
case ACTION_DOWN:
case ACTION_POINTER_UP: {
- throw new IllegalArgumentException(
+ throw new GestureException(
"Unexpected event type: " + MotionEvent.actionToString(action));
}
}
@@ -1087,4 +1094,13 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH
mGestureHandler.mDetectingState.setShortcutTriggered(false);
}
}
+
+ /**
+ * Indicates an error with a gesture handler or state.
+ */
+ private static class GestureException extends Exception {
+ GestureException(String message) {
+ super(message);
+ }
+ }
}
diff --git a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
index 1af8ad344190..84707a8d9c00 100644
--- a/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
+++ b/services/appprediction/java/com/android/server/appprediction/AppPredictionPerUserService.java
@@ -398,18 +398,7 @@ public class AppPredictionPerUserService extends
final IBinder.DeathRecipient mDeathRecipient;
private final RemoteCallbackList<IPredictionCallback> mCallbacks =
- new RemoteCallbackList<IPredictionCallback>() {
- @Override
- public void onCallbackDied(IPredictionCallback callback) {
- if (DEBUG) {
- Slog.d(TAG, "Binder died for session Id=" + mSessionId
- + " and callback=" + callback.asBinder());
- }
- if (mCallbacks.getRegisteredCallbackCount() == 0) {
- destroy();
- }
- }
- };
+ new RemoteCallbackList<>();
AppPredictionSessionInfo(
@NonNull final AppPredictionSessionId id,
diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
index 8fe57e18ea37..4892718d6203 100644
--- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -123,6 +123,7 @@ import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
import com.android.internal.widget.IRemoteViewsFactory;
import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
import com.android.server.WidgetBackupProvider;
import org.xmlpull.v1.XmlPullParser;
@@ -266,7 +267,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
mDevicePolicyManagerInternal = LocalServices.getService(DevicePolicyManagerInternal.class);
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
mSaveStateHandler = BackgroundThread.getHandler();
- mCallbackHandler = new CallbackHandler(mContext.getMainLooper());
+ final ServiceThread serviceThread = new ServiceThread(TAG,
+ android.os.Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */);
+ serviceThread.start();
+ mCallbackHandler = new CallbackHandler(serviceThread.getLooper());
mBackupRestoreController = new BackupRestoreController();
mSecurityPolicy = new SecurityPolicy();
mIsProviderInfoPersisted = !ActivityManager.isLowRamDeviceStatic()
@@ -307,26 +311,26 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
packageFilter.addDataScheme("package");
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- packageFilter, null, null);
+ packageFilter, null, mCallbackHandler);
// Register for events related to sdcard installation.
IntentFilter sdFilter = new IntentFilter();
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- sdFilter, null, null);
+ sdFilter, null, mCallbackHandler);
IntentFilter offModeFilter = new IntentFilter();
offModeFilter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
offModeFilter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- offModeFilter, null, null);
+ offModeFilter, null, mCallbackHandler);
IntentFilter suspendPackageFilter = new IntentFilter();
suspendPackageFilter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
suspendPackageFilter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL,
- suspendPackageFilter, null, null);
+ suspendPackageFilter, null, mCallbackHandler);
}
private void registerOnCrossProfileProvidersChangedListener() {
@@ -1218,11 +1222,12 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku
try {
// Ask ActivityManager to bind it. Notice that we are binding the service with the
// caller app instead of DevicePolicyManagerService.
- if(ActivityManager.getService().bindService(
+ if (ActivityManager.getService().bindService(
caller, activtiyToken, intent,
intent.resolveTypeIfNeeded(mContext.getContentResolver()),
- connection, flags, mContext.getOpPackageName(),
- widget.provider.getUserId()) != 0) {
+ connection, flags & (Context.BIND_AUTO_CREATE
+ | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE),
+ mContext.getOpPackageName(), widget.provider.getUserId()) != 0) {
// Add it to the mapping of RemoteViewsService to appWidgetIds so that we
// can determine when we can call back to the RemoteViewsService later to
diff --git a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
index 229799a8457d..d5991d3930a8 100644
--- a/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
+++ b/services/companion/java/com/android/server/companion/AssociationStoreImpl.java
@@ -73,6 +73,9 @@ class AssociationStoreImpl implements AssociationStore {
private final Set<OnChangeListener> mListeners = new LinkedHashSet<>();
void addAssociation(@NonNull AssociationInfo association) {
+ // Validity check first.
+ checkNotRevoked(association);
+
final int id = association.getId();
if (DEBUG) {
@@ -99,6 +102,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void updateAssociation(@NonNull AssociationInfo updated) {
+ // Validity check first.
+ checkNotRevoked(updated);
+
final int id = updated.getId();
if (DEBUG) {
@@ -292,6 +298,9 @@ class AssociationStoreImpl implements AssociationStore {
}
void setAssociations(Collection<AssociationInfo> allAssociations) {
+ // Validity check first.
+ allAssociations.forEach(AssociationStoreImpl::checkNotRevoked);
+
if (DEBUG) {
Log.i(TAG, "setAssociations() n=" + allAssociations.size());
final StringJoiner stringJoiner = new StringJoiner(", ");
@@ -324,4 +333,11 @@ class AssociationStoreImpl implements AssociationStore {
mAddressMap.clear();
mCachedPerUser.clear();
}
+
+ private static void checkNotRevoked(@NonNull AssociationInfo association) {
+ if (association.isRevoked()) {
+ throw new IllegalArgumentException(
+ "Revoked (removed) associations MUST NOT appear in the AssociationStore");
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 2714addaec9e..abc49372053e 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -18,6 +18,7 @@
package com.android.server.companion;
import static android.Manifest.permission.MANAGE_COMPANION_DEVICES;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE;
import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.SYSTEM_UID;
@@ -48,6 +49,8 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.ActivityManager.RunningAppProcessInfo;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
import android.app.NotificationManager;
@@ -91,6 +94,7 @@ import android.util.SparseBooleanArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.app.IAppOpsService;
import com.android.internal.content.PackageMonitor;
+import com.android.internal.infra.PerUser;
import com.android.internal.notification.NotificationAccessConfirmationActivityContract;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
@@ -105,6 +109,7 @@ import com.android.server.wm.ActivityTaskManagerInternal;
import java.io.File;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -128,6 +133,9 @@ public class CompanionDeviceManagerService extends SystemService {
private static final long ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT = DAYS.toMillis(90);
+ private final ActivityManager mActivityManager;
+ private final OnPackageVisibilityChangeListener mOnPackageVisibilityChangeListener;
+
private PersistentDataStore mPersistentStore;
private final PersistUserStateHandler mUserPersistenceHandler;
@@ -152,12 +160,40 @@ public class CompanionDeviceManagerService extends SystemService {
@GuardedBy("mPreviouslyUsedIds")
private final SparseArray<Map<String, Set<Integer>>> mPreviouslyUsedIds = new SparseArray<>();
+ /**
+ * A structure that consists of a set of revoked associations that pending for role holder
+ * removal per each user.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final PerUserAssociationSet mRevokedAssociationsPendingRoleHolderRemoval =
+ new PerUserAssociationSet();
+ /**
+ * Contains uid-s of packages pending to be removed from the role holder list (after
+ * revocation of an association), which will happen one the package is no longer visible to the
+ * user.
+ * For quicker uid -> (userId, packageName) look-up this is not a {@code Set<Integer>} but
+ * a {@code Map<Integer, String>} which maps uid-s to packageName-s (userId-s can be derived
+ * from uid-s using {@link UserHandle#getUserId(int)}).
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #addToPendingRoleHolderRemoval(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ */
+ @GuardedBy("mRevokedAssociationsPendingRoleHolderRemoval")
+ private final Map<Integer, String> mUidsPendingRoleHolderRemoval = new HashMap<>();
+
private final RemoteCallbackList<IOnAssociationsChangedListener> mListeners =
new RemoteCallbackList<>();
public CompanionDeviceManagerService(Context context) {
super(context);
+ mActivityManager = context.getSystemService(ActivityManager.class);
mPowerWhitelistManager = context.getSystemService(PowerWhitelistManager.class);
mAppOpsManager = IAppOpsService.Stub.asInterface(
ServiceManager.getService(Context.APP_OPS_SERVICE));
@@ -168,6 +204,9 @@ public class CompanionDeviceManagerService extends SystemService {
mUserPersistenceHandler = new PersistUserStateHandler();
mAssociationStore = new AssociationStoreImpl();
+
+ mOnPackageVisibilityChangeListener =
+ new OnPackageVisibilityChangeListener(mActivityManager);
}
@Override
@@ -204,7 +243,33 @@ public class CompanionDeviceManagerService extends SystemService {
mUserManager.getAliveUsers(), allAssociations, mPreviouslyUsedIds);
}
- mAssociationStore.setAssociations(allAssociations);
+ final Set<AssociationInfo> activeAssociations =
+ new ArraySet<>(/* capacity */ allAssociations.size());
+ // A set contains the userIds that need to persist state after remove the app
+ // from the list of role holders.
+ final Set<Integer> usersToPersistStateFor = new ArraySet<>();
+
+ for (AssociationInfo association : allAssociations) {
+ if (!association.isRevoked()) {
+ activeAssociations.add(association);
+ } else if (maybeRemoveRoleHolderForAssociation(association)) {
+ // Nothing more to do here, but we'll need to persist all the associations to the
+ // disk afterwards.
+ usersToPersistStateFor.add(association.getUserId());
+ } else {
+ addToPendingRoleHolderRemoval(association);
+ }
+ }
+
+ mAssociationStore.setAssociations(activeAssociations);
+
+ // IMPORTANT: only do this AFTER mAssociationStore.setAssociations(), because
+ // persistStateForUser() queries AssociationStore.
+ // (If persistStateForUser() is invoked before mAssociationStore.setAssociations() it
+ // would effectively just clear-out all the persisted associations).
+ for (int userId : usersToPersistStateFor) {
+ persistStateForUser(userId);
+ }
}
@Override
@@ -354,10 +419,18 @@ public class CompanionDeviceManagerService extends SystemService {
}
private void persistStateForUser(@UserIdInt int userId) {
- final List<AssociationInfo> updatedAssociations =
- mAssociationStore.getAssociationsForUser(userId);
+ // We want to store both active associations and the revoked (removed) association that we
+ // are keeping around for the final clean-up (delayed role holder removal).
+ final List<AssociationInfo> allAssociations;
+ // Start with the active associations - these we can get from the AssociationStore.
+ allAssociations = new ArrayList<>(
+ mAssociationStore.getAssociationsForUser(userId));
+ // ... and add the revoked (removed) association, that are yet to be permanently removed.
+ allAssociations.addAll(getPendingRoleHolderRemovalAssociationsForUser(userId));
+
final Map<String, Set<Integer>> usedIdsForUser = getPreviouslyUsedIdsForUser(userId);
- mPersistentStore.persistStateForUser(userId, updatedAssociations, usedIdsForUser);
+
+ mPersistentStore.persistStateForUser(userId, allAssociations, usedIdsForUser);
}
private void notifyListeners(
@@ -425,13 +498,17 @@ public class CompanionDeviceManagerService extends SystemService {
removalWindow = ASSOCIATION_REMOVAL_TIME_WINDOW_DEFAULT;
}
- for (AssociationInfo ai : mAssociationStore.getAssociations()) {
- if (!ai.isSelfManaged()) continue;
- final boolean isInactive = currentTime - ai.getLastTimeConnectedMs() >= removalWindow;
- if (isInactive) {
- Slog.i(TAG, "Removing inactive self-managed association: " + ai.getId());
- disassociateInternal(ai.getId());
- }
+ for (AssociationInfo association : mAssociationStore.getAssociations()) {
+ if (!association.isSelfManaged()) continue;
+
+ final boolean isInactive =
+ currentTime - association.getLastTimeConnectedMs() >= removalWindow;
+ if (!isInactive) continue;
+
+ final int id = association.getId();
+
+ Slog.i(TAG, "Removing inactive self-managed association id=" + id);
+ disassociateInternal(id);
}
}
@@ -671,7 +748,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceCallerIsSystemOr(userId, packageName);
AssociationInfo association = mAssociationStore.getAssociationsForPackageWithAddress(
- userId, packageName, deviceAddress);
+ userId, packageName, deviceAddress);
if (association == null) {
throw new RemoteException(new DeviceNotAssociatedException("App " + packageName
@@ -731,7 +808,7 @@ public class CompanionDeviceManagerService extends SystemService {
enforceUsesCompanionDeviceFeature(getContext(), userId, callingPackage);
checkState(!ArrayUtils.isEmpty(
- mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
+ mAssociationStore.getAssociationsForPackage(userId, callingPackage)),
"App must have an association before calling this API");
}
@@ -791,8 +868,8 @@ public class CompanionDeviceManagerService extends SystemService {
final long timestamp = System.currentTimeMillis();
final AssociationInfo association = new AssociationInfo(id, userId, packageName,
- macAddress, displayName, deviceProfile, selfManaged, false, timestamp,
- Long.MAX_VALUE);
+ macAddress, displayName, deviceProfile, selfManaged,
+ /* notifyOnDeviceNearby */ false, /* revoked */ false, timestamp, Long.MAX_VALUE);
Slog.i(TAG, "New CDM association created=" + association);
mAssociationStore.addAssociation(association);
@@ -804,6 +881,11 @@ public class CompanionDeviceManagerService extends SystemService {
updateSpecialAccessPermissionForAssociatedPackage(association);
logCreateAssociation(deviceProfile);
+
+ // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
+ // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
+ // that there are other devices with the same profile, so the role holder won't be removed.
+
return association;
}
@@ -884,36 +966,184 @@ public class CompanionDeviceManagerService extends SystemService {
final String packageName = association.getPackageName();
final String deviceProfile = association.getDeviceProfile();
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Need to remove the app from list of the role holders, but will have to do it later
+ // (the app is in foreground at the moment).
+ addToPendingRoleHolderRemoval(association);
+ }
+
+ // Need to check if device still present now because CompanionDevicePresenceMonitor will
+ // remove current connected device after mAssociationStore.removeAssociation
final boolean wasPresent = mDevicePresenceMonitor.isDevicePresent(associationId);
// Removing the association.
mAssociationStore.removeAssociation(associationId);
+ // Do not need to persistUserState since CompanionDeviceManagerService will get callback
+ // from #onAssociationChanged, and it will handle the persistUserState which including
+ // active and revoked association.
logRemoveAssociation(deviceProfile);
- final List<AssociationInfo> otherAssociations =
- mAssociationStore.getAssociationsForPackage(userId, packageName);
-
- // Check if the package is associated with other devices with the same profile.
- // If not: take away the role.
- if (deviceProfile != null) {
- final boolean shouldKeepTheRole = any(otherAssociations,
- it -> deviceProfile.equals(it.getDeviceProfile()));
- if (!shouldKeepTheRole) {
- Binder.withCleanCallingIdentity(() ->
- removeRoleHolderForAssociation(getContext(), association));
- }
- }
-
if (!wasPresent || !association.isNotifyOnDeviceNearby()) return;
// The device was connected and the app was notified: check if we need to unbind the app
// now.
- final boolean shouldStayBound = any(otherAssociations,
+ final boolean shouldStayBound = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
it -> it.isNotifyOnDeviceNearby()
&& mDevicePresenceMonitor.isDevicePresent(it.getId()));
if (shouldStayBound) return;
mCompanionAppController.unbindCompanionApplication(userId, packageName);
}
+ /**
+ * First, checks if the companion application should be removed from the list role holders when
+ * upon association's removal, i.e.: association's profile (matches the role) is not null,
+ * the application does not have other associations with the same profile, etc.
+ *
+ * <p>
+ * Then, if establishes that the application indeed has to be removed from the list of the role
+ * holders, checks if it could be done right now -
+ * {@link android.app.role.RoleManager#removeRoleHolderAsUser(String, String, int, UserHandle, java.util.concurrent.Executor, java.util.function.Consumer) RoleManager#removeRoleHolderAsUser()}
+ * will kill the application's process, which leads poor user experience if the application was
+ * in foreground when this happened, to avoid this CDMS delays invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} until the app is no longer in foreground.
+ *
+ * @return {@code true} if the application does NOT need be removed from the list of the role
+ * holders OR if the application was successfully removed from the list of role holders.
+ * I.e.: from the role-management perspective the association is done with.
+ * {@code false} if the application needs to be removed from the list of role the role
+ * holders, BUT it CDMS would prefer to do it later.
+ * I.e.: application is in the foreground at the moment, but invoking
+ * {@code RoleManager.removeRoleHolderAsUser()} will kill the application's process,
+ * which would lead to the poor UX, hence need to try later.
+ */
+
+ private boolean maybeRemoveRoleHolderForAssociation(@NonNull AssociationInfo association) {
+ if (DEBUG) Log.d(TAG, "maybeRemoveRoleHolderForAssociation() association=" + association);
+
+ final String deviceProfile = association.getDeviceProfile();
+ if (deviceProfile == null) {
+ // No role was granted to for this association, there is nothing else we need to here.
+ return true;
+ }
+
+ // Check if the applications is associated with another devices with the profile. If so,
+ // it should remain the role holder.
+ final int id = association.getId();
+ final int userId = association.getUserId();
+ final String packageName = association.getPackageName();
+ final boolean roleStillInUse = any(
+ mAssociationStore.getAssociationsForPackage(userId, packageName),
+ it -> deviceProfile.equals(it.getDeviceProfile()) && id != it.getId());
+ if (roleStillInUse) {
+ // Application should remain a role holder, there is nothing else we need to here.
+ return true;
+ }
+
+ final int packageProcessImportance = getPackageProcessImportance(userId, packageName);
+ if (packageProcessImportance <= IMPORTANCE_VISIBLE) {
+ // Need to remove the app from the list of role holders, but the process is visible to
+ // the user at the moment, so we'll need to it later: log and return false.
+ Slog.i(TAG, "Cannot remove role holder for the removed association id=" + id
+ + " now - process is visible.");
+ return false;
+ }
+
+ removeRoleHolderForAssociation(getContext(), association);
+ return true;
+ }
+
+ private int getPackageProcessImportance(@UserIdInt int userId, @NonNull String packageName) {
+ return Binder.withCleanCallingIdentity(() -> {
+ final int uid =
+ mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+ return mActivityManager.getUidImportance(uid);
+ });
+ }
+
+ /**
+ * Set revoked flag for active association and add the revoked association and the uid into
+ * the caches.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void addToPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ // First: set revoked flag.
+ association = AssociationInfo.builder(association)
+ .setRevoked(true)
+ .build();
+
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+
+ // Second: add to the set.
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(association.getUserId())
+ .add(association);
+ if (!mUidsPendingRoleHolderRemoval.containsKey(uid)) {
+ mUidsPendingRoleHolderRemoval.put(uid, packageName);
+
+ if (mUidsPendingRoleHolderRemoval.size() == 1) {
+ // Just added first uid: start the listener
+ mOnPackageVisibilityChangeListener.startListening();
+ }
+ }
+ }
+ }
+
+ /**
+ * Remove the revoked association form the cache and also remove the uid form the map if
+ * there are other associations with the same package still pending for role holder removal.
+ *
+ * @see #mRevokedAssociationsPendingRoleHolderRemoval
+ * @see #mUidsPendingRoleHolderRemoval
+ * @see OnPackageVisibilityChangeListener
+ */
+ private void removeFromPendingRoleHolderRemoval(@NonNull AssociationInfo association) {
+ final String packageName = association.getPackageName();
+ final int userId = association.getUserId();
+ final int uid = mPackageManagerInternal.getPackageUid(packageName, /* flags */0, userId);
+
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId)
+ .remove(association);
+
+ final boolean shouldKeepUidForRemoval = any(
+ getPendingRoleHolderRemovalAssociationsForUser(userId),
+ ai -> packageName.equals(ai.getPackageName()));
+ // Do not remove the uid form the map since other associations with
+ // the same packageName still pending for role holder removal.
+ if (!shouldKeepUidForRemoval) {
+ mUidsPendingRoleHolderRemoval.remove(uid);
+ }
+
+ if (mUidsPendingRoleHolderRemoval.isEmpty()) {
+ // The set is empty now - can "turn off" the listener.
+ mOnPackageVisibilityChangeListener.stopListening();
+ }
+ }
+ }
+
+ /**
+ * @return a copy of the revoked associations set (safeguarding against
+ * {@code ConcurrentModificationException}-s).
+ */
+ private @NonNull Set<AssociationInfo> getPendingRoleHolderRemovalAssociationsForUser(
+ @UserIdInt int userId) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ // Return a copy.
+ return new ArraySet<>(mRevokedAssociationsPendingRoleHolderRemoval.forUser(userId));
+ }
+ }
+
+ private String getPackageNameByUid(int uid) {
+ synchronized (mRevokedAssociationsPendingRoleHolderRemoval) {
+ return mUidsPendingRoleHolderRemoval.get(uid);
+ }
+ }
+
private void updateSpecialAccessPermissionForAssociatedPackage(AssociationInfo association) {
final PackageInfo packageInfo =
getPackageInfo(getContext(), association.getUserId(), association.getPackageName());
@@ -1134,4 +1364,80 @@ public class CompanionDeviceManagerService extends SystemService {
persistStateForUser(userId);
}
}
+
+ /**
+ * An OnUidImportanceListener class which watches the importance of the packages.
+ * In this class, we ONLY interested in the importance of the running process is greater than
+ * {@link RunningAppProcessInfo.IMPORTANCE_VISIBLE} for the uids have been added into the
+ * {@link mUidsPendingRoleHolderRemoval}. Lastly remove the role holder for the revoked
+ * associations for the same packages.
+ *
+ * @see #maybeRemoveRoleHolderForAssociation(AssociationInfo)
+ * @see #removeFromPendingRoleHolderRemoval(AssociationInfo)
+ * @see #getPendingRoleHolderRemovalAssociationsForUser(int)
+ */
+ private class OnPackageVisibilityChangeListener implements
+ ActivityManager.OnUidImportanceListener {
+ final @NonNull ActivityManager mAm;
+
+ OnPackageVisibilityChangeListener(@NonNull ActivityManager am) {
+ this.mAm = am;
+ }
+
+ void startListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.addOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this,
+ RunningAppProcessInfo.IMPORTANCE_VISIBLE));
+ }
+
+ void stopListening() {
+ Binder.withCleanCallingIdentity(
+ () -> mAm.removeOnUidImportanceListener(
+ /* listener */ OnPackageVisibilityChangeListener.this));
+ }
+
+ @Override
+ public void onUidImportance(int uid, int importance) {
+ if (importance <= RunningAppProcessInfo.IMPORTANCE_VISIBLE) {
+ // The lower the importance value the more "important" the process is.
+ // We are only interested when the process ceases to be visible.
+ return;
+ }
+
+ final String packageName = getPackageNameByUid(uid);
+ if (packageName == null) {
+ // Not interested in this uid.
+ return;
+ }
+
+ final int userId = UserHandle.getUserId(uid);
+
+ boolean needToPersistStateForUser = false;
+
+ for (AssociationInfo association :
+ getPendingRoleHolderRemovalAssociationsForUser(userId)) {
+ if (!packageName.equals(association.getPackageName())) continue;
+
+ if (!maybeRemoveRoleHolderForAssociation(association)) {
+ // Did not remove the role holder, will have to try again later.
+ continue;
+ }
+
+ removeFromPendingRoleHolderRemoval(association);
+ needToPersistStateForUser = true;
+ }
+
+ if (needToPersistStateForUser) {
+ mUserPersistenceHandler.postPersistUserState(userId);
+ }
+ }
+ }
+
+ private static class PerUserAssociationSet extends PerUser<Set<AssociationInfo>> {
+ @Override
+ protected @NonNull Set<AssociationInfo> create(int userId) {
+ return new ArraySet<>();
+ }
+ }
}
diff --git a/services/companion/java/com/android/server/companion/PackageUtils.java b/services/companion/java/com/android/server/companion/PackageUtils.java
index a2b20593a9cb..f523773033d1 100644
--- a/services/companion/java/com/android/server/companion/PackageUtils.java
+++ b/services/companion/java/com/android/server/companion/PackageUtils.java
@@ -30,6 +30,7 @@ import android.companion.CompanionDeviceService;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.FeatureInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.PackageInfoFlags;
@@ -39,8 +40,6 @@ import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.util.Slog;
-import com.android.internal.util.ArrayUtils;
-
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -65,15 +64,20 @@ final class PackageUtils {
static void enforceUsesCompanionDeviceFeature(@NonNull Context context,
@UserIdInt int userId, @NonNull String packageName) {
- final boolean requested = ArrayUtils.contains(
- getPackageInfo(context, userId, packageName).reqFeatures,
- FEATURE_COMPANION_DEVICE_SETUP);
-
- if (requested) {
- throw new IllegalStateException("Must declare uses-feature "
- + FEATURE_COMPANION_DEVICE_SETUP
- + " in manifest to use this API");
+ String requiredFeature = FEATURE_COMPANION_DEVICE_SETUP;
+
+ FeatureInfo[] requestedFeatures = getPackageInfo(context, userId, packageName).reqFeatures;
+ if (requestedFeatures != null) {
+ for (int i = 0; i < requestedFeatures.length; i++) {
+ if (requiredFeature.equals(requestedFeatures[i].name)) {
+ return;
+ }
+ }
}
+
+ throw new IllegalStateException("Must declare uses-feature "
+ + requiredFeature
+ + " in manifest to use this API");
}
/**
diff --git a/services/companion/java/com/android/server/companion/PersistentDataStore.java b/services/companion/java/com/android/server/companion/PersistentDataStore.java
index 36393894f727..4b56c1b28036 100644
--- a/services/companion/java/com/android/server/companion/PersistentDataStore.java
+++ b/services/companion/java/com/android/server/companion/PersistentDataStore.java
@@ -103,7 +103,7 @@ import java.util.concurrent.ConcurrentMap;
* Since Android T the data is stored to "companion_device_manager.xml" file in
* {@link Environment#getDataSystemDeDirectory(int) /data/system_de/}.
*
- * See {@link #getBaseStorageFileForUser(int) getBaseStorageFileForUser()}
+ * See {@link DataStoreUtils#getBaseStorageFileForUser(int, String)}
*
* <p>
* Since Android T the data is stored using the v1 schema.
@@ -120,7 +120,7 @@ import java.util.concurrent.ConcurrentMap;
* <li> {@link #readPreviouslyUsedIdsV1(TypedXmlPullParser, Map) readPreviouslyUsedIdsV1()}
* </ul>
*
- * The following snippet is a sample of a file that is using v0 schema.
+ * The following snippet is a sample of a file that is using v1 schema.
* <pre>{@code
* <state persistence-version="1">
* <associations>
@@ -130,6 +130,8 @@ import java.util.concurrent.ConcurrentMap;
* mac_address="AA:BB:CC:DD:EE:00"
* self_managed="false"
* notify_device_nearby="false"
+ * revoked="false"
+ * last_time_connected="1634641160229"
* time_approved="1634389553216"/>
*
* <association
@@ -139,6 +141,8 @@ import java.util.concurrent.ConcurrentMap;
* display_name="Jhon's Chromebook"
* self_managed="true"
* notify_device_nearby="false"
+ * revoked="false"
+ * last_time_connected="1634641160229"
* time_approved="1634641160229"/>
* </associations>
*
@@ -178,6 +182,7 @@ final class PersistentDataStore {
private static final String XML_ATTR_PROFILE = "profile";
private static final String XML_ATTR_SELF_MANAGED = "self_managed";
private static final String XML_ATTR_NOTIFY_DEVICE_NEARBY = "notify_device_nearby";
+ private static final String XML_ATTR_REVOKED = "revoked";
private static final String XML_ATTR_TIME_APPROVED = "time_approved";
private static final String XML_ATTR_LAST_TIME_CONNECTED = "last_time_connected";
@@ -415,7 +420,8 @@ final class PersistentDataStore {
out.add(new AssociationInfo(associationId, userId, appPackage,
MacAddress.fromString(deviceAddress), null, profile,
- /* managedByCompanionApp */false, notify, timeApproved, Long.MAX_VALUE));
+ /* managedByCompanionApp */ false, notify, /* revoked */ false, timeApproved,
+ Long.MAX_VALUE));
}
private static void readAssociationsV1(@NonNull TypedXmlPullParser parser,
@@ -444,13 +450,14 @@ final class PersistentDataStore {
final String displayName = readStringAttribute(parser, XML_ATTR_DISPLAY_NAME);
final boolean selfManaged = readBooleanAttribute(parser, XML_ATTR_SELF_MANAGED);
final boolean notify = readBooleanAttribute(parser, XML_ATTR_NOTIFY_DEVICE_NEARBY);
+ final boolean revoked = readBooleanAttribute(parser, XML_ATTR_REVOKED, false);
final long timeApproved = readLongAttribute(parser, XML_ATTR_TIME_APPROVED, 0L);
final long lastTimeConnected = readLongAttribute(
parser, XML_ATTR_LAST_TIME_CONNECTED, Long.MAX_VALUE);
final AssociationInfo associationInfo = createAssociationInfoNoThrow(associationId, userId,
- appPackage, macAddress, displayName, profile, selfManaged, notify, timeApproved,
- lastTimeConnected);
+ appPackage, macAddress, displayName, profile, selfManaged, notify, revoked,
+ timeApproved, lastTimeConnected);
if (associationInfo != null) {
out.add(associationInfo);
}
@@ -503,6 +510,8 @@ final class PersistentDataStore {
writeBooleanAttribute(serializer, XML_ATTR_SELF_MANAGED, a.isSelfManaged());
writeBooleanAttribute(
serializer, XML_ATTR_NOTIFY_DEVICE_NEARBY, a.isNotifyOnDeviceNearby());
+ writeBooleanAttribute(
+ serializer, XML_ATTR_REVOKED, a.isRevoked());
writeLongAttribute(serializer, XML_ATTR_TIME_APPROVED, a.getTimeApprovedMs());
writeLongAttribute(
serializer, XML_ATTR_LAST_TIME_CONNECTED, a.getLastTimeConnectedMs());
@@ -544,11 +553,12 @@ final class PersistentDataStore {
private static AssociationInfo createAssociationInfoNoThrow(int associationId,
@UserIdInt int userId, @NonNull String appPackage, @Nullable MacAddress macAddress,
@Nullable CharSequence displayName, @Nullable String profile, boolean selfManaged,
- boolean notify, long timeApproved, long lastTimeConnected) {
+ boolean notify, boolean revoked, long timeApproved, long lastTimeConnected) {
AssociationInfo associationInfo = null;
try {
associationInfo = new AssociationInfo(associationId, userId, appPackage, macAddress,
- displayName, profile, selfManaged, notify, timeApproved, lastTimeConnected);
+ displayName, profile, selfManaged, notify, revoked, timeApproved,
+ lastTimeConnected);
} catch (Exception e) {
if (DEBUG) Log.w(TAG, "Could not create AssociationInfo", e);
}
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 35488a80b78b..0fff3f488562 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -85,6 +85,8 @@ final class RolesUtils {
final int userId = associationInfo.getUserId();
final UserHandle userHandle = UserHandle.of(userId);
+ Slog.i(TAG, "Removing CDM role holder, role=" + deviceProfile
+ + ", package=u" + userId + "\\" + packageName);
roleManager.removeRoleHolderAsUser(deviceProfile, packageName,
MANAGE_HOLDERS_FLAG_DONT_KILL_APP, userHandle, context.getMainExecutor(),
success -> {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index aa3bb22a2d35..987af4b415d9 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -2297,13 +2297,9 @@ public class ActivityManagerService extends IActivityManager.Stub
return mAppOpsManager;
}
- /**
- * Provides the basic functionality for activity task related tests when a handler thread is
- * given to initialize the dependency members.
- */
+ /** Provides the basic functionality for unit tests. */
@VisibleForTesting
- public ActivityManagerService(Injector injector, ServiceThread handlerThread) {
- final boolean hasHandlerThread = handlerThread != null;
+ ActivityManagerService(Injector injector, @NonNull ServiceThread handlerThread) {
mInjector = injector;
mContext = mInjector.getContext();
mUiContext = null;
@@ -2311,33 +2307,27 @@ public class ActivityManagerService extends IActivityManager.Stub
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
mBatteryStatsService = null;
- mHandler = hasHandlerThread ? new MainHandler(handlerThread.getLooper()) : null;
+ mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
- mConstants = hasHandlerThread
- ? new ActivityManagerConstants(mContext, this, mHandler) : null;
+ mConstants = new ActivityManagerConstants(mContext, this, mHandler);
final ActiveUids activeUids = new ActiveUids(this, false /* postChangesToAtm */);
mPlatformCompat = null;
mProcessList = injector.getProcessList(this);
mProcessList.init(this, activeUids, mPlatformCompat);
mAppProfiler = new AppProfiler(this, BackgroundThread.getHandler().getLooper(), null);
mPhantomProcessList = new PhantomProcessList(this);
- mOomAdjuster = hasHandlerThread
- ? new OomAdjuster(this, mProcessList, activeUids, handlerThread) : null;
+ mOomAdjuster = new OomAdjuster(this, mProcessList, activeUids, handlerThread);
- mIntentFirewall = hasHandlerThread
- ? new IntentFirewall(new IntentFirewallInterface(), mHandler) : null;
+ mIntentFirewall = null;
mProcessStats = null;
mCpHelper = new ContentProviderHelper(this, false);
- // For the usage of {@link ActiveServices#cleanUpServices} that may be invoked from
- // {@link ActivityTaskSupervisor#cleanUpRemovedTaskLocked}.
- mServices = hasHandlerThread ? new ActiveServices(this) : null;
+ mServices = null;
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
- mUserController = hasHandlerThread ? new UserController(this) : null;
- mPendingIntentController = hasHandlerThread
- ? new PendingIntentController(handlerThread.getLooper(), mUserController,
- mConstants) : null;
+ mUserController = new UserController(this);
+ mPendingIntentController =
+ new PendingIntentController(handlerThread.getLooper(), mUserController, mConstants);
mAppRestrictionController = new AppRestrictionController(mContext, this);
mProcStartHandlerThread = null;
mProcStartHandler = null;
@@ -2346,7 +2336,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mFactoryTest = FACTORY_TEST_OFF;
mUgmInternal = LocalServices.getService(UriGrantsManagerInternal.class);
mInternal = new LocalService();
- mPendingStartActivityUids = new PendingStartActivityUids(mContext);
+ mPendingStartActivityUids = new PendingStartActivityUids();
mUseFifoUiScheduling = false;
mEnableOffloadQueue = false;
mFgBroadcastQueue = mBgBroadcastQueue = mBgOffloadBroadcastQueue =
@@ -2482,7 +2472,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mInternal = new LocalService();
- mPendingStartActivityUids = new PendingStartActivityUids(mContext);
+ mPendingStartActivityUids = new PendingStartActivityUids();
mTraceErrorLogger = new TraceErrorLogger();
mComponentAliasResolver = new ComponentAliasResolver(this);
}
@@ -14399,6 +14389,19 @@ public class ActivityManagerService extends IActivityManager.Stub
final int callingPid = Binder.getCallingPid();
final int callingUid = Binder.getCallingUid();
+ // Non-system callers can't declare that a broadcast is alarm-related.
+ // The PendingIntent invocation case is handled in PendingIntentRecord.
+ if (bOptions != null && callingUid != SYSTEM_UID) {
+ if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+ if (DEBUG_BROADCAST) {
+ Slog.w(TAG, "Non-system caller " + callingUid
+ + " may not flag broadcast as alarm-related");
+ }
+ throw new SecurityException(
+ "Non-system callers may not flag broadcasts as alarm-related");
+ }
+ }
+
final long origId = Binder.clearCallingIdentity();
try {
return broadcastIntentLocked(callerApp,
@@ -14412,6 +14415,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
+ // Not the binder call surface
int broadcastIntentInPackage(String packageName, @Nullable String featureId, int uid,
int realCallingUid, int realCallingPid, Intent intent, String resolvedType,
IIntentReceiver resultTo, int resultCode, String resultData, Bundle resultExtras,
@@ -17388,7 +17392,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// next top activity on time. This race will fail the following binder transactions WM
// sends to the activity. After this race issue between WM/ATMS and AMS is solved, this
// workaround can be removed. (b/213288355)
- if (isNewPending && mOomAdjuster != null) { // It can be null in unit test.
+ if (isNewPending) {
mOomAdjuster.mCachedAppOptimizer.unfreezeProcess(pid);
}
// We need to update the network rules for the app coming to the top state so that
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 19ffc1733f3d..ae91d75ef0ce 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -70,6 +70,7 @@ final class BroadcastRecord extends Binder {
final boolean callerInstantApp; // caller is an Instant App?
final boolean ordered; // serialize the send to receivers?
final boolean sticky; // originated from existing sticky data?
+ final boolean alarm; // originated from an alarm triggering?
final boolean initialSticky; // initial broadcast from register to sticky?
final int userId; // user id this broadcast was for
final String resolvedType; // the resolved data type
@@ -305,6 +306,7 @@ final class BroadcastRecord extends Binder {
this.allowBackgroundActivityStarts = allowBackgroundActivityStarts;
mBackgroundActivityStartsToken = backgroundActivityStartsToken;
this.timeoutExempt = timeoutExempt;
+ alarm = options != null && options.isAlarmBroadcast();
}
/**
@@ -357,6 +359,7 @@ final class BroadcastRecord extends Binder {
allowBackgroundActivityStarts = from.allowBackgroundActivityStarts;
mBackgroundActivityStartsToken = from.mBackgroundActivityStartsToken;
timeoutExempt = from.timeoutExempt;
+ alarm = from.alarm;
}
/**
diff --git a/services/core/java/com/android/server/am/ContentProviderHelper.java b/services/core/java/com/android/server/am/ContentProviderHelper.java
index 728792f608ef..453385938aca 100644
--- a/services/core/java/com/android/server/am/ContentProviderHelper.java
+++ b/services/core/java/com/android/server/am/ContentProviderHelper.java
@@ -22,6 +22,9 @@ import static android.os.Process.PROC_PARENS;
import static android.os.Process.PROC_SPACE_TERM;
import static android.os.Process.SYSTEM_UID;
+import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD;
+import static com.android.internal.util.FrameworkStatsLog.PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
import static com.android.server.am.ActivityManagerService.TAG_MU;
import static com.android.server.am.ProcessProfileRecord.HOSTING_COMPONENT_TYPE_PROVIDER;
@@ -73,6 +76,7 @@ import android.util.SparseArray;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.RescueParty;
import com.android.server.pm.UserManagerInternal;
@@ -241,6 +245,10 @@ public class ContentProviderHelper {
ContentProviderHolder holder = cpr.newHolder(null, true);
// don't give caller the provider object, it needs to make its own.
holder.provider = null;
+ FrameworkStatsLog.write(
+ PROVIDER_ACQUISITION_EVENT_REPORTED,
+ r.uid, callingUid,
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
return holder;
}
@@ -307,6 +315,10 @@ public class ContentProviderHelper {
dyingProc = cpr.proc;
} else {
cpr.proc.mState.setVerifiedAdj(cpr.proc.mState.getSetAdj());
+ FrameworkStatsLog.write(
+ PROVIDER_ACQUISITION_EVENT_REPORTED,
+ cpr.proc.uid, callingUid,
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -479,6 +491,10 @@ public class ContentProviderHelper {
} catch (RemoteException e) {
}
}
+ FrameworkStatsLog.write(
+ PROVIDER_ACQUISITION_EVENT_REPORTED,
+ proc.uid, callingUid,
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM);
} else {
checkTime(startTime, "getContentProviderImpl: before start process");
proc = mService.startProcessLocked(
@@ -495,6 +511,10 @@ public class ContentProviderHelper {
+ ": process is bad");
return null;
}
+ FrameworkStatsLog.write(
+ PROVIDER_ACQUISITION_EVENT_REPORTED,
+ proc.uid, callingUid,
+ PROVIDER_ACQUISITION_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_COLD);
}
cpr.launchingApp = proc;
mLaunchingProviders.add(cpr);
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index f7fbbe4ebead..e9658dbc7b1a 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -1556,14 +1556,22 @@ public class OomAdjuster {
boolean foregroundActivities = false;
boolean hasVisibleActivities = false;
- if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP && app == topApp) {
+ if (app == topApp && (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP
+ || PROCESS_STATE_CUR_TOP == PROCESS_STATE_IMPORTANT_FOREGROUND)) {
// The last app on the list is the foreground app.
adj = ProcessList.FOREGROUND_APP_ADJ;
- schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
- state.setAdjType("top-activity");
+ if (PROCESS_STATE_CUR_TOP == PROCESS_STATE_TOP) {
+ schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
+ state.setAdjType("top-activity");
+ } else {
+ // Demote the scheduling group to avoid CPU contention if there is another more
+ // important process which also uses top-app, such as if SystemUI is animating.
+ schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
+ state.setAdjType("intermediate-top-activity");
+ }
foregroundActivities = true;
hasVisibleActivities = true;
- procState = PROCESS_STATE_CUR_TOP;
+ procState = PROCESS_STATE_TOP;
if (DEBUG_OOM_ADJ_REASON || logUid == appUid) {
reportOomAdjMessageLocked(TAG_OOM_ADJ, "Making top: " + app);
}
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 4044cceb606b..bda60ff2172b 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,6 +18,7 @@ package com.android.server.am;
import static android.app.ActivityManager.START_SUCCESS;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -34,6 +35,7 @@ import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerWhitelistManager;
import android.os.PowerWhitelistManager.ReasonCode;
+import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;
@@ -416,6 +418,22 @@ public final class PendingIntentRecord extends IIntentSender.Stub {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
+
+ // Only system senders can declare a broadcast to be alarm-originated. We check
+ // this here rather than in the general case handling below to fail before the other
+ // invocation side effects such as allowlisting.
+ if (options != null && callingUid != Process.SYSTEM_UID
+ && key.type == ActivityManager.INTENT_SENDER_BROADCAST) {
+ if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+ if (DEBUG_BROADCAST_LIGHT) {
+ Slog.w(TAG, "Non-system caller " + callingUid
+ + " may not flag broadcast as alarm-related");
+ }
+ throw new SecurityException(
+ "Non-system callers may not flag broadcasts as alarm-related");
+ }
+ }
+
final long origId = Binder.clearCallingIdentity();
int res = START_SUCCESS;
diff --git a/services/core/java/com/android/server/am/PendingStartActivityUids.java b/services/core/java/com/android/server/am/PendingStartActivityUids.java
index bd600571f0a3..da09317add90 100644
--- a/services/core/java/com/android/server/am/PendingStartActivityUids.java
+++ b/services/core/java/com/android/server/am/PendingStartActivityUids.java
@@ -16,7 +16,6 @@
package com.android.server.am;
-import android.content.Context;
import android.os.SystemClock;
import android.util.Pair;
import android.util.Slog;
@@ -40,11 +39,6 @@ final class PendingStartActivityUids {
// Key is uid, value is Pair of pid and SystemClock.elapsedRealtime() when the
// uid is added.
private final SparseArray<Pair<Integer, Long>> mPendingUids = new SparseArray();
- private Context mContext;
-
- PendingStartActivityUids(Context context) {
- mContext = context;
- }
/** Returns {@code true} if the uid is put to the pending array. Otherwise it existed. */
synchronized boolean add(int uid, int pid) {
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 178b6bbe755e..be0bef2f4ddb 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -2269,7 +2269,9 @@ public final class ProcessList {
final boolean inBgRestricted = ast.isAppBackgroundRestricted(
app.info.uid, app.info.packageName);
if (inBgRestricted) {
- mAppsInBackgroundRestricted.add(app);
+ synchronized (mService) {
+ mAppsInBackgroundRestricted.add(app);
+ }
}
app.mState.setBackgroundRestricted(inBgRestricted);
}
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index 3a789741842f..dd73cbed06d9 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -60,6 +60,7 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.UserInfo;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.content.res.XmlResourceParser;
@@ -68,6 +69,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
+import android.os.FileUtils;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@@ -77,9 +79,12 @@ import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.ShellCallback;
+import android.os.UserManager;
import android.provider.DeviceConfig;
import android.provider.DeviceConfig.Properties;
+import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.AtomicFile;
import android.util.AttributeSet;
import android.util.KeyValueListParser;
import android.util.Slog;
@@ -90,6 +95,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.compat.CompatibilityOverrideConfig;
import com.android.internal.compat.IPlatformCompat;
import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.LocalServices;
import com.android.server.ServiceThread;
@@ -99,9 +105,17 @@ import com.android.server.SystemService.TargetUser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
+import java.io.BufferedWriter;
+import java.io.File;
import java.io.FileDescriptor;
+import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStreamWriter;
import java.io.PrintWriter;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
/**
@@ -122,6 +136,7 @@ public final class GameManagerService extends IGameManagerService.Stub {
static final int POPULATE_GAME_MODE_SETTINGS = 3;
static final int SET_GAME_STATE = 4;
static final int CANCEL_GAME_LOADING_MODE = 5;
+ static final int WRITE_GAME_MODE_INTERVENTION_LIST_FILE = 6;
static final int WRITE_SETTINGS_DELAY = 10 * 1000; // 10 seconds
static final int LOADING_BOOST_MAX_DURATION = 5 * 1000; // 5 seconds
@@ -131,6 +146,8 @@ public final class GameManagerService extends IGameManagerService.Stub {
.build();
private static final String PACKAGE_NAME_MSG_KEY = "packageName";
private static final String USER_ID_MSG_KEY = "userId";
+ private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME =
+ "game_mode_intervention.list";
private final Context mContext;
private final Object mLock = new Object();
@@ -138,8 +155,12 @@ public final class GameManagerService extends IGameManagerService.Stub {
private final Object mOverrideConfigLock = new Object();
private final Handler mHandler;
private final PackageManager mPackageManager;
+ private final UserManager mUserManager;
private final IPlatformCompat mPlatformCompat;
private final PowerManagerInternal mPowerManagerInternal;
+ private final File mSystemDir;
+ @VisibleForTesting
+ final AtomicFile mGameModeInterventionListFile;
private DeviceConfigListener mDeviceConfigListener;
@GuardedBy("mLock")
private final ArrayMap<Integer, GameManagerSettings> mSettings = new ArrayMap<>();
@@ -158,9 +179,53 @@ public final class GameManagerService extends IGameManagerService.Stub {
mContext = context;
mHandler = new SettingsHandler(looper);
mPackageManager = mContext.getPackageManager();
+ mUserManager = mContext.getSystemService(UserManager.class);
mPlatformCompat = IPlatformCompat.Stub.asInterface(
ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mSystemDir = new File(Environment.getDataDirectory(), "system");
+ mSystemDir.mkdirs();
+ FileUtils.setPermissions(mSystemDir.toString(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+ -1, -1);
+ mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
+ GAME_MODE_INTERVENTION_LIST_FILE_NAME));
+ FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR
+ | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ -1, -1);
+ if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
+ mGameServiceController = new GameServiceController(
+ context, BackgroundThread.getExecutor(),
+ new GameServiceProviderSelectorImpl(
+ context.getResources(),
+ context.getPackageManager()),
+ new GameServiceProviderInstanceFactoryImpl(context));
+ } else {
+ mGameServiceController = null;
+ }
+ }
+
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+ GameManagerService(Context context, Looper looper, File dataDir) {
+ mContext = context;
+ mHandler = new SettingsHandler(looper);
+ mPackageManager = mContext.getPackageManager();
+ mUserManager = mContext.getSystemService(UserManager.class);
+ mPlatformCompat = IPlatformCompat.Stub.asInterface(
+ ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+ mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);
+ mSystemDir = new File(dataDir, "system");
+ mSystemDir.mkdirs();
+ FileUtils.setPermissions(mSystemDir.toString(),
+ FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IROTH | FileUtils.S_IXOTH,
+ -1, -1);
+ mGameModeInterventionListFile = new AtomicFile(new File(mSystemDir,
+ GAME_MODE_INTERVENTION_LIST_FILE_NAME));
+ FileUtils.setPermissions(mGameModeInterventionListFile.getBaseFile().getAbsolutePath(),
+ FileUtils.S_IRUSR | FileUtils.S_IWUSR
+ | FileUtils.S_IRGRP | FileUtils.S_IWGRP,
+ -1, -1);
if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_GAME_SERVICE)) {
mGameServiceController = new GameServiceController(
context, BackgroundThread.getExecutor(),
@@ -306,6 +371,22 @@ public final class GameManagerService extends IGameManagerService.Stub {
mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
break;
}
+ case WRITE_GAME_MODE_INTERVENTION_LIST_FILE: {
+ final int userId = (int) msg.obj;
+ if (userId < 0) {
+ Slog.wtf(TAG, "Attempt to write setting for invalid user: " + userId);
+ synchronized (mLock) {
+ removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+ }
+ break;
+ }
+
+ Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
+ removeMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, null);
+ writeGameModeInterventionsToFile(userId);
+ Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
+ break;
+ }
}
}
}
@@ -955,6 +1036,11 @@ public final class GameManagerService extends IGameManagerService.Stub {
}
}
updateInterventions(packageName, gameMode, userId);
+ final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
+ msg.obj = userId;
+ if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
+ mHandler.sendMessage(msg);
+ }
}
/**
@@ -1520,15 +1606,98 @@ public final class GameManagerService extends IGameManagerService.Stub {
final int newGameMode = getNewGameMode(gameMode, config);
if (newGameMode != gameMode) {
setGameMode(packageName, newGameMode, userId);
+ } else {
+ // Make sure we handle the case when the interventions are changed while
+ // the game mode remains the same. We call only updateInterventions() here.
+ updateInterventions(packageName, gameMode, userId);
}
- updateInterventions(packageName, gameMode, userId);
}
} catch (Exception e) {
Slog.e(TAG, "Failed to update compat modes for user " + userId + ": " + e);
}
+
+ final Message msg = mHandler.obtainMessage(WRITE_GAME_MODE_INTERVENTION_LIST_FILE);
+ msg.obj = userId;
+ if (!mHandler.hasEqualMessages(WRITE_GAME_MODE_INTERVENTION_LIST_FILE, userId)) {
+ mHandler.sendMessage(msg);
+ }
+ }
+
+ /*
+ Write the interventions and mode of each game to file /system/data/game_mode_intervention.list
+ Each line will contain the information of each game, separated by tab.
+ The format of the output is:
+ <package name> <UID> <current mode> <game mode 1> <interventions> <game mode 2> <interventions>
+ For example:
+ com.android.app1 1425 1 2 angle=0,scaling=1.0,fps=60 3 angle=1,scaling=0.5,fps=30
+ */
+ private void writeGameModeInterventionsToFile(@UserIdInt int userId) {
+ FileOutputStream fileOutputStream = null;
+ BufferedWriter bufferedWriter;
+ try {
+ fileOutputStream = mGameModeInterventionListFile.startWrite();
+ bufferedWriter = new BufferedWriter(new OutputStreamWriter(fileOutputStream,
+ Charset.defaultCharset()));
+
+ final StringBuilder sb = new StringBuilder();
+ final List<String> installedGamesList = getInstalledGamePackageNamesByAllUsers(userId);
+ for (final String packageName : installedGamesList) {
+ GamePackageConfiguration packageConfig = getConfig(packageName);
+ if (packageConfig == null) {
+ continue;
+ }
+ sb.append(packageName);
+ sb.append("\t");
+ sb.append(mPackageManager.getPackageUidAsUser(packageName, userId));
+ sb.append("\t");
+ sb.append(getGameMode(packageName, userId));
+ sb.append("\t");
+ final int[] modes = packageConfig.getAvailableGameModes();
+ for (int mode : modes) {
+ final GamePackageConfiguration.GameModeConfiguration gameModeConfiguration =
+ packageConfig.getGameModeConfiguration(mode);
+ if (gameModeConfiguration == null) {
+ continue;
+ }
+ sb.append(mode);
+ sb.append("\t");
+ final int useAngle = gameModeConfiguration.getUseAngle() ? 1 : 0;
+ sb.append(TextUtils.formatSimple("angle=%d", useAngle));
+ sb.append(",");
+ final String scaling = gameModeConfiguration.getScaling();
+ sb.append("scaling=");
+ sb.append(scaling);
+ sb.append(",");
+ final int fps = gameModeConfiguration.getFps();
+ sb.append(TextUtils.formatSimple("fps=%d", fps));
+ sb.append("\t");
+ }
+ sb.append("\n");
+ }
+ bufferedWriter.append(sb);
+ bufferedWriter.flush();
+ FileUtils.sync(fileOutputStream);
+ mGameModeInterventionListFile.finishWrite(fileOutputStream);
+ } catch (Exception e) {
+ mGameModeInterventionListFile.failWrite(fileOutputStream);
+ Slog.wtf(TAG, "Failed to write game_mode_intervention.list, exception " + e);
+ }
+ return;
+ }
+
+ private int[] getAllUserIds(@UserIdInt int currentUserId) {
+ final List<UserInfo> users = mUserManager.getUsers();
+ int[] userIds = new int[users.size()];
+ for (int i = 0; i < userIds.length; ++i) {
+ userIds[i] = users.get(i).id;
+ }
+ if (currentUserId != -1) {
+ userIds = ArrayUtils.appendInt(userIds, currentUserId);
+ }
+ return userIds;
}
- private String[] getInstalledGamePackageNames(int userId) {
+ private String[] getInstalledGamePackageNames(@UserIdInt int userId) {
final List<PackageInfo> packages =
mPackageManager.getInstalledPackagesAsUser(0, userId);
return packages.stream().filter(e -> e.applicationInfo != null && e.applicationInfo.category
@@ -1537,6 +1706,17 @@ public final class GameManagerService extends IGameManagerService.Stub {
.toArray(String[]::new);
}
+ private List<String> getInstalledGamePackageNamesByAllUsers(@UserIdInt int currentUserId) {
+ HashSet<String> packageSet = new HashSet<>();
+
+ final int[] userIds = getAllUserIds(currentUserId);
+ for (int userId : userIds) {
+ packageSet.addAll(Arrays.asList(getInstalledGamePackageNames(userId)));
+ }
+
+ return new ArrayList<>(packageSet);
+ }
+
/**
* @hide
*/
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 61cec77ab2da..66f6f1541752 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -275,6 +275,25 @@ public class AudioService extends IAudioService.Stub
// indicates whether the system maps all streams to a single stream.
private final boolean mIsSingleVolume;
+ /**
+ * indicates whether STREAM_NOTIFICATION is aliased to STREAM_RING
+ * not final due to test method, see {@link #setNotifAliasRingForTest(boolean)}.
+ */
+ private boolean mNotifAliasRing;
+
+ /**
+ * Test method to temporarily override whether STREAM_NOTIFICATION is aliased to STREAM_RING,
+ * volumes will be updated in case of a change.
+ * @param alias if true, STREAM_NOTIFICATION is aliased to STREAM_RING
+ */
+ /*package*/ void setNotifAliasRingForTest(boolean alias) {
+ boolean update = (mNotifAliasRing != alias);
+ mNotifAliasRing = alias;
+ if (update) {
+ updateStreamVolumeAlias(true, "AudioServiceTest");
+ }
+ }
+
/*package*/ boolean isPlatformVoice() {
return mPlatformType == AudioSystem.PLATFORM_VOICE;
}
@@ -944,10 +963,25 @@ public class AudioService extends IAudioService.Stub
*/
public AudioService(Context context, AudioSystemAdapter audioSystem,
SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper) {
+ this (context, audioSystem, systemServer, settings, looper,
+ context.getSystemService(AppOpsManager.class));
+ }
+
+ /**
+ * @param context
+ * @param audioSystem Adapter for {@link AudioSystem}
+ * @param systemServer Adapter for privilieged functionality for system server components
+ * @param settings Adapter for {@link Settings}
+ * @param looper Looper to use for the service's message handler. If this is null, an
+ * {@link AudioSystemThread} is created as the messaging thread instead.
+ */
+ public AudioService(Context context, AudioSystemAdapter audioSystem,
+ SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
+ AppOpsManager appOps) {
sLifecycleLogger.log(new AudioEventLogger.StringEvent("AudioService()"));
mContext = context;
mContentResolver = context.getContentResolver();
- mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
+ mAppOps = appOps;
mAudioSystem = audioSystem;
mSystemServer = systemServer;
@@ -978,6 +1012,9 @@ public class AudioService extends IAudioService.Stub
mUseVolumeGroupAliases = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
+ mNotifAliasRing = mContext.getResources().getBoolean(
+ com.android.internal.R.bool.config_alias_ring_notif_stream_types);
+
// Initialize volume
// Priority 1 - Android Property
// Priority 2 - Audio Policy Service
@@ -2026,7 +2063,13 @@ public class AudioService extends IAudioService.Stub
pw.println("\nStream volumes (device: index)");
int numStreamTypes = AudioSystem.getNumStreamTypes();
for (int i = 0; i < numStreamTypes; i++) {
- pw.println("- " + AudioSystem.STREAM_NAMES[i] + ":");
+ StringBuilder alias = new StringBuilder();
+ if (mStreamVolumeAlias[i] != i) {
+ alias.append(" (aliased to: ")
+ .append(AudioSystem.STREAM_NAMES[mStreamVolumeAlias[i]])
+ .append(")");
+ }
+ pw.println("- " + AudioSystem.STREAM_NAMES[i] + alias + ":");
mStreamStates[i].dump(pw);
pw.println("");
}
@@ -2058,6 +2101,10 @@ public class AudioService extends IAudioService.Stub
mStreamVolumeAlias = STREAM_VOLUME_ALIAS_DEFAULT;
dtmfStreamAlias = AudioSystem.STREAM_MUSIC;
}
+ if (!mNotifAliasRing) {
+ mStreamVolumeAlias[AudioSystem.STREAM_NOTIFICATION] =
+ AudioSystem.STREAM_NOTIFICATION;
+ }
}
if (mIsSingleVolume) {
@@ -9168,11 +9215,8 @@ public class AudioService extends IAudioService.Stub
Settings.Secure.SPATIAL_AUDIO_ENABLED, UserHandle.USER_CURRENT);
if (settings == null) {
Log.e(TAG, "error reading spatial audio device settings");
- } else {
- Log.v(TAG, "restoring spatial audio device settings: " + settings);
- mSpatializerHelper.setSADeviceSettings(settings);
}
- mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect);
+ mSpatializerHelper.init(/*effectExpected*/ mHasSpatializerEffect, settings);
mSpatializerHelper.setFeatureEnabled(mHasSpatializerEffect);
}
@@ -9950,6 +9994,7 @@ public class AudioService extends IAudioService.Stub
pw.print(" mBtScoOnByApp="); pw.println(mBtScoOnByApp);
pw.print(" mIsSingleVolume="); pw.println(mIsSingleVolume);
pw.print(" mUseFixedVolume="); pw.println(mUseFixedVolume);
+ pw.print(" mNotifAliasRing="); pw.println(mNotifAliasRing);
pw.print(" mFixedVolumeDevices="); pw.println(dumpDeviceTypes(mFixedVolumeDevices));
pw.print(" mFullVolumeDevices="); pw.println(dumpDeviceTypes(mFullVolumeDevices));
pw.print(" mAbsoluteVolumeDevices.keySet()="); pw.println(dumpDeviceTypes(
@@ -10359,6 +10404,11 @@ public class AudioService extends IAudioService.Stub
@Override
public void setAccessibilityServiceUids(IntArray uids) {
+ // TODO(b/233287010): Fix voice interaction and a11y concurrency in audio policy service
+ if (isPlatformAutomotive()) {
+ return;
+ }
+
synchronized (mAccessibilityServiceUidsLock) {
if (uids.size() == 0) {
mAccessibilityServiceUids = null;
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 5b26672c7de2..1def72b2987f 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -46,6 +46,8 @@ import android.util.Log;
import android.util.Pair;
import android.util.SparseIntArray;
+import com.android.internal.annotations.GuardedBy;
+
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
@@ -103,10 +105,6 @@ public class SpatializerHelper {
AudioDeviceInfo.TYPE_BLE_BROADCAST
};
- private static final int[] WIRELESS_SPEAKER_TYPES = {
- AudioDeviceInfo.TYPE_BLE_SPEAKER,
- };
-
// Spatializer state machine
private static final int STATE_UNINITIALIZED = 0;
private static final int STATE_NOT_SUPPORTED = 1;
@@ -166,6 +164,7 @@ public class SpatializerHelper {
* List of devices where Spatial Audio is possible. Each device can be enabled or disabled
* (== user choice to use or not)
*/
+ @GuardedBy("this")
private final ArrayList<SADeviceState> mSADevices = new ArrayList<>(0);
//------------------------------------------------------
@@ -175,7 +174,7 @@ public class SpatializerHelper {
mASA = asa;
}
- synchronized void init(boolean effectExpected) {
+ synchronized void init(boolean effectExpected, @Nullable String settings) {
loglogi("init effectExpected=" + effectExpected);
if (!effectExpected) {
loglogi("init(): setting state to STATE_NOT_SUPPORTED due to effect not expected");
@@ -278,20 +277,22 @@ public class SpatializerHelper {
mSACapableDeviceTypes.add(SPAT_MODE_FOR_DEVICE_TYPE.keyAt(i));
}
}
+
+ // When initialized from AudioService, the settings string will be non-null.
+ // Saved settings need to be applied after spatialization support is initialized above.
+ if (settings != null) {
+ setSADeviceSettings(settings);
+ }
+
// for both transaural / binaural, we are not forcing enablement as the init() method
// could have been called another time after boot in case of audioserver restart
- if (mTransauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
- false /*forceEnable*/);
- }
- if (mBinauralSupported) {
- // not force-enabling as this device might already be in the device list
- addCompatibleAudioDevice(
- new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
- false /*forceEnable*/);
- }
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+ false /*forceEnable*/);
+ // not force-enabling as this device might already be in the device list
+ addCompatibleAudioDevice(
+ new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_WIRED_HEADPHONE, ""),
+ false /*forceEnable*/);
} catch (RemoteException e) {
resetCapabilities();
} finally {
@@ -321,7 +322,7 @@ public class SpatializerHelper {
mState = STATE_UNINITIALIZED;
mSpatLevel = Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE;
mActualHeadTrackingMode = Spatializer.HEAD_TRACKING_MODE_UNSUPPORTED;
- init(true);
+ init(true, null /* settings */);
setSpatializerEnabledInt(featureEnabled);
}
@@ -497,10 +498,9 @@ public class SpatializerHelper {
synchronized @NonNull List<AudioDeviceAttributes> getCompatibleAudioDevices() {
// build unionOf(mCompatibleAudioDevices, mEnabledDevice) - mDisabledAudioDevices
ArrayList<AudioDeviceAttributes> compatList = new ArrayList<>();
- for (SADeviceState dev : mSADevices) {
- if (dev.mEnabled) {
- compatList.add(new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
- dev.mDeviceType, dev.mDeviceAddress == null ? "" : dev.mDeviceAddress));
+ for (SADeviceState deviceState : mSADevices) {
+ if (deviceState.mEnabled) {
+ compatList.add(deviceState.getAudioDeviceAttributes());
}
}
return compatList;
@@ -519,31 +519,30 @@ public class SpatializerHelper {
* set to true if the device is added to the list, otherwise, if already
* present, the setting is left untouched.
*/
+ @GuardedBy("this")
private void addCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada,
boolean forceEnable) {
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
+ }
loglogi("addCompatibleAudioDevice: dev=" + ada);
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
- boolean isInList = false;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
SADeviceState deviceUpdated = null; // non-null on update.
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
- isInList = true;
- if (forceEnable) {
- deviceState.mEnabled = true;
- deviceUpdated = deviceState;
- }
- break;
+ if (deviceState != null) {
+ if (forceEnable && !deviceState.mEnabled) {
+ deviceUpdated = deviceState;
+ deviceUpdated.mEnabled = true;
}
- }
- if (!isInList) {
- final SADeviceState dev = new SADeviceState(deviceType,
- wireless ? ada.getAddress() : "");
- dev.mEnabled = true;
- mSADevices.add(dev);
- deviceUpdated = dev;
+ } else {
+ // When adding, force the device type to be a canonical one.
+ final int canonicalDeviceType = getCanonicalDeviceType(ada.getType());
+ if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
+ Log.e(TAG, "addCompatibleAudioDevice with incompatible AudioDeviceAttributes "
+ + ada);
+ return;
+ }
+ deviceUpdated = new SADeviceState(canonicalDeviceType, ada.getAddress());
+ mSADevices.add(deviceUpdated);
}
if (deviceUpdated != null) {
onRoutingUpdated();
@@ -574,23 +573,50 @@ public class SpatializerHelper {
synchronized void removeCompatibleAudioDevice(@NonNull AudioDeviceAttributes ada) {
loglogi("removeCompatibleAudioDevice: dev=" + ada);
+
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState != null && deviceState.mEnabled) {
+ deviceState.mEnabled = false;
+ onRoutingUpdated();
+ mAudioService.persistSpatialAudioDeviceSettings();
+ logDeviceState(deviceState, "removeCompatibleAudioDevice");
+ }
+ }
+
+ /**
+ * Returns a possibly aliased device type which is used
+ * for spatial audio settings (or TYPE_UNKNOWN if it doesn't exist).
+ */
+ private static @AudioDeviceInfo.AudioDeviceType int getCanonicalDeviceType(int deviceType) {
+ if (isWireless(deviceType)) return deviceType;
+
+ final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+ if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
+ return AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
+ } else if (spatMode == SpatializationMode.SPATIALIZER_BINAURAL) {
+ return AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
+ }
+ return AudioDeviceInfo.TYPE_UNKNOWN;
+ }
+
+ /**
+ * Returns the Spatial Audio device state for an audio device attributes
+ * or null if it does not exist.
+ */
+ @GuardedBy("this")
+ @Nullable
+ private SADeviceState findDeviceStateForAudioDeviceAttributes(AudioDeviceAttributes ada) {
final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
- SADeviceState deviceUpdated = null; // non-null on update.
+ final boolean isWireless = isWireless(deviceType);
+ final int canonicalDeviceType = getCanonicalDeviceType(deviceType);
for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
- deviceState.mEnabled = false;
- deviceUpdated = deviceState;
- break;
+ if (deviceState.mDeviceType == canonicalDeviceType
+ && (!isWireless || ada.getAddress().equals(deviceState.mDeviceAddress))) {
+ return deviceState;
}
}
- if (deviceUpdated != null) {
- onRoutingUpdated();
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceUpdated, "removeCompatibleAudioDevice");
- }
+ return null;
}
/**
@@ -599,70 +625,43 @@ public class SpatializerHelper {
* @return a pair of boolean, 1/ enabled? 2/ available?
*/
private synchronized Pair<Boolean, Boolean> evaluateState(AudioDeviceAttributes ada) {
- // if not a wireless device, this value will be overwritten to map the type
- // to TYPE_BUILTIN_SPEAKER or TYPE_WIRED_HEADPHONES
- @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
- // if not a wireless device: find if media device is in the speaker, wired headphones
- if (!wireless) {
- // is the device type capable of doing SA?
- if (!mSACapableDeviceTypes.contains(deviceType)) {
- Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
- return new Pair<>(false, false);
- }
- // what spatialization mode to use for this device?
- final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
- if (spatMode == Integer.MIN_VALUE) {
- // error case, device not found
- Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
- return new Pair<>(false, false);
- }
- // map the spatialization mode to the SPEAKER or HEADPHONES device
- if (spatMode == SpatializationMode.SPATIALIZER_TRANSAURAL) {
- deviceType = AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
- } else {
- deviceType = AudioDeviceInfo.TYPE_WIRED_HEADPHONES;
- }
- } else { // wireless device
- if (isWirelessSpeaker(deviceType) && !mTransauralSupported) {
- Log.i(TAG, "Device incompatible with Spatial Audio (no transaural) dev:"
- + ada);
- return new Pair<>(false, false);
- }
- if (!mBinauralSupported) {
- Log.i(TAG, "Device incompatible with Spatial Audio (no binaural) dev:"
- + ada);
- return new Pair<>(false, false);
- }
+ final @AudioDeviceInfo.AudioDeviceType int deviceType = ada.getType();
+ // is the device type capable of doing SA?
+ if (!mSACapableDeviceTypes.contains(deviceType)) {
+ Log.i(TAG, "Device incompatible with Spatial Audio dev:" + ada);
+ return new Pair<>(false, false);
}
-
- boolean enabled = false;
- boolean available = false;
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
- available = true;
- enabled = deviceState.mEnabled;
- break;
- }
+ // what spatialization mode to use for this device?
+ final int spatMode = SPAT_MODE_FOR_DEVICE_TYPE.get(deviceType, Integer.MIN_VALUE);
+ if (spatMode == Integer.MIN_VALUE) {
+ // error case, device not found
+ Log.e(TAG, "no spatialization mode found for device type:" + deviceType);
+ return new Pair<>(false, false);
}
- return new Pair<>(enabled, available);
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState == null) {
+ // no matching device state?
+ Log.i(TAG, "no spatialization device state found for Spatial Audio device:" + ada);
+ return new Pair<>(false, false);
+ }
+ // found the matching device state.
+ return new Pair<>(deviceState.mEnabled, true /* available */);
}
private synchronized void addWirelessDeviceIfNew(@NonNull AudioDeviceAttributes ada) {
- boolean knownDevice = false;
- for (SADeviceState deviceState : mSADevices) {
- // wireless device so always check address
- if (ada.getType() == deviceState.mDeviceType
- && ada.getAddress().equals(deviceState.mDeviceAddress)) {
- knownDevice = true;
- break;
- }
+ if (!isDeviceCompatibleWithSpatializationModes(ada)) {
+ return;
}
- if (!knownDevice) {
- final SADeviceState deviceState = new SADeviceState(ada.getType(), ada.getAddress());
+ if (findDeviceStateForAudioDeviceAttributes(ada) == null) {
+ // wireless device types should be canonical, but we translate to be sure.
+ final int canonicalDeviceType = getCanonicalDeviceType((ada.getType()));
+ if (canonicalDeviceType == AudioDeviceInfo.TYPE_UNKNOWN) {
+ Log.e(TAG, "addWirelessDeviceIfNew with incompatible AudioDeviceAttributes "
+ + ada);
+ return;
+ }
+ final SADeviceState deviceState =
+ new SADeviceState(canonicalDeviceType, ada.getAddress());
mSADevices.add(deviceState);
mAudioService.persistSpatialAudioDeviceSettings();
logDeviceState(deviceState, "addWirelessDeviceIfNew"); // may be updated later.
@@ -704,27 +703,24 @@ public class SpatializerHelper {
if (ada.getRole() != AudioDeviceAttributes.ROLE_OUTPUT) {
return false;
}
+ return findDeviceStateForAudioDeviceAttributes(ada) != null;
+ }
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
- return true;
- }
+ private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
+ @NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
+ if (isDeviceCompatibleWithSpatializationModes(devices[0])) {
+ return AudioSystem.canBeSpatialized(attributes, format, devices);
}
return false;
}
- private synchronized boolean canBeSpatializedOnDevice(@NonNull AudioAttributes attributes,
- @NonNull AudioFormat format, @NonNull AudioDeviceAttributes[] devices) {
- final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(devices[0].getType(),
+ private boolean isDeviceCompatibleWithSpatializationModes(@NonNull AudioDeviceAttributes ada) {
+ final byte modeForDevice = (byte) SPAT_MODE_FOR_DEVICE_TYPE.get(ada.getType(),
/*default when type not found*/ SpatializationMode.SPATIALIZER_BINAURAL);
if ((modeForDevice == SpatializationMode.SPATIALIZER_BINAURAL && mBinauralSupported)
|| (modeForDevice == SpatializationMode.SPATIALIZER_TRANSAURAL
&& mTransauralSupported)) {
- return AudioSystem.canBeSpatialized(attributes, format, devices);
+ return true;
}
return false;
}
@@ -741,7 +737,7 @@ public class SpatializerHelper {
return;
}
if (mState == STATE_UNINITIALIZED) {
- init(true);
+ init(true, null /* settings */);
}
setSpatializerEnabledInt(true);
} else {
@@ -1089,27 +1085,20 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, ignoring setHeadTrackerEnabled to " + enabled
+ " for " + ada);
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
- if (!deviceState.mHasHeadTracker) {
- Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
- + " device:" + ada + " on a device without headtracker");
- return;
- }
- Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
- deviceState.mHeadTrackerEnabled = enabled;
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceState, "setHeadTrackerEnabled");
- break;
- }
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState == null) return;
+ if (!deviceState.mHasHeadTracker) {
+ Log.e(TAG, "Called setHeadTrackerEnabled enabled:" + enabled
+ + " device:" + ada + " on a device without headtracker");
+ return;
}
+ Log.i(TAG, "setHeadTrackerEnabled enabled:" + enabled + " device:" + ada);
+ deviceState.mHeadTrackerEnabled = enabled;
+ mAudioService.persistSpatialAudioDeviceSettings();
+ logDeviceState(deviceState, "setHeadTrackerEnabled");
+
// check current routing to see if it affects the headtracking mode
- if (ROUTING_DEVICES[0].getType() == deviceType
+ if (ROUTING_DEVICES[0].getType() == ada.getType()
&& ROUTING_DEVICES[0].getAddress().equals(ada.getAddress())) {
setDesiredHeadTrackingMode(enabled ? mDesiredHeadTrackingModeWhenEnabled
: Spatializer.HEAD_TRACKING_MODE_DISABLED);
@@ -1121,17 +1110,8 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, hasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
- return deviceState.mHasHeadTracker;
- }
- }
- return false;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ return deviceState != null && deviceState.mHasHeadTracker;
}
/**
@@ -1144,20 +1124,14 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, setHasHeadTracker always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
- if (!deviceState.mHasHeadTracker) {
- deviceState.mHasHeadTracker = true;
- mAudioService.persistSpatialAudioDeviceSettings();
- logDeviceState(deviceState, "setHasHeadTracker");
- }
- return deviceState.mHeadTrackerEnabled;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ if (deviceState != null) {
+ if (!deviceState.mHasHeadTracker) {
+ deviceState.mHasHeadTracker = true;
+ mAudioService.persistSpatialAudioDeviceSettings();
+ logDeviceState(deviceState, "setHasHeadTracker");
}
+ return deviceState.mHeadTrackerEnabled;
}
Log.e(TAG, "setHasHeadTracker: device not found for:" + ada);
return false;
@@ -1168,20 +1142,9 @@ public class SpatializerHelper {
Log.v(TAG, "no headtracking support, isHeadTrackerEnabled always false for " + ada);
return false;
}
- final int deviceType = ada.getType();
- final boolean wireless = isWireless(deviceType);
-
- for (SADeviceState deviceState : mSADevices) {
- if (deviceType == deviceState.mDeviceType
- && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress))
- || !wireless) {
- if (!deviceState.mHasHeadTracker) {
- return false;
- }
- return deviceState.mHeadTrackerEnabled;
- }
- }
- return false;
+ final SADeviceState deviceState = findDeviceStateForAudioDeviceAttributes(ada);
+ return deviceState != null
+ && deviceState.mHasHeadTracker && deviceState.mHeadTrackerEnabled;
}
synchronized boolean isHeadTrackerAvailable() {
@@ -1531,7 +1494,7 @@ public class SpatializerHelper {
SADeviceState(@AudioDeviceInfo.AudioDeviceType int deviceType, @NonNull String address) {
mDeviceType = deviceType;
- mDeviceAddress = Objects.requireNonNull(address);
+ mDeviceAddress = isWireless(deviceType) ? Objects.requireNonNull(address) : "";
}
@Override
@@ -1599,6 +1562,12 @@ public class SpatializerHelper {
return null;
}
}
+
+ public AudioDeviceAttributes getAudioDeviceAttributes() {
+ return new AudioDeviceAttributes(AudioDeviceAttributes.ROLE_OUTPUT,
+ mDeviceType, mDeviceAddress == null ? "" : mDeviceAddress);
+ }
+
}
/*package*/ synchronized String getSADeviceSettings() {
@@ -1619,7 +1588,12 @@ public class SpatializerHelper {
// small list, not worth overhead of Arrays.stream(devSettings)
for (String setting : devSettings) {
SADeviceState devState = SADeviceState.fromPersistedString(setting);
- if (devState != null) {
+ // Note if the device is not compatible with spatialization mode
+ // or the device type is not canonical, it is ignored.
+ if (devState != null
+ && devState.mDeviceType == getCanonicalDeviceType(devState.mDeviceType)
+ && isDeviceCompatibleWithSpatializationModes(
+ devState.getAudioDeviceAttributes())) {
mSADevices.add(devState);
logDeviceState(devState, "setSADeviceSettings");
}
@@ -1654,15 +1628,6 @@ public class SpatializerHelper {
return false;
}
- private static boolean isWirelessSpeaker(@AudioDeviceInfo.AudioDeviceType int deviceType) {
- for (int type : WIRELESS_SPEAKER_TYPES) {
- if (type == deviceType) {
- return true;
- }
- }
- return false;
- }
-
private int getHeadSensorHandleUpdateTracker() {
int headHandle = -1;
UUID routingDeviceUuid = mAudioService.getDeviceSensorUuid(ROUTING_DEVICES[0]);
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index ad24cf0591ca..262be08b60e2 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -28,8 +28,10 @@ import android.hardware.biometrics.BiometricsProtoEnums;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.face.FaceManager;
import android.hardware.fingerprint.FingerprintManager;
+import android.util.Log;
import android.util.Slog;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FrameworkStatsLog;
import com.android.server.biometrics.Utils;
@@ -41,6 +43,10 @@ public class BiometricLogger {
public static final String TAG = "BiometricLogger";
public static final boolean DEBUG = false;
+ private static final Object sLock = new Object();
+
+ @GuardedBy("sLock")
+ private static int sAlsCounter;
private final int mStatsModality;
private final int mStatsAction;
@@ -345,13 +351,33 @@ public class BiometricLogger {
if (!mLightSensorEnabled) {
mLightSensorEnabled = true;
mLastAmbientLux = 0;
- mSensorManager.registerListener(mLightSensorListener, lightSensor,
- SensorManager.SENSOR_DELAY_NORMAL);
+ int localAlsCounter;
+ synchronized (sLock) {
+ localAlsCounter = sAlsCounter++;
+ }
+
+ if (localAlsCounter == 0) {
+ mSensorManager.registerListener(mLightSensorListener, lightSensor,
+ SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ Slog.e(TAG, "Ignoring request to subscribe to ALSProbe due to non-zero ALS"
+ + " counter: " + localAlsCounter);
+ Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
}
} else {
mLightSensorEnabled = false;
mLastAmbientLux = 0;
mSensorManager.unregisterListener(mLightSensorListener);
+ int localAlsCounter;
+ synchronized (sLock) {
+ localAlsCounter = --sAlsCounter;
+ }
+ if (localAlsCounter != 0) {
+ Slog.e(TAG, "Non-zero ALS counter after unsubscribing from ALSProbe: "
+ + localAlsCounter);
+ Slog.e(TAG, Log.getStackTraceString(new Throwable()));
+ }
}
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
index 1b24aa896652..0d789f7a1840 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricStateCallback.java
@@ -108,6 +108,17 @@ public class BiometricStateCallback implements ClientMonitorCallback {
}
}
+ @Override
+ public void onBiometricAction(@BiometricStateListener.Action int action) {
+ for (IBiometricStateListener listener : mBiometricStateListeners) {
+ try {
+ listener.onBiometricAction(action);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Remote exception in onBiometricAction", e);
+ }
+ }
+ }
+
/**
* This should be invoked when:
* 1) Enrolled --> None-enrolled
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
index 8ea4ee911cb1..4417f9270d9c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCallback.java
@@ -31,6 +31,11 @@ public interface ClientMonitorCallback {
default void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {}
/**
+ * Invoked when a biometric action has occurred.
+ */
+ default void onBiometricAction(int action) {}
+
+ /**
* Invoked when the ClientMonitor operation is complete. This abstracts away asynchronous
* (i.e. Authenticate, Enroll, Enumerate, Remove) and synchronous (i.e. generateChallenge,
* revokeChallenge) so that a scheduler can process ClientMonitors regardless of their
diff --git a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
index b82f5fa129d6..07041bf8ce3d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
+++ b/services/core/java/com/android/server/biometrics/sensors/ClientMonitorCompositeCallback.java
@@ -44,6 +44,13 @@ public class ClientMonitorCompositeCallback implements ClientMonitorCallback {
}
@Override
+ public final void onBiometricAction(int action) {
+ for (int i = 0; i < mCallbacks.size(); i++) {
+ mCallbacks.get(i).onBiometricAction(action);
+ }
+ }
+
+ @Override
public final void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
for (int i = mCallbacks.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
index d99abcd4b3d2..9494547c7aaf 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/UsageStats.java
@@ -70,20 +70,24 @@ public class UsageStats {
private int mAcceptCount;
private int mRejectCount;
- private SparseIntArray mErrorCount;
+ private int mErrorCount;
+ private int mAuthAttemptCount;
+ private SparseIntArray mErrorFrequencyMap;
private long mAcceptLatency;
private long mRejectLatency;
- private SparseLongArray mErrorLatency;
+ private long mErrorLatency;
+ private SparseLongArray mErrorLatencyMap;
public UsageStats(Context context) {
mAuthenticationEvents = new ArrayDeque<>();
- mErrorCount = new SparseIntArray();
- mErrorLatency = new SparseLongArray();
+ mErrorFrequencyMap = new SparseIntArray();
+ mErrorLatencyMap = new SparseLongArray();
mContext = context;
}
public void addEvent(AuthenticationEvent event) {
+ mAuthAttemptCount++;
if (mAuthenticationEvents.size() >= EVENT_LOG_SIZE) {
mAuthenticationEvents.removeFirst();
}
@@ -96,29 +100,38 @@ public class UsageStats {
mRejectCount++;
mRejectLatency += event.mLatency;
} else {
- mErrorCount.put(event.mError, mErrorCount.get(event.mError, 0) + 1);
- mErrorLatency.put(event.mError, mErrorLatency.get(event.mError, 0L) + event.mLatency);
+ mErrorCount++;
+ mErrorLatency += event.mLatency;
+ mErrorFrequencyMap.put(event.mError, mErrorFrequencyMap.get(event.mError, 0) + 1);
+ mErrorLatencyMap.put(event.mError,
+ mErrorLatencyMap.get(event.mError, 0L) + event.mLatency);
}
}
public void print(PrintWriter pw) {
- pw.println("Events since last reboot: " + mAuthenticationEvents.size());
+ pw.println("Printing most recent events since last reboot("
+ + mAuthenticationEvents.size() + " events)");
for (AuthenticationEvent event : mAuthenticationEvents) {
pw.println(event.toString(mContext));
}
// Dump aggregated usage stats
- pw.println("Accept\tCount: " + mAcceptCount + "\tLatency: " + mAcceptLatency
+ pw.println("");
+ pw.println("Accept Count: " + mAcceptCount + "\tLatency: " + mAcceptLatency
+ "\tAverage: " + (mAcceptCount > 0 ? mAcceptLatency / mAcceptCount : 0));
- pw.println("Reject\tCount: " + mRejectCount + "\tLatency: " + mRejectLatency
+ pw.println("Reject Count: " + mRejectCount + "\tLatency: " + mRejectLatency
+ "\tAverage: " + (mRejectCount > 0 ? mRejectLatency / mRejectCount : 0));
-
- for (int i = 0; i < mErrorCount.size(); i++) {
- final int key = mErrorCount.keyAt(i);
- final int count = mErrorCount.get(i);
+ pw.println("Total Error Count: " + mErrorCount + "\tLatency: " + mErrorLatency
+ + "\tAverage: " + (mErrorCount > 0 ? mErrorLatency / mErrorCount : 0));
+ pw.println("Total Attempts: " + mAuthAttemptCount);
+ pw.println("");
+
+ for (int i = 0; i < mErrorFrequencyMap.size(); i++) {
+ final int key = mErrorFrequencyMap.keyAt(i);
+ final int count = mErrorFrequencyMap.get(key);
pw.println("Error" + key + "\tCount: " + count
- + "\tLatency: " + mErrorLatency.get(key, 0L)
- + "\tAverage: " + (count > 0 ? mErrorLatency.get(key, 0L) / count : 0)
+ + "\tLatency: " + mErrorLatencyMap.get(key, 0L)
+ + "\tAverage: " + (count > 0 ? mErrorLatencyMap.get(key, 0L) / count : 0)
+ "\t" + FaceManager.getErrorString(mContext, key, 0 /* vendorCode */));
}
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 0c5b19b0fb2e..94b67cedf86c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -1020,9 +1020,18 @@ public class FingerprintService extends SystemService {
@Override
public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
FingerprintService.this.registerBiometricStateListener(listener);
}
- }
+
+ @Override
+ public void onPowerPressed() {
+ Utils.checkPermission(getContext(), USE_BIOMETRIC_INTERNAL);
+ for (ServiceProvider provider : mServiceProviders) {
+ provider.onPowerPressed();
+ }
+ }
+ };
public FingerprintService(Context context) {
super(context);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java
index af2ab158ab46..288c372a3f13 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/legacysplitscreen/DividerState.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/PowerPressHandler.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,12 +14,14 @@
* limitations under the License.
*/
-package com.android.wm.shell.legacysplitscreen;
+package com.android.server.biometrics.sensors.fingerprint;
/**
- * Class to hold state of divider that needs to persist across configuration changes.
+ * Interface for handling power presses.
*/
-final class DividerState {
- public boolean animateAfterRecentsDrawn;
- public float mRatioPositionBeforeMinimized;
+public interface PowerPressHandler {
+ /**
+ * Indicates a power press has occurred.
+ */
+ void onPowerPressed();
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 9cdbdc9158fb..24a47e081498 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -150,6 +150,8 @@ public interface ServiceProvider {
void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
+ void onPowerPressed();
+
/**
* Sets side-fps controller
* @param controller side-fps controller
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 79e3bf53acd3..e1626f014d8b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.app.TaskStackListener;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
+import android.hardware.biometrics.BiometricConstants;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
import android.hardware.biometrics.common.ICancellationSignal;
@@ -29,10 +30,15 @@ import android.hardware.biometrics.fingerprint.PointerContext;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Build;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.UserHandle;
+import android.provider.Settings;
import android.util.Slog;
+import com.android.internal.R;
import com.android.server.biometrics.log.BiometricContext;
import com.android.server.biometrics.log.BiometricLogger;
import com.android.server.biometrics.log.CallbackWithProbe;
@@ -46,17 +52,18 @@ import com.android.server.biometrics.sensors.LockoutCache;
import com.android.server.biometrics.sensors.LockoutConsumer;
import com.android.server.biometrics.sensors.LockoutTracker;
import com.android.server.biometrics.sensors.SensorOverlays;
+import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
import java.util.ArrayList;
import java.util.function.Supplier;
/**
- * Fingerprint-specific authentication client supporting the
- * {@link android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
+ * Fingerprint-specific authentication client supporting the {@link
+ * android.hardware.biometrics.fingerprint.IFingerprint} AIDL interface.
*/
-class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession> implements
- Udfps, LockoutConsumer {
+class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
+ implements Udfps, LockoutConsumer, PowerPressHandler {
private static final String TAG = "FingerprintAuthenticationClient";
@NonNull private final LockoutCache mLockoutCache;
@@ -64,33 +71,88 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
@NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
@NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
- @Nullable private ICancellationSignal mCancellationSignal;
+ @Nullable
+ private ICancellationSignal mCancellationSignal;
private boolean mIsPointerDown;
+ private final Handler mHandler;
- FingerprintAuthenticationClient(@NonNull Context context,
+ private static final int MESSAGE_IGNORE_AUTH = 1;
+ private static final int MESSAGE_AUTH_SUCCESS = 2;
+ private long mWaitForAuthKeyguard;
+ private long mWaitForAuthBp;
+ private long mIgnoreAuthFor;
+
+ FingerprintAuthenticationClient(
+ @NonNull Context context,
@NonNull Supplier<AidlSession> lazyDaemon,
- @NonNull IBinder token, long requestId,
- @NonNull ClientMonitorCallbackConverter listener, int targetUserId, long operationId,
- boolean restricted, @NonNull String owner, int cookie, boolean requireConfirmation,
+ @NonNull IBinder token,
+ long requestId,
+ @NonNull ClientMonitorCallbackConverter listener,
+ int targetUserId,
+ long operationId,
+ boolean restricted,
+ @NonNull String owner,
+ int cookie,
+ boolean requireConfirmation,
int sensorId,
- @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
+ @NonNull BiometricLogger biometricLogger,
+ @NonNull BiometricContext biometricContext,
boolean isStrongBiometric,
- @Nullable TaskStackListener taskStackListener, @NonNull LockoutCache lockoutCache,
+ @Nullable TaskStackListener taskStackListener,
+ @NonNull LockoutCache lockoutCache,
@Nullable IUdfpsOverlayController udfpsOverlayController,
@Nullable ISidefpsController sidefpsController,
boolean allowBackgroundAuthentication,
- @NonNull FingerprintSensorPropertiesInternal sensorProps) {
- super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted, owner,
- cookie, requireConfirmation, sensorId,
- biometricLogger, biometricContext,
- isStrongBiometric, taskStackListener,
- lockoutCache, allowBackgroundAuthentication, true /* shouldVibrate */,
+ @NonNull FingerprintSensorPropertiesInternal sensorProps,
+ @NonNull Handler handler) {
+ super(
+ context,
+ lazyDaemon,
+ token,
+ listener,
+ targetUserId,
+ operationId,
+ restricted,
+ owner,
+ cookie,
+ requireConfirmation,
+ sensorId,
+ biometricLogger,
+ biometricContext,
+ isStrongBiometric,
+ taskStackListener,
+ lockoutCache,
+ allowBackgroundAuthentication,
+ true /* shouldVibrate */,
false /* isKeyguardBypassEnabled */);
setRequestId(requestId);
mLockoutCache = lockoutCache;
mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
mSensorProps = sensorProps;
mALSProbeCallback = getLogger().createALSCallback(false /* startWithClient */);
+ mHandler = handler;
+
+ mWaitForAuthKeyguard =
+ context.getResources()
+ .getInteger(R.integer.config_sidefpsKeyguardPowerPressWindow);
+ mWaitForAuthBp =
+ context.getResources().getInteger(R.integer.config_sidefpsBpPowerPressWindow);
+ mIgnoreAuthFor =
+ context.getResources().getInteger(R.integer.config_sidefpsPostAuthDowntime);
+
+ if (mSensorProps.isAnySidefpsType()) {
+ if (Build.isDebuggable()) {
+ mWaitForAuthKeyguard = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_KG_POWER_WINDOW,
+ (int) mWaitForAuthKeyguard, UserHandle.USER_CURRENT);
+ mWaitForAuthBp = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW, (int) mWaitForAuthBp,
+ UserHandle.USER_CURRENT);
+ mIgnoreAuthFor = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, (int) mIgnoreAuthFor,
+ UserHandle.USER_CURRENT);
+ }
+ }
}
@Override
@@ -108,8 +170,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
@NonNull
@Override
protected ClientMonitorCallback wrapCallbackForStart(@NonNull ClientMonitorCallback callback) {
- return new ClientMonitorCompositeCallback(mALSProbeCallback,
- getBiometricContextUnsubscriber(), callback);
+ return new ClientMonitorCompositeCallback(
+ mALSProbeCallback, getBiometricContextUnsubscriber(), callback);
}
@Override
@@ -126,16 +188,37 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
}
@Override
- public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
- boolean authenticated, ArrayList<Byte> token) {
- super.onAuthenticated(identifier, authenticated, token);
-
- if (authenticated) {
- mState = STATE_STOPPED;
- mSensorOverlays.hide(getSensorId());
- } else {
- mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ public void onAuthenticated(
+ BiometricAuthenticator.Identifier identifier,
+ boolean authenticated,
+ ArrayList<Byte> token) {
+
+ long delay = 0;
+ if (authenticated && mSensorProps.isAnySidefpsType()) {
+ if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
+ Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ return;
+ }
+ delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
+ Slog.i(TAG, "(sideFPS) Auth succeeded, sideFps waiting for power until: " + delay);
}
+
+ mHandler.postDelayed(
+ () -> {
+ if (authenticated && mSensorProps.isAnySidefpsType()) {
+ Slog.i(TAG, "(sideFPS): No power press detected, sending auth");
+ }
+ super.onAuthenticated(identifier, authenticated, token);
+ if (authenticated) {
+ mState = STATE_STOPPED;
+ mSensorOverlays.hide(getSensorId());
+ } else {
+ mState = STATE_STARTED_PAUSED_ATTEMPTED;
+ }
+ },
+ MESSAGE_AUTH_SUCCESS,
+ delay);
}
@Override
@@ -165,7 +248,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mCancellationSignal = doAuthenticate();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ onError(
+ BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
@@ -177,15 +261,18 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
if (session.hasContextMethods()) {
final OperationContext opContext = getOperationContext();
- final ICancellationSignal cancel = session.getSession().authenticateWithContext(
- mOperationId, opContext);
- getBiometricContext().subscribe(opContext, ctx -> {
- try {
- session.getSession().onContextChanged(ctx);
- } catch (RemoteException e) {
- Slog.e(TAG, "Unable to notify context changed", e);
- }
- });
+ final ICancellationSignal cancel =
+ session.getSession().authenticateWithContext(mOperationId, opContext);
+ getBiometricContext()
+ .subscribe(
+ opContext,
+ ctx -> {
+ try {
+ session.getSession().onContextChanged(ctx);
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Unable to notify context changed", e);
+ }
+ });
return cancel;
} else {
return session.getSession().authenticate(mOperationId);
@@ -202,7 +289,8 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mCancellationSignal.cancel();
} catch (RemoteException e) {
Slog.e(TAG, "Remote exception", e);
- onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
+ onError(
+ BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
0 /* vendorCode */);
mCallback.onClientFinished(this, false /* success */);
}
@@ -284,8 +372,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_TIMED);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
- getLogger().logOnError(getContext(), getOperationContext(),
- error, 0 /* vendorCode */, getTargetUserId());
+ getLogger()
+ .logOnError(
+ getContext(),
+ getOperationContext(),
+ error,
+ 0 /* vendorCode */,
+ getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -303,8 +396,13 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mLockoutCache.setLockoutModeForUser(getTargetUserId(), LockoutTracker.LOCKOUT_PERMANENT);
// Lockout metrics are logged as an error code.
final int error = BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
- getLogger().logOnError(getContext(), getOperationContext(),
- error, 0 /* vendorCode */, getTargetUserId());
+ getLogger()
+ .logOnError(
+ getContext(),
+ getOperationContext(),
+ error,
+ 0 /* vendorCode */,
+ getTargetUserId());
try {
getListener().onError(getSensorId(), getCookie(), error, 0 /* vendorCode */);
@@ -315,4 +413,19 @@ class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
mSensorOverlays.hide(getSensorId());
mCallback.onClientFinished(this, false /* success */);
}
+
+ @Override
+ public void onPowerPressed() {
+ if (mSensorProps.isAnySidefpsType()) {
+ Slog.i(TAG, "(sideFPS): onPowerPressed");
+ if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
+ Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
+ mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+ // Do not call onError() as that will send an additional callback to coex.
+ onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
+ }
+ mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
+ mHandler.postDelayed(() -> {}, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
+ }
+ }
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index f23659c94e49..f4f0a19d0db7 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -22,6 +22,7 @@ import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
import android.hardware.biometrics.BiometricFingerprintConstants.FingerprintAcquired;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.common.ICancellationSignal;
import android.hardware.biometrics.common.OperationContext;
import android.hardware.biometrics.fingerprint.PointerContext;
@@ -139,7 +140,7 @@ class FingerprintEnrollClient extends EnrollClient<AidlSession> implements Udfps
controller.onEnrollmentHelp(getSensorId());
}
});
-
+ mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
super.onAcquired(acquiredInfo, vendorCode);
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index f16af78364a2..6f6c09b91a66 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -67,6 +67,7 @@ import com.android.server.biometrics.sensors.LockoutResetDispatcher;
import com.android.server.biometrics.sensors.PerformanceTracker;
import com.android.server.biometrics.sensors.fingerprint.FingerprintUtils;
import com.android.server.biometrics.sensors.fingerprint.GestureAvailabilityDispatcher;
+import com.android.server.biometrics.sensors.fingerprint.PowerPressHandler;
import com.android.server.biometrics.sensors.fingerprint.ServiceProvider;
import com.android.server.biometrics.sensors.fingerprint.Udfps;
@@ -388,6 +389,11 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
@@ -441,7 +447,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
mBiometricContext, isStrongBiometric,
mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
- mSensors.get(sensorId).getSensorProperties());
+ mSensors.get(sensorId).getSensorProperties(), mHandler);
scheduleForSensor(sensorId, client, mBiometricStateCallback);
});
}
@@ -614,6 +620,21 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi
}
@Override
+ public void onPowerPressed() {
+ for (int i = 0; i < mSensors.size(); i++) {
+ final Sensor sensor = mSensors.valueAt(i);
+ BaseClientMonitor client = sensor.getScheduler().getCurrentClient();
+ if (client == null) {
+ return;
+ }
+ if (!(client instanceof PowerPressHandler)) {
+ continue;
+ }
+ ((PowerPressHandler) client).onPowerPressed();
+ }
+ }
+
+ @Override
public void setSidefpsController(@NonNull ISidefpsController controller) {
mSidefpsController = controller;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 2a3f34ae3cd4..c1a86386dfd4 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -603,6 +603,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
+ public void onBiometricAction(int action) {
+ mBiometricStateCallback.onBiometricAction(action);
+ }
+
+ @Override
public void onClientFinished(@NonNull BaseClientMonitor clientMonitor,
boolean success) {
mBiometricStateCallback.onClientFinished(clientMonitor, success);
@@ -821,6 +826,11 @@ public class Fingerprint21 implements IHwBinder.DeathRecipient, ServiceProvider
}
@Override
+ public void onPowerPressed() {
+ Slog.e(TAG, "onPowerPressed not supported for HIDL clients");
+ }
+
+ @Override
public void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller) {
mUdfpsOverlayController = controller;
}
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 1d478e53fd38..2a59c8c0f204 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -21,6 +21,7 @@ import android.annotation.Nullable;
import android.content.Context;
import android.hardware.biometrics.BiometricAuthenticator;
import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.BiometricStateListener;
import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintManager;
@@ -151,6 +152,8 @@ public class FingerprintEnrollClient extends EnrollClient<IBiometricsFingerprint
controller.onEnrollmentHelp(getSensorId());
}
});
+
+ mCallback.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
}
@Override
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 61350bb6095e..6f9a17682dd7 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -43,6 +43,7 @@ import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
+import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
@@ -63,6 +64,7 @@ import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
+import android.util.SafetyProtectionUtils;
import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -215,9 +217,12 @@ public class ClipboardService extends SystemService {
private class ListenerInfo {
final int mUid;
final String mPackageName;
- ListenerInfo(int uid, String packageName) {
+ final String mAttributionTag;
+
+ ListenerInfo(int uid, String packageName, String attributionTag) {
mUid = uid;
mPackageName = packageName;
+ mAttributionTag = attributionTag;
}
}
@@ -355,27 +360,43 @@ public class ClipboardService extends SystemService {
}
@Override
- public void setPrimaryClip(ClipData clip, String callingPackage, @UserIdInt int userId) {
- checkAndSetPrimaryClip(clip, callingPackage, userId, callingPackage);
+ public void setPrimaryClip(
+ ClipData clip,
+ String callingPackage,
+ String attributionTag,
+ @UserIdInt int userId) {
+ checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, callingPackage);
}
@Override
public void setPrimaryClipAsPackage(
- ClipData clip, String callingPackage, @UserIdInt int userId, String sourcePackage) {
+ ClipData clip,
+ String callingPackage,
+ String attributionTag,
+ @UserIdInt int userId,
+ String sourcePackage) {
getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
"Requires SET_CLIP_SOURCE permission");
- checkAndSetPrimaryClip(clip, callingPackage, userId, sourcePackage);
+ checkAndSetPrimaryClip(clip, callingPackage, attributionTag, userId, sourcePackage);
}
private void checkAndSetPrimaryClip(
- ClipData clip, String callingPackage, @UserIdInt int userId, String sourcePackage) {
+ ClipData clip,
+ String callingPackage,
+ String attributionTag,
+ @UserIdInt int userId,
+ String sourcePackage) {
if (clip == null || clip.getItemCount() <= 0) {
throw new IllegalArgumentException("No items");
}
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)) {
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_WRITE_CLIPBOARD,
+ callingPackage,
+ attributionTag,
+ intendingUid,
+ intendingUserId)) {
return;
}
checkDataOwner(clip, intendingUid);
@@ -392,8 +413,13 @@ public class ClipboardService extends SystemService {
PROPERTY_AUTO_CLEAR_ENABLED, true)) {
mClipboardClearHandler.removeEqualMessages(ClipboardClearHandler.MSG_CLEAR,
userId);
- Message clearMessage = Message.obtain(mClipboardClearHandler,
- ClipboardClearHandler.MSG_CLEAR, userId, intendingUid, userId);
+ Message clearMessage =
+ Message.obtain(
+ mClipboardClearHandler,
+ ClipboardClearHandler.MSG_CLEAR,
+ userId,
+ intendingUid,
+ userId);
mClipboardClearHandler.sendMessageDelayed(clearMessage,
getTimeoutForAutoClear());
}
@@ -409,11 +435,16 @@ public class ClipboardService extends SystemService {
}
@Override
- public void clearPrimaryClip(String callingPackage, @UserIdInt int userId) {
+ public void clearPrimaryClip(
+ String callingPackage, String attributionTag, @UserIdInt int userId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_WRITE_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId)) {
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_WRITE_CLIPBOARD,
+ callingPackage,
+ attributionTag,
+ intendingUid,
+ intendingUserId)) {
return;
}
synchronized (mLock) {
@@ -424,11 +455,15 @@ public class ClipboardService extends SystemService {
}
@Override
- public ClipData getPrimaryClip(String pkg, @UserIdInt int userId) {
+ public ClipData getPrimaryClip(String pkg, String attributionTag, @UserIdInt int userId) {
final int intendingUid = getIntendingUid(pkg, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, pkg,
- intendingUid, intendingUserId)
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_READ_CLIPBOARD,
+ pkg,
+ attributionTag,
+ intendingUid,
+ intendingUserId)
|| isDeviceLocked(intendingUserId)) {
return null;
}
@@ -453,12 +488,17 @@ public class ClipboardService extends SystemService {
}
@Override
- public ClipDescription getPrimaryClipDescription(String callingPackage,
- @UserIdInt int userId) {
+ public ClipDescription getPrimaryClipDescription(
+ String callingPackage, String attributionTag, @UserIdInt int userId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId, false)
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_READ_CLIPBOARD,
+ callingPackage,
+ attributionTag,
+ intendingUid,
+ intendingUserId,
+ false)
|| isDeviceLocked(intendingUserId)) {
return null;
}
@@ -470,11 +510,17 @@ public class ClipboardService extends SystemService {
}
@Override
- public boolean hasPrimaryClip(String callingPackage, @UserIdInt int userId) {
+ public boolean hasPrimaryClip(
+ String callingPackage, String attributionTag, @UserIdInt int userId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId, false)
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_READ_CLIPBOARD,
+ callingPackage,
+ attributionTag,
+ intendingUid,
+ intendingUserId,
+ false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
@@ -484,19 +530,28 @@ public class ClipboardService extends SystemService {
}
@Override
- public void addPrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
- String callingPackage, @UserIdInt int userId) {
+ public void addPrimaryClipChangedListener(
+ IOnPrimaryClipChangedListener listener,
+ String callingPackage,
+ String attributionTag,
+ @UserIdInt int userId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
synchronized (mLock) {
- getClipboardLocked(intendingUserId).primaryClipListeners.register(listener,
- new ListenerInfo(intendingUid, callingPackage));
+ getClipboardLocked(intendingUserId)
+ .primaryClipListeners
+ .register(
+ listener,
+ new ListenerInfo(intendingUid, callingPackage, attributionTag));
}
}
@Override
- public void removePrimaryClipChangedListener(IOnPrimaryClipChangedListener listener,
- String callingPackage, @UserIdInt int userId) {
+ public void removePrimaryClipChangedListener(
+ IOnPrimaryClipChangedListener listener,
+ String callingPackage,
+ String attributionTag,
+ @UserIdInt int userId) {
final int intendingUserId = getIntendingUserId(callingPackage, userId);
synchronized (mLock) {
getClipboardLocked(intendingUserId).primaryClipListeners.unregister(listener);
@@ -504,11 +559,16 @@ public class ClipboardService extends SystemService {
}
@Override
- public boolean hasClipboardText(String callingPackage, int userId) {
+ public boolean hasClipboardText(String callingPackage, String attributionTag, int userId) {
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId, false)
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_READ_CLIPBOARD,
+ callingPackage,
+ attributionTag,
+ intendingUid,
+ intendingUserId,
+ false)
|| isDeviceLocked(intendingUserId)) {
return false;
}
@@ -523,13 +583,19 @@ public class ClipboardService extends SystemService {
}
@Override
- public String getPrimaryClipSource(String callingPackage, int userId) {
+ public String getPrimaryClipSource(
+ String callingPackage, String attributionTag, int userId) {
getContext().enforceCallingOrSelfPermission(Manifest.permission.SET_CLIP_SOURCE,
"Requires SET_CLIP_SOURCE permission");
final int intendingUid = getIntendingUid(callingPackage, userId);
final int intendingUserId = UserHandle.getUserId(intendingUid);
- if (!clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, callingPackage,
- intendingUid, intendingUserId, false)
+ if (!clipboardAccessAllowed(
+ AppOpsManager.OP_READ_CLIPBOARD,
+ callingPackage,
+ attributionTag,
+ intendingUid,
+ intendingUserId,
+ false)
|| isDeviceLocked(intendingUserId)) {
return null;
}
@@ -718,8 +784,12 @@ public class ClipboardService extends SystemService {
ListenerInfo li = (ListenerInfo)
clipboard.primaryClipListeners.getBroadcastCookie(i);
- if (clipboardAccessAllowed(AppOpsManager.OP_READ_CLIPBOARD, li.mPackageName,
- li.mUid, UserHandle.getUserId(li.mUid))) {
+ if (clipboardAccessAllowed(
+ AppOpsManager.OP_READ_CLIPBOARD,
+ li.mPackageName,
+ li.mAttributionTag,
+ li.mUid,
+ UserHandle.getUserId(li.mUid))) {
clipboard.primaryClipListeners.getBroadcastItem(i)
.dispatchPrimaryClipChanged();
}
@@ -965,13 +1035,18 @@ public class ClipboardService extends SystemService {
}
}
- private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
- @UserIdInt int userId) {
- return clipboardAccessAllowed(op, callingPackage, uid, userId, true);
+ private boolean clipboardAccessAllowed(
+ int op, String callingPackage, String attributionTag, int uid, @UserIdInt int userId) {
+ return clipboardAccessAllowed(op, callingPackage, attributionTag, uid, userId, true);
}
- private boolean clipboardAccessAllowed(int op, String callingPackage, int uid,
- @UserIdInt int userId, boolean shouldNoteOp) {
+ private boolean clipboardAccessAllowed(
+ int op,
+ String callingPackage,
+ String attributionTag,
+ int uid,
+ @UserIdInt int userId,
+ boolean shouldNoteOp) {
boolean allowed;
@@ -1040,7 +1115,7 @@ public class ClipboardService extends SystemService {
// Finally, check the app op.
int appOpsResult;
if (shouldNoteOp) {
- appOpsResult = mAppOps.noteOp(op, uid, callingPackage);
+ appOpsResult = mAppOps.noteOp(op, uid, callingPackage, attributionTag, null);
} else {
appOpsResult = mAppOps.checkOp(op, uid, callingPackage);
}
@@ -1108,9 +1183,19 @@ public class ClipboardService extends SystemService {
String message =
getContext().getString(R.string.pasted_from_clipboard, callingAppLabel);
Slog.i(TAG, message);
- Toast.makeText(
- getContext(), UiThread.get().getLooper(), message, Toast.LENGTH_SHORT)
- .show();
+ Toast toastToShow;
+ if (SafetyProtectionUtils.shouldShowSafetyProtectionResources(getContext())) {
+ Drawable safetyProtectionIcon = getContext()
+ .getDrawable(R.drawable.ic_safety_protection);
+ toastToShow = Toast.makeCustomToastWithIcon(getContext(),
+ UiThread.get().getLooper(), message,
+ Toast.LENGTH_SHORT, safetyProtectionIcon);
+ } else {
+ toastToShow = Toast.makeText(
+ getContext(), UiThread.get().getLooper(), message,
+ Toast.LENGTH_SHORT);
+ }
+ toastToShow.show();
} catch (PackageManager.NameNotFoundException e) {
// do nothing
}
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 03e18a04c2d5..8e00ccf68fb1 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -40,6 +40,8 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ShellCallback;
+import android.os.SystemProperties;
+import android.os.Trace;
import android.util.Slog;
import android.util.SparseArray;
@@ -144,15 +146,30 @@ public final class DeviceStateManagerService extends SystemService {
private Set<Integer> mDeviceStatesAvailableForAppRequests;
+ @VisibleForTesting
+ interface SystemPropertySetter {
+ void setDebugTracingDeviceStateProperty(String value);
+ }
+ @NonNull
+ private final SystemPropertySetter mSystemPropertySetter;
+
public DeviceStateManagerService(@NonNull Context context) {
this(context, DeviceStatePolicy.Provider
.fromResources(context.getResources())
.instantiate(context));
}
+ private DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
+ this(context, policy, (value) -> {
+ SystemProperties.set("debug.tracing.device_state", value);
+ });
+ }
+
@VisibleForTesting
- DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy) {
+ DeviceStateManagerService(@NonNull Context context, @NonNull DeviceStatePolicy policy,
+ @NonNull SystemPropertySetter systemPropertySetter) {
super(context);
+ mSystemPropertySetter = systemPropertySetter;
// We use the DisplayThread because this service indirectly drives
// display (on/off) and window (position) events through its callbacks.
DisplayThread displayThread = DisplayThread.get();
@@ -462,6 +479,10 @@ public final class DeviceStateManagerService extends SystemService {
FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_STATE_CHANGED,
newState.getIdentifier(), !mCommittedState.isPresent());
+ String traceString = newState.getIdentifier() + ":" + newState.getName();
+ Trace.instantForTrack(
+ Trace.TRACE_TAG_SYSTEM_SERVER, "DeviceStateChanged", traceString);
+ mSystemPropertySetter.setDebugTracingDeviceStateProperty(traceString);
mCommittedState = Optional.of(newState);
mPendingState = Optional.empty();
diff --git a/services/core/java/com/android/server/display/BrightnessThrottler.java b/services/core/java/com/android/server/display/BrightnessThrottler.java
index 767b2d18a69a..eccee52f37aa 100644
--- a/services/core/java/com/android/server/display/BrightnessThrottler.java
+++ b/services/core/java/com/android/server/display/BrightnessThrottler.java
@@ -16,21 +16,31 @@
package com.android.server.display;
+import android.annotation.NonNull;
import android.content.Context;
import android.hardware.display.BrightnessInfo;
+import android.hardware.display.DisplayManager;
import android.os.Handler;
+import android.os.HandlerExecutor;
import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Temperature;
+import android.provider.DeviceConfig;
+import android.provider.DeviceConfigInterface;
import android.util.Slog;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
/**
* This class monitors various conditions, such as skin temperature throttling status, and limits
@@ -44,28 +54,54 @@ class BrightnessThrottler {
private final Injector mInjector;
private final Handler mHandler;
- private BrightnessThrottlingData mThrottlingData;
+ // We need a separate handler for unit testing. These two handlers are the same throughout the
+ // non-test code.
+ private final Handler mDeviceConfigHandler;
private final Runnable mThrottlingChangeCallback;
private final SkinThermalStatusObserver mSkinThermalStatusObserver;
+ private final DeviceConfigListener mDeviceConfigListener;
+ private final DeviceConfigInterface mDeviceConfig;
+
private int mThrottlingStatus;
+ private BrightnessThrottlingData mThrottlingData;
+ private BrightnessThrottlingData mDdcThrottlingData;
private float mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
private @BrightnessInfo.BrightnessMaxReason int mBrightnessMaxReason =
BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
+ private String mUniqueDisplayId;
+
+ // The most recent string that has been set from DeviceConfig
+ private String mBrightnessThrottlingDataString;
+
+ // This is a collection of brightness throttling data that has been written as overrides from
+ // the DeviceConfig. This will always take priority over the display device config data.
+ private HashMap<String, BrightnessThrottlingData> mBrightnessThrottlingDataOverride =
+ new HashMap<>(1);
BrightnessThrottler(Handler handler, BrightnessThrottlingData throttlingData,
- Runnable throttlingChangeCallback) {
- this(new Injector(), handler, throttlingData, throttlingChangeCallback);
+ Runnable throttlingChangeCallback, String uniqueDisplayId) {
+ this(new Injector(), handler, handler, throttlingData, throttlingChangeCallback,
+ uniqueDisplayId);
}
- BrightnessThrottler(Injector injector, Handler handler, BrightnessThrottlingData throttlingData,
- Runnable throttlingChangeCallback) {
+ @VisibleForTesting
+ BrightnessThrottler(Injector injector, Handler handler, Handler deviceConfigHandler,
+ BrightnessThrottlingData throttlingData, Runnable throttlingChangeCallback,
+ String uniqueDisplayId) {
mInjector = injector;
+
mHandler = handler;
+ mDeviceConfigHandler = deviceConfigHandler;
mThrottlingData = throttlingData;
+ mDdcThrottlingData = throttlingData;
mThrottlingChangeCallback = throttlingChangeCallback;
mSkinThermalStatusObserver = new SkinThermalStatusObserver(mInjector, mHandler);
- resetThrottlingData(mThrottlingData);
+ mUniqueDisplayId = uniqueDisplayId;
+ mDeviceConfig = injector.getDeviceConfig();
+ mDeviceConfigListener = new DeviceConfigListener();
+
+ resetThrottlingData(mThrottlingData, mUniqueDisplayId);
}
boolean deviceSupportsThrottling() {
@@ -86,7 +122,7 @@ class BrightnessThrottler {
void stop() {
mSkinThermalStatusObserver.stopObserving();
-
+ mDeviceConfig.removeOnPropertiesChangedListener(mDeviceConfigListener);
// We're asked to stop throttling, so reset brightness restrictions.
mBrightnessCap = PowerManager.BRIGHTNESS_MAX;
mBrightnessMaxReason = BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE;
@@ -97,9 +133,19 @@ class BrightnessThrottler {
mThrottlingStatus = THROTTLING_INVALID;
}
- void resetThrottlingData(BrightnessThrottlingData throttlingData) {
+ private void resetThrottlingData() {
+ resetThrottlingData(mDdcThrottlingData, mUniqueDisplayId);
+ }
+
+ void resetThrottlingData(BrightnessThrottlingData throttlingData, String displayId) {
stop();
- mThrottlingData = throttlingData;
+
+ mUniqueDisplayId = displayId;
+ mDdcThrottlingData = throttlingData;
+ mDeviceConfigListener.startListening();
+ reloadBrightnessThrottlingDataOverride();
+ mThrottlingData = mBrightnessThrottlingDataOverride.getOrDefault(mUniqueDisplayId,
+ throttlingData);
if (deviceSupportsThrottling()) {
mSkinThermalStatusObserver.startObserving();
@@ -173,14 +219,148 @@ class BrightnessThrottler {
private void dumpLocal(PrintWriter pw) {
pw.println("BrightnessThrottler:");
pw.println(" mThrottlingData=" + mThrottlingData);
+ pw.println(" mDdcThrottlingData=" + mDdcThrottlingData);
+ pw.println(" mUniqueDisplayId=" + mUniqueDisplayId);
pw.println(" mThrottlingStatus=" + mThrottlingStatus);
pw.println(" mBrightnessCap=" + mBrightnessCap);
pw.println(" mBrightnessMaxReason=" +
BrightnessInfo.briMaxReasonToString(mBrightnessMaxReason));
+ pw.println(" mBrightnessThrottlingDataOverride=" + mBrightnessThrottlingDataOverride);
+ pw.println(" mBrightnessThrottlingDataString=" + mBrightnessThrottlingDataString);
mSkinThermalStatusObserver.dump(pw);
}
+ private String getBrightnessThrottlingDataString() {
+ return mDeviceConfig.getString(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA,
+ /* defaultValue= */ null);
+ }
+
+ private boolean parseAndSaveData(@NonNull String strArray,
+ @NonNull HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData) {
+ boolean validConfig = true;
+ String[] items = strArray.split(",");
+ int i = 0;
+
+ try {
+ String uniqueDisplayId = items[i++];
+
+ // number of throttling points
+ int noOfThrottlingPoints = Integer.parseInt(items[i++]);
+ List<ThrottlingLevel> throttlingLevels = new ArrayList<>(noOfThrottlingPoints);
+
+ // throttling level and point
+ for (int j = 0; j < noOfThrottlingPoints; j++) {
+ String severity = items[i++];
+ int status = parseThermalStatus(severity);
+
+ float brightnessPoint = parseBrightness(items[i++]);
+
+ throttlingLevels.add(new ThrottlingLevel(status, brightnessPoint));
+ }
+ BrightnessThrottlingData toSave =
+ DisplayDeviceConfig.BrightnessThrottlingData.create(throttlingLevels);
+ tempBrightnessThrottlingData.put(uniqueDisplayId, toSave);
+ } catch (NumberFormatException | IndexOutOfBoundsException
+ | UnknownThermalStatusException e) {
+ validConfig = false;
+ Slog.e(TAG, "Throttling data is invalid array: '" + strArray + "'", e);
+ }
+
+ if (i != items.length) {
+ validConfig = false;
+ }
+
+ return validConfig;
+ }
+
+ public void reloadBrightnessThrottlingDataOverride() {
+ HashMap<String, BrightnessThrottlingData> tempBrightnessThrottlingData =
+ new HashMap<>(1);
+ mBrightnessThrottlingDataString = getBrightnessThrottlingDataString();
+ boolean validConfig = true;
+ mBrightnessThrottlingDataOverride.clear();
+ if (mBrightnessThrottlingDataString != null) {
+ String[] throttlingDataSplits = mBrightnessThrottlingDataString.split(";");
+ for (String s : throttlingDataSplits) {
+ if (!parseAndSaveData(s, tempBrightnessThrottlingData)) {
+ validConfig = false;
+ break;
+ }
+ }
+
+ if (validConfig) {
+ mBrightnessThrottlingDataOverride.putAll(tempBrightnessThrottlingData);
+ tempBrightnessThrottlingData.clear();
+ }
+
+ } else {
+ Slog.w(TAG, "DeviceConfig BrightnessThrottlingData is null");
+ }
+ }
+
+ /**
+ * Listens to config data change and updates the brightness throttling data using
+ * DisplayManager#KEY_BRIGHTNESS_THROTTLING_DATA.
+ * The format should be a string similar to: "local:4619827677550801152,2,moderate,0.5,severe,
+ * 0.379518072;local:4619827677550801151,1,moderate,0.75"
+ * In this order:
+ * <displayId>,<no of throttling levels>,[<severity as string>,<brightness cap>]
+ * Where the latter part is repeated for each throttling level, and the entirety is repeated
+ * for each display, separated by a semicolon.
+ */
+ public class DeviceConfigListener implements DeviceConfig.OnPropertiesChangedListener {
+ public Executor mExecutor = new HandlerExecutor(mDeviceConfigHandler);
+
+ public void startListening() {
+ mDeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ mExecutor, this);
+ }
+
+ @Override
+ public void onPropertiesChanged(DeviceConfig.Properties properties) {
+ reloadBrightnessThrottlingDataOverride();
+ resetThrottlingData();
+ }
+ }
+
+ private float parseBrightness(String intVal) throws NumberFormatException {
+ float value = Float.parseFloat(intVal);
+ if (value < PowerManager.BRIGHTNESS_MIN || value > PowerManager.BRIGHTNESS_MAX) {
+ throw new NumberFormatException("Brightness constraint value out of bounds.");
+ }
+ return value;
+ }
+
+ @PowerManager.ThermalStatus private int parseThermalStatus(@NonNull String value)
+ throws UnknownThermalStatusException {
+ switch (value) {
+ case "none":
+ return PowerManager.THERMAL_STATUS_NONE;
+ case "light":
+ return PowerManager.THERMAL_STATUS_LIGHT;
+ case "moderate":
+ return PowerManager.THERMAL_STATUS_MODERATE;
+ case "severe":
+ return PowerManager.THERMAL_STATUS_SEVERE;
+ case "critical":
+ return PowerManager.THERMAL_STATUS_CRITICAL;
+ case "emergency":
+ return PowerManager.THERMAL_STATUS_EMERGENCY;
+ case "shutdown":
+ return PowerManager.THERMAL_STATUS_SHUTDOWN;
+ default:
+ throw new UnknownThermalStatusException("Invalid Thermal Status: " + value);
+ }
+ }
+
+ private static class UnknownThermalStatusException extends Exception {
+ UnknownThermalStatusException(String message) {
+ super(message);
+ }
+ }
+
private final class SkinThermalStatusObserver extends IThermalEventListener.Stub {
private final Injector mInjector;
private final Handler mHandler;
@@ -258,5 +438,10 @@ class BrightnessThrottler {
return IThermalService.Stub.asInterface(
ServiceManager.getService(Context.THERMAL_SERVICE));
}
+
+ @NonNull
+ public DeviceConfigInterface getDeviceConfig() {
+ return DeviceConfigInterface.REAL;
+ }
}
}
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a25ac210f9c8..b5aa7b14792b 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -279,11 +279,16 @@ public class DisplayDeviceConfig {
private HighBrightnessModeData mHbmData;
private DensityMapping mDensityMapping;
private String mLoadedFrom = null;
+ private Spline mSdrToHdrRatioSpline;
+ // Brightness Throttling data may be updated via the DeviceConfig. Here we store the original
+ // data, which comes from the ddc, and the current one, which may be the DeviceConfig
+ // overwritten value.
private BrightnessThrottlingData mBrightnessThrottlingData;
- private Spline mSdrToHdrRatioSpline;
+ private BrightnessThrottlingData mOriginalBrightnessThrottlingData;
- private DisplayDeviceConfig(Context context) {
+ @VisibleForTesting
+ DisplayDeviceConfig(Context context) {
mContext = context;
}
@@ -422,6 +427,10 @@ public class DisplayDeviceConfig {
return config;
}
+ void setBrightnessThrottlingData(BrightnessThrottlingData brightnessThrottlingData) {
+ mBrightnessThrottlingData = brightnessThrottlingData;
+ }
+
/**
* Return the brightness mapping nits array.
*
@@ -637,6 +646,7 @@ public class DisplayDeviceConfig {
+ ", mHbmData=" + mHbmData
+ ", mSdrToHdrRatioSpline=" + mSdrToHdrRatioSpline
+ ", mBrightnessThrottlingData=" + mBrightnessThrottlingData
+ + ", mOriginalBrightnessThrottlingData=" + mOriginalBrightnessThrottlingData
+ ", mBrightnessRampFastDecrease=" + mBrightnessRampFastDecrease
+ ", mBrightnessRampFastIncrease=" + mBrightnessRampFastIncrease
+ ", mBrightnessRampSlowDecrease=" + mBrightnessRampSlowDecrease
@@ -682,7 +692,8 @@ public class DisplayDeviceConfig {
return config;
}
- private boolean initFromFile(File configFile) {
+ @VisibleForTesting
+ boolean initFromFile(File configFile) {
if (!configFile.exists()) {
// Display configuration files aren't required to exist.
return false;
@@ -932,6 +943,7 @@ public class DisplayDeviceConfig {
if (!badConfig) {
mBrightnessThrottlingData = BrightnessThrottlingData.create(throttlingLevels);
+ mOriginalBrightnessThrottlingData = mBrightnessThrottlingData;
}
}
@@ -1407,7 +1419,9 @@ public class DisplayDeviceConfig {
/**
* Container for brightness throttling data.
*/
- static class BrightnessThrottlingData {
+ public static class BrightnessThrottlingData {
+ public List<ThrottlingLevel> throttlingLevels;
+
static class ThrottlingLevel {
public @PowerManager.ThermalStatus int thermalStatus;
public float brightness;
@@ -1421,9 +1435,25 @@ public class DisplayDeviceConfig {
public String toString() {
return "[" + thermalStatus + "," + brightness + "]";
}
- }
- public List<ThrottlingLevel> throttlingLevels;
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof ThrottlingLevel)) {
+ return false;
+ }
+ ThrottlingLevel otherThrottlingLevel = (ThrottlingLevel) obj;
+
+ return otherThrottlingLevel.thermalStatus == this.thermalStatus
+ && otherThrottlingLevel.brightness == this.brightness;
+ }
+ @Override
+ public int hashCode() {
+ int result = 1;
+ result = 31 * result + thermalStatus;
+ result = 31 * result + Float.hashCode(brightness);
+ return result;
+ }
+ }
static public BrightnessThrottlingData create(List<ThrottlingLevel> throttlingLevels)
{
@@ -1482,12 +1512,30 @@ public class DisplayDeviceConfig {
+ "} ";
}
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+
+ if (!(obj instanceof BrightnessThrottlingData)) {
+ return false;
+ }
+
+ BrightnessThrottlingData otherBrightnessThrottlingData = (BrightnessThrottlingData) obj;
+ return throttlingLevels.equals(otherBrightnessThrottlingData.throttlingLevels);
+ }
+
+ @Override
+ public int hashCode() {
+ return throttlingLevels.hashCode();
+ }
+
private BrightnessThrottlingData(List<ThrottlingLevel> inLevels) {
throttlingLevels = new ArrayList<>(inLevels.size());
for (ThrottlingLevel level : inLevels) {
throttlingLevels.add(new ThrottlingLevel(level.thermalStatus, level.brightness));
}
}
-
}
}
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index d3e2966c6df0..6285ef1fdabd 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -485,7 +485,7 @@ public final class DisplayManagerService extends SystemService {
mUiHandler = UiThread.getHandler();
mDisplayDeviceRepo = new DisplayDeviceRepository(mSyncRoot, mPersistentDataStore);
mLogicalDisplayMapper = new LogicalDisplayMapper(mContext, mDisplayDeviceRepo,
- new LogicalDisplayListener(), mSyncRoot, mHandler, new DeviceStateToLayoutMap());
+ new LogicalDisplayListener(), mSyncRoot, mHandler);
mDisplayModeDirector = new DisplayModeDirector(context, mHandler);
mBrightnessSynchronizer = new BrightnessSynchronizer(mContext);
Resources resources = mContext.getResources();
@@ -945,8 +945,7 @@ public final class DisplayManagerService extends SystemService {
private DisplayInfo getDisplayInfoInternal(int displayId, int callingUid) {
synchronized (mSyncRoot) {
- final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId,
- /* includeDisabledDisplays= */ true);
+ final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(displayId);
if (display != null) {
final DisplayInfo info =
getDisplayInfoForFrameRateOverride(display.getFrameRateOverrides(),
@@ -2129,18 +2128,16 @@ public final class DisplayManagerService extends SystemService {
}
void resetBrightnessConfigurations() {
- synchronized (mSyncRoot) {
- mPersistentDataStore.setBrightnessConfigurationForUser(null, mContext.getUserId(),
+ mPersistentDataStore.setBrightnessConfigurationForUser(null, mContext.getUserId(),
+ mContext.getPackageName());
+ mLogicalDisplayMapper.forEachLocked((logicalDisplay -> {
+ if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
+ return;
+ }
+ final String uniqueId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
+ setBrightnessConfigurationForDisplayInternal(null, uniqueId, mContext.getUserId(),
mContext.getPackageName());
- mLogicalDisplayMapper.forEachLocked((logicalDisplay -> {
- if (logicalDisplay.getDisplayInfoLocked().type != Display.TYPE_INTERNAL) {
- return;
- }
- String uniqueId = logicalDisplay.getPrimaryDisplayDeviceLocked().getUniqueId();
- setBrightnessConfigurationForDisplayInternal(null, uniqueId, mContext.getUserId(),
- mContext.getPackageName());
- }));
- }
+ }));
}
void setAutoBrightnessLoggingEnabled(boolean enabled) {
@@ -2817,16 +2814,15 @@ public final class DisplayManagerService extends SystemService {
}
/**
- * Returns the list of all enabled display ids, and disabled ones if specified.
+ * Returns the list of all display ids.
*/
@Override // Binder call
- public int[] getDisplayIds(boolean includeDisabledDisplays) {
+ public int[] getDisplayIds() {
final int callingUid = Binder.getCallingUid();
final long token = Binder.clearCallingIdentity();
try {
synchronized (mSyncRoot) {
- return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid,
- includeDisabledDisplays);
+ return mLogicalDisplayMapper.getDisplayIdsLocked(callingUid);
}
} finally {
Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 6a57e4070f65..95c8fef12976 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -873,7 +873,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
}
});
mBrightnessThrottler.resetThrottlingData(
- mDisplayDeviceConfig.getBrightnessThrottlingData());
+ mDisplayDeviceConfig.getBrightnessThrottlingData(), mUniqueDisplayId);
}
private void sendUpdatePowerState() {
@@ -1842,7 +1842,7 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call
() -> {
sendUpdatePowerStateLocked();
postBrightnessChangeRunnable();
- });
+ }, mUniqueDisplayId);
}
private void blockScreenOn() {
diff --git a/services/core/java/com/android/server/display/LogicalDisplay.java b/services/core/java/com/android/server/display/LogicalDisplay.java
index 90b9967eef59..d14902eaf8f5 100644
--- a/services/core/java/com/android/server/display/LogicalDisplay.java
+++ b/services/core/java/com/android/server/display/LogicalDisplay.java
@@ -522,10 +522,12 @@ final class LogicalDisplay {
// Set the layer stack.
device.setLayerStackLocked(t, isBlanked ? BLANK_LAYER_STACK : mLayerStack);
// Also inform whether the device is the same one sent to inputflinger for its layerstack.
+ // Prevent displays that are disabled from receiving input.
// TODO(b/188914255): Remove once input can dispatch against device vs layerstack.
device.setDisplayFlagsLocked(t,
- device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE
- ? SurfaceControl.DISPLAY_RECEIVES_INPUT : 0);
+ (isEnabled() && device.getDisplayDeviceInfoLocked().touch != TOUCH_NONE)
+ ? SurfaceControl.DISPLAY_RECEIVES_INPUT
+ : 0);
// Set the color mode and allowed display mode.
if (device == mPrimaryDisplayDevice) {
diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
index 34e8e75314ce..70c9e23c6af8 100644
--- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java
+++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java
@@ -79,12 +79,8 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
private static final int MSG_TRANSITION_TO_PENDING_DEVICE_STATE = 1;
private static final int UPDATE_STATE_NEW = 0;
- private static final int UPDATE_STATE_UPDATED = 1;
- private static final int UPDATE_STATE_DISABLED = 2;
-
- private static final int UPDATE_STATE_MASK = 0x3;
-
- private static final int UPDATE_STATE_FLAG_TRANSITION = 0x100;
+ private static final int UPDATE_STATE_TRANSITION = 1;
+ private static final int UPDATE_STATE_UPDATED = 2;
/**
* Temporary display info, used for comparing display configurations.
@@ -170,7 +166,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
LogicalDisplayMapper(@NonNull Context context, @NonNull DisplayDeviceRepository repo,
@NonNull Listener listener, @NonNull DisplayManagerService.SyncRoot syncRoot,
- @NonNull Handler handler, @NonNull DeviceStateToLayoutMap deviceStateToLayoutMap) {
+ @NonNull Handler handler) {
mSyncRoot = syncRoot;
mPowerManager = context.getSystemService(PowerManager.class);
mInteractive = mPowerManager.isInteractive();
@@ -185,7 +181,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mDeviceStatesOnWhichToSleep = toSparseBooleanArray(context.getResources().getIntArray(
com.android.internal.R.array.config_deviceStatesOnWhichToSleep));
mDisplayDeviceRepo.addListener(this);
- mDeviceStateToLayoutMap = deviceStateToLayoutMap;
+ mDeviceStateToLayoutMap = new DeviceStateToLayoutMap();
}
@Override
@@ -222,29 +218,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
public LogicalDisplay getDisplayLocked(int displayId) {
- return getDisplayLocked(displayId, /* includeDisabled= */ false);
- }
-
- LogicalDisplay getDisplayLocked(int displayId, boolean includeDisabled) {
- LogicalDisplay display = mLogicalDisplays.get(displayId);
- if (display != null && (display.isEnabled() || includeDisabled)) {
- return display;
- }
- return null;
+ return mLogicalDisplays.get(displayId);
}
public LogicalDisplay getDisplayLocked(DisplayDevice device) {
- return getDisplayLocked(device, /* includeDisabled= */ false);
- }
-
- /**
- * Loops through the existing list of displays and returns one that is associated with the
- * specified display device.
- *
- * @param device The display device that should be associated with the LogicalDisplay.
- * @param includeDisabled True if this method should return disabled displays as well.
- */
- private LogicalDisplay getDisplayLocked(DisplayDevice device, boolean includeDisabled) {
if (device == null) {
return null;
}
@@ -252,32 +229,18 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
for (int i = 0; i < count; i++) {
final LogicalDisplay display = mLogicalDisplays.valueAt(i);
if (display.getPrimaryDisplayDeviceLocked() == device) {
- if (display.isEnabled() || includeDisabled) {
- return display;
- } else {
- return null;
- }
+ return display;
}
}
return null;
}
- // Returns display Ids, defaults to enabled only.
public int[] getDisplayIdsLocked(int callingUid) {
- return getDisplayIdsLocked(callingUid, /* includeDisabledDisplays= */ false);
- }
-
- // Returns display Ids, specified whether enabled only, or all displays.
- public int[] getDisplayIdsLocked(int callingUid, boolean includeDisabledDisplays) {
final int count = mLogicalDisplays.size();
int[] displayIds = new int[count];
int n = 0;
for (int i = 0; i < count; i++) {
LogicalDisplay display = mLogicalDisplays.valueAt(i);
- if (!includeDisabledDisplays && !display.isEnabled()) {
- continue; // Ignore disabled displays.
- }
-
DisplayInfo info = display.getDisplayInfoLocked();
if (info.hasAccess(callingUid)) {
displayIds[n++] = mLogicalDisplays.keyAt(i);
@@ -292,10 +255,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
public void forEachLocked(Consumer<LogicalDisplay> consumer) {
final int count = mLogicalDisplays.size();
for (int i = 0; i < count; i++) {
- LogicalDisplay display = mLogicalDisplays.valueAt(i);
- if (display.isEnabled()) {
- consumer.accept(display);
- }
+ consumer.accept(mLogicalDisplays.valueAt(i));
}
}
@@ -356,8 +316,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// Find or create the LogicalDisplay to map the DisplayDevice to.
final int logicalDisplayId = displayLayout.getLogicalDisplayId();
- final LogicalDisplay logicalDisplay =
- getDisplayLocked(logicalDisplayId, /* includeDisabled= */ true);
+ final LogicalDisplay logicalDisplay = getDisplayLocked(logicalDisplayId);
if (logicalDisplay == null) {
Slog.w(TAG, "The logical display (" + address + "), is not available"
+ " for the display state " + deviceState);
@@ -493,7 +452,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
/**
- * Returns true if the device should be put to sleep or not.
+ * Returns if the device should be put to sleep or not.
*
* Includes a check to verify that the device state that we are moving to, {@code pendingState},
* is the same as the physical state of the device, {@code baseState}. Different values for
@@ -639,12 +598,9 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
display.getNonOverrideDisplayInfoLocked(mTempNonOverrideDisplayInfo);
display.updateLocked(mDisplayDeviceRepo);
- DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
-
- final int storedState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);
- final int updateState = storedState & UPDATE_STATE_MASK;
- final boolean isTransitioning = (storedState & UPDATE_STATE_FLAG_TRANSITION) != 0;
- final boolean wasPreviouslyUpdated = updateState == UPDATE_STATE_UPDATED;
+ final DisplayInfo newDisplayInfo = display.getDisplayInfoLocked();
+ final int updateState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);
+ final boolean wasPreviouslyUpdated = updateState != UPDATE_STATE_NEW;
// The display is no longer valid and needs to be removed.
if (!display.isValidLocked()) {
@@ -668,35 +624,6 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
continue;
- // The display has been newly disabled, we report this as a removed display but
- // don't actually remove it from our internal list in LogicalDisplayMapper. The reason
- // is that LogicalDisplayMapper assumes and relies on the fact that every DisplayDevice
- // has a LogicalDisplay wrapper, but certain displays that are unusable (like the inner
- // display on a folded foldable device) are not available for use by the system and
- // we keep them hidden. To do this, we mark those LogicalDisplays as "disabled".
- // Also, if the display is in TRANSITION but was previously reported as disabled
- // then keep it unreported.
- } else if (!display.isEnabled()
- || (display.getPhase() == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION
- && updateState == UPDATE_STATE_DISABLED)) {
- mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_DISABLED);
-
- // If we never told anyone about this display, nothing to do
- if (!wasPreviouslyUpdated) {
- continue;
- }
-
- // Remove from group
- final DisplayGroup displayGroup = getDisplayGroupLocked(
- getDisplayGroupIdFromDisplayIdLocked(displayId));
- if (displayGroup != null) {
- displayGroup.removeDisplayLocked(display);
- }
-
- Slog.i(TAG, "Removing (disabled) display: " + displayId);
- mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_REMOVED);
- continue;
-
// The display is new.
} else if (!wasPreviouslyUpdated) {
Slog.i(TAG, "Adding new display: " + displayId + ": " + newDisplayInfo);
@@ -716,7 +643,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
mLogicalDisplaysToUpdate.put(displayId, LOGICAL_DISPLAY_EVENT_CHANGED);
// The display is involved in a display layout transition
- } else if (isTransitioning) {
+ } else if (updateState == UPDATE_STATE_TRANSITION) {
mLogicalDisplaysToUpdate.put(displayId,
LOGICAL_DISPLAY_EVENT_DEVICE_STATE_TRANSITION);
@@ -790,7 +717,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
}
final int id = mLogicalDisplaysToUpdate.keyAt(i);
- final LogicalDisplay display = getDisplayLocked(id, /* includeDisabled= */ true);
+ final LogicalDisplay display = getDisplayLocked(id);
if (DEBUG) {
final DisplayDevice device = display.getPrimaryDisplayDeviceLocked();
final String uniqueId = device == null ? "null" : device.getUniqueId();
@@ -798,7 +725,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
+ " with device=" + uniqueId);
}
mListener.onLogicalDisplayEventLocked(display, msg);
- if (msg == LOGICAL_DISPLAY_EVENT_REMOVED && !display.isValidLocked()) {
+ if (msg == LOGICAL_DISPLAY_EVENT_REMOVED) {
// We wait until we sent the EVENT_REMOVED event before actually removing the
// display.
mLogicalDisplays.delete(id);
@@ -918,8 +845,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
if (isTransitioning) {
setDisplayPhase(logicalDisplay, phase);
if (phase == LogicalDisplay.DISPLAY_PHASE_LAYOUT_TRANSITION) {
- int oldState = mUpdatedLogicalDisplays.get(displayId, UPDATE_STATE_NEW);
- mUpdatedLogicalDisplays.put(displayId, oldState | UPDATE_STATE_FLAG_TRANSITION);
+ mUpdatedLogicalDisplays.put(displayId, UPDATE_STATE_TRANSITION);
}
}
}
@@ -953,15 +879,14 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
// Now that we have a display-device, we need a LogicalDisplay to map it to. Find the
// right one, if it doesn't exist, create a new one.
final int logicalDisplayId = displayLayout.getLogicalDisplayId();
- LogicalDisplay newDisplay =
- getDisplayLocked(logicalDisplayId, /* includeDisabled= */ true);
+ LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId);
if (newDisplay == null) {
newDisplay = createNewLogicalDisplayLocked(
- /* displayDevice= */ null, logicalDisplayId);
+ null /*displayDevice*/, logicalDisplayId);
}
// Now swap the underlying display devices between the old display and the new display
- final LogicalDisplay oldDisplay = getDisplayLocked(device, /* includeDisabled= */ true);
+ final LogicalDisplay oldDisplay = getDisplayLocked(device);
if (newDisplay != oldDisplay) {
newDisplay.swapDisplaysLocked(oldDisplay);
}
@@ -978,14 +903,13 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener {
* Creates a new logical display for the specified device and display Id and adds it to the list
* of logical displays.
*
- * @param displayDevice The displayDevice to associate with the LogicalDisplay.
+ * @param device The device to associate with the LogicalDisplay.
* @param displayId The display ID to give the new display. If invalid, a new ID is assigned.
* @return The new logical display if created, null otherwise.
*/
- private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice displayDevice,
- int displayId) {
+ private LogicalDisplay createNewLogicalDisplayLocked(DisplayDevice device, int displayId) {
final int layerStack = assignLayerStackLocked(displayId);
- final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, displayDevice);
+ final LogicalDisplay display = new LogicalDisplay(displayId, layerStack, device);
display.updateLocked(mDisplayDeviceRepo);
mLogicalDisplays.put(displayId, display);
setDisplayPhase(display, LogicalDisplay.DISPLAY_PHASE_ENABLED);
diff --git a/services/core/java/com/android/server/display/color/ColorDisplayService.java b/services/core/java/com/android/server/display/color/ColorDisplayService.java
index 8de150ac4124..223b8c181fea 100644
--- a/services/core/java/com/android/server/display/color/ColorDisplayService.java
+++ b/services/core/java/com/android/server/display/color/ColorDisplayService.java
@@ -956,6 +956,8 @@ public final class ColorDisplayService extends SystemService {
R.array.config_availableColorModes);
if (availableColorModes.length > 0) {
colorMode = availableColorModes[0];
+ } else {
+ colorMode = NOT_SET;
}
}
}
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 63c5456c972b..7b60345caf87 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -23,6 +23,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.TaskInfo;
@@ -45,6 +46,9 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.PowerManager;
import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ShellCallback;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
@@ -221,6 +225,10 @@ public final class DreamManagerService extends SystemService {
}
}
+ protected void requestStartDreamFromShell() {
+ requestDreamInternal();
+ }
+
private void requestDreamInternal() {
// Ask the power manager to nap. It will eventually call back into
// startDream() if/when it is appropriate to start dreaming.
@@ -275,6 +283,10 @@ public final class DreamManagerService extends SystemService {
}
}
+ protected void requestStopDreamFromShell() {
+ stopDreamInternal(true, "stopping dream from shell");
+ }
+
private void stopDreamInternal(boolean immediate, String reason) {
synchronized (mLock) {
stopDreamLocked(immediate, reason);
@@ -593,6 +605,14 @@ public final class DreamManagerService extends SystemService {
}
}
+ public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
+ @Nullable FileDescriptor err,
+ @NonNull String[] args, @Nullable ShellCallback callback,
+ @NonNull ResultReceiver resultReceiver) throws RemoteException {
+ new DreamShellCommand(DreamManagerService.this, mPowerManager)
+ .exec(this, in, out, err, args, callback, resultReceiver);
+ }
+
@Override // Binder call
public ComponentName[] getDreamComponents() {
return getDreamComponentsForUser(UserHandle.getCallingUserId());
diff --git a/services/core/java/com/android/server/dreams/DreamShellCommand.java b/services/core/java/com/android/server/dreams/DreamShellCommand.java
new file mode 100644
index 000000000000..eae7e80a89f1
--- /dev/null
+++ b/services/core/java/com/android/server/dreams/DreamShellCommand.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.dreams;
+
+import android.annotation.NonNull;
+import android.os.Binder;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.ShellCommand;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+
+/**
+ * {@link DreamShellCommand} allows accessing dream functionality, including toggling dream state.
+ */
+public class DreamShellCommand extends ShellCommand {
+ private static final boolean DEBUG = true;
+ private static final String TAG = "DreamShellCommand";
+ private final @NonNull DreamManagerService mService;
+ private final @NonNull PowerManager mPowerManager;
+
+ DreamShellCommand(@NonNull DreamManagerService service, @NonNull PowerManager powerManager) {
+ mService = service;
+ mPowerManager = powerManager;
+ }
+
+ @Override
+ public int onCommand(String cmd) {
+ final int callingUid = Binder.getCallingUid();
+ if (callingUid != Process.ROOT_UID) {
+ Slog.e(TAG, "Must be root before calling Dream shell commands");
+ return -1;
+ }
+
+ if (TextUtils.isEmpty(cmd)) {
+ return super.handleDefaultCommands(cmd);
+ }
+ if (DEBUG) {
+ Slog.d(TAG, "onCommand:" + cmd);
+ }
+
+ switch (cmd) {
+ case "start-dreaming":
+ return startDreaming();
+ case "stop-dreaming":
+ return stopDreaming();
+ default:
+ return super.handleDefaultCommands(cmd);
+ }
+ }
+
+ private int startDreaming() {
+ mPowerManager.wakeUp(SystemClock.uptimeMillis(),
+ PowerManager.WAKE_REASON_PLUGGED_IN, "shell:cmd:android.service.dreams:DREAM");
+ mService.requestStartDreamFromShell();
+ return 0;
+ }
+
+ private int stopDreaming() {
+ mService.requestStopDreamFromShell();
+ return 0;
+ }
+
+ @Override
+ public void onHelp() {
+ PrintWriter pw = getOutPrintWriter();
+ pw.println("Dream manager (dreams) commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" start-dreaming");
+ pw.println(" Start the currently configured dream.");
+ pw.println(" stop-dreaming");
+ pw.println(" Stops any active dream");
+ }
+}
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 1a1c265f568d..9d15ed33797f 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5754,10 +5754,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
@Override
public void onImeParentChanged() {
synchronized (ImfLock.class) {
- // Hide the IME method menu when the IME surface parent will change in
- // case seeing the dialog dismiss flickering during the next focused window
- // starting the input connection.
- mMenuController.hideInputMethodMenu();
+ // Hide the IME method menu only when the IME surface parent is changed by the
+ // input target changed, in case seeing the dialog dismiss flickering during
+ // the next focused window starting the input connection.
+ if (mLastImeTargetWindow != mCurFocusedWindow) {
+ mMenuController.hideInputMethodMenu();
+ }
}
}
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
index 9fb1d8e744fb..625f1936e28e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubClientBroker.java
@@ -871,10 +871,9 @@ public class ContextHubClientBroker extends IContextHubClient.Stub
if (!mPendingIntentRequest.hasPendingIntent() && mRegistered) {
mClientManager.unregisterClient(mHostEndPointId);
mRegistered = false;
+ mAppOpsManager.stopWatchingMode(this);
+ mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId);
}
- mAppOpsManager.stopWatchingMode(this);
-
- mContextHubProxy.onHostEndpointDisconnected(mHostEndPointId);
}
private String authStateToString(@ContextHubManager.AuthorizationState int state) {
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 111621da06ce..5b2188ac078e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -1183,11 +1183,11 @@ public class ContextHubService extends IContextHubService.Stub {
}
} else {
Log.d(TAG, "BT adapter not available. Defaulting to disabled");
- if (mIsBtMainEnabled) {
+ if (forceUpdate || mIsBtMainEnabled) {
mIsBtMainEnabled = false;
mContextHubWrapper.onBtMainSettingChanged(mIsBtMainEnabled);
}
- if (mIsBtScanningEnabled) {
+ if (forceUpdate || mIsBtScanningEnabled) {
mIsBtScanningEnabled = false;
mContextHubWrapper.onBtScanningSettingChanged(mIsBtScanningEnabled);
}
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
index 098e8f74749c..7d12ede754ef 100644
--- a/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionManagerService.java
@@ -46,7 +46,6 @@ import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.Slog;
import android.view.ContentRecordingSession;
-import android.window.WindowContainerToken;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.DumpUtils;
@@ -433,7 +432,7 @@ public final class MediaProjectionManagerService extends SystemService
private IBinder mToken;
private IBinder.DeathRecipient mDeathEater;
private boolean mRestoreSystemAlertWindow;
- private WindowContainerToken mTaskRecordingWindowContainerToken = null;
+ private IBinder mLaunchCookie = null;
MediaProjection(int type, int uid, String packageName, int targetSdkVersion,
boolean isPrivileged) {
@@ -609,14 +608,13 @@ public final class MediaProjectionManagerService extends SystemService
}
@Override // Binder call
- public void setTaskRecordingWindowContainerToken(WindowContainerToken token) {
- // TODO(b/221417940) set the task id to record from sysui, for the package chosen.
- mTaskRecordingWindowContainerToken = token;
+ public void setLaunchCookie(IBinder launchCookie) {
+ mLaunchCookie = launchCookie;
}
@Override // Binder call
- public WindowContainerToken getTaskRecordingWindowContainerToken() {
- return mTaskRecordingWindowContainerToken;
+ public IBinder getLaunchCookie() {
+ return mLaunchCookie;
}
public MediaProjectionInfo getProjectionInfo() {
diff --git a/services/core/java/com/android/server/notification/ManagedServices.java b/services/core/java/com/android/server/notification/ManagedServices.java
index e6bc79679a8f..5a40b30551b6 100644
--- a/services/core/java/com/android/server/notification/ManagedServices.java
+++ b/services/core/java/com/android/server/notification/ManagedServices.java
@@ -1447,6 +1447,16 @@ abstract public class ManagedServices {
}
}
+ @VisibleForTesting
+ void reregisterService(final ComponentName cn, final int userId) {
+ // If rebinding a package that died, ensure it still has permission
+ // after the rebind delay
+ if (isPackageOrComponentAllowed(cn.getPackageName(), userId)
+ || isPackageOrComponentAllowed(cn.flattenToString(), userId)) {
+ registerService(cn, userId);
+ }
+ }
+
/**
* Inject a system service into the management list.
*/
@@ -1545,12 +1555,9 @@ abstract public class ManagedServices {
unbindService(this, name, userid);
if (!mServicesRebinding.contains(servicesBindingTag)) {
mServicesRebinding.add(servicesBindingTag);
- mHandler.postDelayed(new Runnable() {
- @Override
- public void run() {
- registerService(name, userid);
- }
- }, ON_BINDING_DIED_REBIND_DELAY_MS);
+ mHandler.postDelayed(() ->
+ reregisterService(name, userid),
+ ON_BINDING_DIED_REBIND_DELAY_MS);
} else {
Slog.v(TAG, getCaption() + " not rebinding in user " + userid
+ " as a previous rebind attempt was made: " + name);
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
index a9b2570a3dda..7fe2f81aa231 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabase.java
@@ -16,15 +16,8 @@
package com.android.server.notification;
-import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
-import android.app.PendingIntent;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.net.Uri;
import android.os.Handler;
import android.util.AtomicFile;
import android.util.Slog;
@@ -60,18 +53,9 @@ public class NotificationHistoryDatabase {
private static final String TAG = "NotiHistoryDatabase";
private static final boolean DEBUG = NotificationManagerService.DBG;
private static final int HISTORY_RETENTION_DAYS = 1;
- private static final int HISTORY_RETENTION_MS = 24 * 60 * 60 * 1000;
private static final long WRITE_BUFFER_INTERVAL_MS = 1000 * 60 * 20;
private static final long INVALID_FILE_TIME_MS = -1;
- private static final String ACTION_HISTORY_DELETION =
- NotificationHistoryDatabase.class.getSimpleName() + ".CLEANUP";
- private static final int REQUEST_CODE_DELETION = 1;
- private static final String SCHEME_DELETION = "delete";
- private static final String EXTRA_KEY = "key";
-
- private final Context mContext;
- private final AlarmManager mAlarmManager;
private final Object mLock = new Object();
private final Handler mFileWriteHandler;
@VisibleForTesting
@@ -87,9 +71,7 @@ public class NotificationHistoryDatabase {
@VisibleForTesting
NotificationHistory mBuffer;
- public NotificationHistoryDatabase(Context context, Handler fileWriteHandler, File dir) {
- mContext = context;
- mAlarmManager = context.getSystemService(AlarmManager.class);
+ public NotificationHistoryDatabase(Handler fileWriteHandler, File dir) {
mCurrentVersion = DEFAULT_CURRENT_VERSION;
mFileWriteHandler = fileWriteHandler;
mVersionFile = new File(dir, "version");
@@ -97,11 +79,6 @@ public class NotificationHistoryDatabase {
mHistoryFiles = new LinkedList<>();
mBuffer = new NotificationHistory();
mWriteBufferRunnable = new WriteBufferRunnable();
-
- IntentFilter deletionFilter = new IntentFilter(ACTION_HISTORY_DELETION);
- deletionFilter.addDataScheme(SCHEME_DELETION);
- mContext.registerReceiver(mFileCleanupReceiver, deletionFilter,
- Context.RECEIVER_EXPORTED_UNAUDITED);
}
public void init() {
@@ -117,7 +94,7 @@ public class NotificationHistoryDatabase {
checkVersionAndBuildLocked();
indexFilesLocked();
- prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
+ prune();
}
}
@@ -246,7 +223,14 @@ public class NotificationHistoryDatabase {
}
/**
- * Remove any files that are too old and schedule jobs to clean up the rest
+ * Remove any files that are too old.
+ */
+ void prune() {
+ prune(HISTORY_RETENTION_DAYS, System.currentTimeMillis());
+ }
+
+ /**
+ * Remove any files that are too old.
*/
void prune(final int retentionDays, final long currentTimeMillis) {
synchronized (mLock) {
@@ -265,10 +249,6 @@ public class NotificationHistoryDatabase {
if (creationTime <= retentionBoundary.getTimeInMillis()) {
deleteFile(currentOldestFile);
- } else {
- // all remaining files are newer than the cut off; schedule jobs to delete
- scheduleDeletion(
- currentOldestFile.getBaseFile(), creationTime, retentionDays);
}
}
}
@@ -306,26 +286,6 @@ public class NotificationHistoryDatabase {
removeFilePathFromHistory(file.getBaseFile().getAbsolutePath());
}
- private void scheduleDeletion(File file, long creationTime, int retentionDays) {
- final long deletionTime = creationTime + (retentionDays * HISTORY_RETENTION_MS);
- scheduleDeletion(file, deletionTime);
- }
-
- private void scheduleDeletion(File file, long deletionTime) {
- if (DEBUG) {
- Slog.d(TAG, "Scheduling deletion for " + file.getName() + " at " + deletionTime);
- }
- final PendingIntent pi = PendingIntent.getBroadcast(mContext,
- REQUEST_CODE_DELETION,
- new Intent(ACTION_HISTORY_DELETION)
- .setData(new Uri.Builder().scheme(SCHEME_DELETION)
- .appendPath(file.getAbsolutePath()).build())
- .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_KEY, file.getAbsolutePath()),
- PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
- mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, deletionTime, pi);
- }
-
private void writeLocked(AtomicFile file, NotificationHistory notifications)
throws IOException {
FileOutputStream fos = file.startWrite();
@@ -355,12 +315,6 @@ public class NotificationHistoryDatabase {
}
}
- public void unregisterFileCleanupReceiver() {
- if(mContext != null) {
- mContext.unregisterReceiver(mFileCleanupReceiver);
- }
- }
-
private static long safeParseLong(String fileName) {
// AtomicFile will create copies of the numeric files with ".new" and ".bak"
// over the course of its processing. If these files still exist on boot we need to clean
@@ -372,40 +326,15 @@ public class NotificationHistoryDatabase {
}
}
- private final BroadcastReceiver mFileCleanupReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- if (action == null) {
- return;
- }
- if (ACTION_HISTORY_DELETION.equals(action)) {
- try {
- synchronized (mLock) {
- final String filePath = intent.getStringExtra(EXTRA_KEY);
- AtomicFile fileToDelete = new AtomicFile(new File(filePath));
- if (DEBUG) {
- Slog.d(TAG, "Removed " + fileToDelete.getBaseFile().getName());
- }
- fileToDelete.delete();
- removeFilePathFromHistory(filePath);
- }
- } catch (Exception e) {
- Slog.e(TAG, "Failed to delete notification history file", e);
- }
- }
- }
- };
-
final class WriteBufferRunnable implements Runnable {
@Override
public void run() {
long time = System.currentTimeMillis();
- run(time, new AtomicFile(new File(mHistoryDir, String.valueOf(time))));
+ run(new AtomicFile(new File(mHistoryDir, String.valueOf(time))));
}
- void run(long time, AtomicFile file) {
+ void run(AtomicFile file) {
synchronized (mLock) {
if (DEBUG) Slog.d(TAG, "WriteBufferRunnable "
+ file.getBaseFile().getAbsolutePath());
@@ -413,8 +342,6 @@ public class NotificationHistoryDatabase {
writeLocked(file, mBuffer);
mHistoryFiles.addFirst(file);
mBuffer = new NotificationHistory();
-
- scheduleDeletion(file.getBaseFile(), time, HISTORY_RETENTION_DAYS);
} catch (IOException e) {
Slog.e(TAG, "Failed to write buffer to disk. not flushing buffer", e);
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java b/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java
index d9e0d79883c5..0d975cc50d79 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryDatabaseFactory.java
@@ -35,6 +35,6 @@ public class NotificationHistoryDatabaseFactory {
if(sTestingNotificationHistoryDb != null) {
return sTestingNotificationHistoryDb;
}
- return new NotificationHistoryDatabase(context, handler, rootDir);
+ return new NotificationHistoryDatabase(handler, rootDir);
}
}
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryJobService.java b/services/core/java/com/android/server/notification/NotificationHistoryJobService.java
new file mode 100644
index 000000000000..3776ad7a0799
--- /dev/null
+++ b/services/core/java/com/android/server/notification/NotificationHistoryJobService.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static android.app.job.JobScheduler.RESULT_SUCCESS;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.CancellationSignal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This service runs every twenty minutes to ensure the retention policy for notification history
+ * data.
+ */
+public class NotificationHistoryJobService extends JobService {
+ private final static String TAG = "NotificationHistoryJob";
+ private static final long JOB_RUN_INTERVAL = TimeUnit.MINUTES.toMillis(20);
+
+ static final int BASE_JOB_ID = 237039804;
+
+ static void scheduleJob(Context context) {
+ JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+ if (jobScheduler.getPendingJob(BASE_JOB_ID) == null) {
+ ComponentName component =
+ new ComponentName(context, NotificationHistoryJobService.class);
+ JobInfo newJob = new JobInfo.Builder(BASE_JOB_ID, component)
+ .setRequiresDeviceIdle(false)
+ .setPeriodic(JOB_RUN_INTERVAL)
+ .build();
+ if (jobScheduler.schedule(newJob) != RESULT_SUCCESS) {
+ Slog.w(TAG, "Failed to schedule history cleanup job");
+ }
+ }
+ }
+
+ private CancellationSignal mSignal;
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ mSignal = new CancellationSignal();
+ new Thread(() -> {
+ NotificationManagerInternal nmInternal =
+ LocalServices.getService(NotificationManagerInternal.class);
+ nmInternal.cleanupHistoryFiles();
+ jobFinished(params, mSignal.isCanceled());
+ }).start();
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ if (mSignal != null) {
+ mSignal.cancel();
+ }
+ return false;
+ }
+}
+
diff --git a/services/core/java/com/android/server/notification/NotificationHistoryManager.java b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
index 0aacd130c837..6a46048e80e1 100644
--- a/services/core/java/com/android/server/notification/NotificationHistoryManager.java
+++ b/services/core/java/com/android/server/notification/NotificationHistoryManager.java
@@ -84,6 +84,11 @@ public class NotificationHistoryManager {
}
void onBootPhaseAppsCanStart() {
+ try {
+ NotificationHistoryJobService.scheduleJob(mContext);
+ } catch (Throwable e) {
+ Slog.e(TAG, "Failed to schedule cleanup job", e);
+ }
mSettingsObserver.observe();
}
@@ -151,6 +156,24 @@ public class NotificationHistoryManager {
}
}
+ public void cleanupHistoryFiles() {
+ synchronized (mLock) {
+ int n = mUserUnlockedStates.size();
+ for (int i = 0; i < n; i++) {
+ // cleanup old files for currently unlocked users. User are additionally cleaned
+ // on unlock in NotificationHistoryDatabase.init().
+ if (mUserUnlockedStates.valueAt(i)) {
+ final NotificationHistoryDatabase userHistory =
+ mUserState.get(mUserUnlockedStates.keyAt(i));
+ if (userHistory == null) {
+ continue;
+ }
+ userHistory.prune();
+ }
+ }
+ }
+ }
+
public void deleteNotificationHistoryItem(String pkg, int uid, long postedTime) {
synchronized (mLock) {
int userId = UserHandle.getUserId(uid);
@@ -288,7 +311,6 @@ public class NotificationHistoryManager {
private void disableHistory(NotificationHistoryDatabase userHistory, @UserIdInt int userId) {
userHistory.disableHistory();
- userHistory.unregisterFileCleanupReceiver();
mUserPendingHistoryDisables.put(userId, false);
mHistoryEnabled.put(userId, false);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
index 8a627367c1dc..bc3885605a6c 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java
@@ -45,4 +45,6 @@ public interface NotificationManagerInternal {
/** Send a notification to the user prompting them to review their notification permissions. */
void sendReviewPermissionsNotification();
+
+ void cleanupHistoryFiles();
}
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 829b9c7692fe..9bfdd0465168 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -517,7 +517,7 @@ public class NotificationManagerService extends SystemService {
private ActivityManagerInternal mAmi;
private IPackageManager mPackageManager;
private PackageManager mPackageManagerClient;
- private PackageManagerInternal mPackageManagerInternal;
+ PackageManagerInternal mPackageManagerInternal;
private PermissionPolicyInternal mPermissionPolicyInternal;
AudioManager mAudioManager;
AudioManagerInternal mAudioManagerInternal;
@@ -1794,6 +1794,7 @@ public class NotificationManagerService extends SystemService {
if (userHandle >= 0) {
cancelAllNotificationsInt(MY_UID, MY_PID, null, null, 0, 0, true, userHandle,
REASON_PROFILE_TURNED_OFF, null);
+ mSnoozeHelper.clearData(userHandle);
}
} else if (action.equals(Intent.ACTION_USER_PRESENT)) {
// turn off LED when user passes through lock screen
@@ -2458,11 +2459,11 @@ public class NotificationManagerService extends SystemService {
SnoozeHelper snoozeHelper = new SnoozeHelper(getContext(), (userId, r, muteOnReturn) -> {
try {
if (DBG) {
- Slog.d(TAG, "Reposting " + r.getKey());
+ Slog.d(TAG, "Reposting " + r.getKey() + " " + muteOnReturn);
}
enqueueNotificationInternal(r.getSbn().getPackageName(), r.getSbn().getOpPkg(),
r.getSbn().getUid(), r.getSbn().getInitialPid(), r.getSbn().getTag(),
- r.getSbn().getId(), r.getSbn().getNotification(), userId, true);
+ r.getSbn().getId(), r.getSbn().getNotification(), userId, muteOnReturn);
} catch (Exception e) {
Slog.e(TAG, "Cannot un-snooze notification", e);
}
@@ -2732,7 +2733,7 @@ public class NotificationManagerService extends SystemService {
}
@Override
- public void onUserUnlocking(@NonNull TargetUser user) {
+ public void onUserUnlocked(@NonNull TargetUser user) {
mHandler.post(() -> {
Trace.traceBegin(Trace.TRACE_TAG_SYSTEM_SERVER, "notifHistoryUnlockUser");
try {
@@ -6344,6 +6345,12 @@ public class NotificationManagerService extends SystemService {
Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN);
}
+
+ @Override
+ public void cleanupHistoryFiles() {
+ checkCallerIsSystem();
+ mHistoryManager.cleanupHistoryFiles();
+ }
};
int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) {
@@ -6757,9 +6764,8 @@ public class NotificationManagerService extends SystemService {
protected void doChannelWarningToast(int forUid, CharSequence toastText) {
Binder.withCleanCallingIdentity(() -> {
- final int defaultWarningEnabled = Build.IS_DEBUGGABLE ? 1 : 0;
final boolean warningEnabled = Settings.Global.getInt(getContext().getContentResolver(),
- Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, defaultWarningEnabled) != 0;
+ Settings.Global.SHOW_NOTIFICATION_CHANNEL_WARNINGS, 0) != 0;
if (warningEnabled) {
Toast toast = Toast.makeText(getContext(), mHandler.getLooper(), toastText,
Toast.LENGTH_SHORT);
@@ -7030,6 +7036,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void snoozeLocked(NotificationRecord r) {
+ final List<NotificationRecord> recordsToSnooze = new ArrayList<>();
if (r.getSbn().isGroup()) {
final List<NotificationRecord> groupNotifications =
findCurrentAndSnoozedGroupNotificationsLocked(
@@ -7038,8 +7045,8 @@ public class NotificationManagerService extends SystemService {
if (r.getNotification().isGroupSummary()) {
// snooze all children
for (int i = 0; i < groupNotifications.size(); i++) {
- if (mKey != groupNotifications.get(i).getKey()) {
- snoozeNotificationLocked(groupNotifications.get(i));
+ if (!mKey.equals(groupNotifications.get(i).getKey())) {
+ recordsToSnooze.add(groupNotifications.get(i));
}
}
} else {
@@ -7049,8 +7056,8 @@ public class NotificationManagerService extends SystemService {
if (groupNotifications.size() == 2) {
// snooze summary and the one child
for (int i = 0; i < groupNotifications.size(); i++) {
- if (mKey != groupNotifications.get(i).getKey()) {
- snoozeNotificationLocked(groupNotifications.get(i));
+ if (!mKey.equals(groupNotifications.get(i).getKey())) {
+ recordsToSnooze.add(groupNotifications.get(i));
}
}
}
@@ -7058,7 +7065,15 @@ public class NotificationManagerService extends SystemService {
}
}
// snooze the notification
- snoozeNotificationLocked(r);
+ recordsToSnooze.add(r);
+
+ if (mSnoozeHelper.canSnooze(recordsToSnooze.size())) {
+ for (int i = 0; i < recordsToSnooze.size(); i++) {
+ snoozeNotificationLocked(recordsToSnooze.get(i));
+ }
+ } else {
+ Log.w(TAG, "Cannot snooze " + r.getKey() + ": too many snoozed notifications");
+ }
}
@@ -9798,7 +9813,7 @@ public class NotificationManagerService extends SystemService {
* notifications visible to the given listener.
*/
@GuardedBy("mNotificationLock")
- private NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
+ NotificationRankingUpdate makeRankingUpdateLocked(ManagedServiceInfo info) {
final int N = mNotificationList.size();
final ArrayList<NotificationListenerService.Ranking> rankings = new ArrayList<>();
@@ -10919,7 +10934,7 @@ public class NotificationManagerService extends SystemService {
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : getServices()) {
- boolean sbnVisible = isVisibleToListener(sbn, r. getNotificationType(), info);
+ boolean sbnVisible = isVisibleToListener(sbn, r.getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
// This notification hasn't been and still isn't visible -> ignore.
@@ -10949,12 +10964,17 @@ public class NotificationManagerService extends SystemService {
info, oldSbnLightClone, update, null, REASON_USER_STOPPED));
continue;
}
-
// Grant access before listener is notified
final int targetUserId = (info.userid == UserHandle.USER_ALL)
? UserHandle.USER_SYSTEM : info.userid;
updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
+ mPackageManagerInternal.grantImplicitAccess(
+ targetUserId, null /* intent */,
+ UserHandle.getAppId(info.uid),
+ sbn.getUid(),
+ false /* direct */, false /* retainOnUpdate */);
+
final StatusBarNotification sbnToPost = trimCache.ForListener(info);
mHandler.post(() -> notifyPosted(info, sbnToPost, update));
}
diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java
index 09ed56745e54..12324bff0c7a 100644
--- a/services/core/java/com/android/server/notification/PermissionHelper.java
+++ b/services/core/java/com/android/server/notification/PermissionHelper.java
@@ -16,6 +16,8 @@
package com.android.server.notification;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -175,12 +177,14 @@ public final class PermissionHelper {
mPermManager.revokeRuntimePermission(packageName, NOTIFICATION_PERMISSION,
userId, TAG);
}
+ int flagMask = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED;
+ flagMask = userSet || !grant ? flagMask | FLAG_PERMISSION_GRANTED_BY_DEFAULT : flagMask;
if (userSet) {
mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, userId);
+ flagMask, FLAG_PERMISSION_USER_SET, true, userId);
} else {
mPermManager.updatePermissionFlags(packageName, NOTIFICATION_PERMISSION,
- 0, FLAG_PERMISSION_USER_SET, true, userId);
+ flagMask, 0, true, userId);
}
} catch (RemoteException e) {
Slog.e(TAG, "Could not reach system server", e);
diff --git a/services/core/java/com/android/server/notification/SnoozeHelper.java b/services/core/java/com/android/server/notification/SnoozeHelper.java
index 7f265df3f416..61936df4a124 100644
--- a/services/core/java/com/android/server/notification/SnoozeHelper.java
+++ b/services/core/java/com/android/server/notification/SnoozeHelper.java
@@ -16,7 +16,6 @@
package com.android.server.notification;
import android.annotation.NonNull;
-import android.annotation.UserIdInt;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
@@ -25,7 +24,6 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.net.Uri;
import android.os.Binder;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.util.ArrayMap;
@@ -38,18 +36,15 @@ import android.util.TypedXmlSerializer;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.logging.nano.MetricsProto;
-import com.android.internal.util.XmlUtils;
import com.android.server.pm.PackageManagerService;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
@@ -59,15 +54,15 @@ import java.util.Set;
/**
* NotificationManagerService helper for handling snoozed notifications.
*/
-public class SnoozeHelper {
+public final class SnoozeHelper {
public static final int XML_SNOOZED_NOTIFICATION_VERSION = 1;
+ static final int CONCURRENT_SNOOZE_LIMIT = 500;
+
protected static final String XML_TAG_NAME = "snoozed-notifications";
private static final String XML_SNOOZED_NOTIFICATION = "notification";
private static final String XML_SNOOZED_NOTIFICATION_CONTEXT = "context";
- private static final String XML_SNOOZED_NOTIFICATION_PKG = "pkg";
- private static final String XML_SNOOZED_NOTIFICATION_USER_ID = "user-id";
private static final String XML_SNOOZED_NOTIFICATION_KEY = "key";
//the time the snoozed notification should be reposted
private static final String XML_SNOOZED_NOTIFICATION_TIME = "time";
@@ -89,24 +84,19 @@ public class SnoozeHelper {
private AlarmManager mAm;
private final ManagedServices.UserProfiles mUserProfiles;
- // User id | package name : notification key : record.
- private ArrayMap<String, ArrayMap<String, NotificationRecord>>
- mSnoozedNotifications = new ArrayMap<>();
- // User id | package name : notification key : time-milliseconds .
+ // notification key : record.
+ private ArrayMap<String, NotificationRecord> mSnoozedNotifications = new ArrayMap<>();
+ // notification key : time-milliseconds .
// This member stores persisted snoozed notification trigger times. it persists through reboots
// It should have the notifications that haven't expired or re-posted yet
- private final ArrayMap<String, ArrayMap<String, Long>>
- mPersistedSnoozedNotifications = new ArrayMap<>();
- // User id | package name : notification key : creation ID .
+ private final ArrayMap<String, Long> mPersistedSnoozedNotifications = new ArrayMap<>();
+ // notification key : creation ID.
// This member stores persisted snoozed notification trigger context for the assistant
// it persists through reboots.
// It should have the notifications that haven't expired or re-posted yet
- private final ArrayMap<String, ArrayMap<String, String>>
+ private final ArrayMap<String, String>
mPersistedSnoozedNotificationsWithContext = new ArrayMap<>();
- // notification key : package.
- private ArrayMap<String, String> mPackages = new ArrayMap<>();
- // key : userId
- private ArrayMap<String, Integer> mUsers = new ArrayMap<>();
+
private Callback mCallback;
private final Object mLock = new Object();
@@ -123,27 +113,20 @@ public class SnoozeHelper {
mUserProfiles = userProfiles;
}
- private String getPkgKey(@UserIdInt int userId, String pkg) {
- return userId + "|" + pkg;
- }
-
- void cleanupPersistedContext(String key){
+ protected boolean canSnooze(int numberToSnooze) {
synchronized (mLock) {
- int userId = mUsers.get(key);
- String pkg = mPackages.get(key);
- removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
+ if ((mSnoozedNotifications.size() + numberToSnooze) > CONCURRENT_SNOOZE_LIMIT) {
+ return false;
+ }
}
+ return true;
}
@NonNull
protected Long getSnoozeTimeForUnpostedNotification(int userId, String pkg, String key) {
Long time = null;
synchronized (mLock) {
- ArrayMap<String, Long> snoozed =
- mPersistedSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (snoozed != null) {
- time = snoozed.get(key);
- }
+ time = mPersistedSnoozedNotifications.get(key);
}
if (time == null) {
time = 0L;
@@ -153,29 +136,26 @@ public class SnoozeHelper {
protected String getSnoozeContextForUnpostedNotification(int userId, String pkg, String key) {
synchronized (mLock) {
- ArrayMap<String, String> snoozed =
- mPersistedSnoozedNotificationsWithContext.get(getPkgKey(userId, pkg));
- if (snoozed != null) {
- return snoozed.get(key);
- }
+ return mPersistedSnoozedNotificationsWithContext.get(key);
}
- return null;
}
protected boolean isSnoozed(int userId, String pkg, String key) {
synchronized (mLock) {
- return mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))
- && mSnoozedNotifications.get(getPkgKey(userId, pkg)).containsKey(key);
+ return mSnoozedNotifications.containsKey(key);
}
}
protected Collection<NotificationRecord> getSnoozed(int userId, String pkg) {
synchronized (mLock) {
- if (mSnoozedNotifications.containsKey(getPkgKey(userId, pkg))) {
- return mSnoozedNotifications.get(getPkgKey(userId, pkg)).values();
+ ArrayList snoozed = new ArrayList();
+ for (NotificationRecord r : mSnoozedNotifications.values()) {
+ if (r.getUserId() == userId && r.getSbn().getPackageName().equals(pkg)) {
+ snoozed.add(r);
+ }
}
+ return snoozed;
}
- return Collections.EMPTY_LIST;
}
@NonNull
@@ -183,15 +163,11 @@ public class SnoozeHelper {
String groupKey, Integer userId) {
ArrayList<NotificationRecord> records = new ArrayList<>();
synchronized (mLock) {
- ArrayMap<String, NotificationRecord> allRecords =
- mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (allRecords != null) {
- for (int i = 0; i < allRecords.size(); i++) {
- NotificationRecord r = allRecords.valueAt(i);
- String currentGroupKey = r.getSbn().getGroup();
- if (Objects.equals(currentGroupKey, groupKey)) {
- records.add(r);
- }
+ for (int i = 0; i < mSnoozedNotifications.size(); i++) {
+ NotificationRecord r = mSnoozedNotifications.valueAt(i);
+ if (r.getSbn().getPackageName().equals(pkg) && r.getUserId() == userId
+ && Objects.equals(r.getSbn().getGroup(), groupKey)) {
+ records.add(r);
}
}
}
@@ -200,31 +176,16 @@ public class SnoozeHelper {
protected NotificationRecord getNotification(String key) {
synchronized (mLock) {
- if (!mUsers.containsKey(key) || !mPackages.containsKey(key)) {
- Slog.w(TAG, "Snoozed data sets no longer agree for " + key);
- return null;
- }
- int userId = mUsers.get(key);
- String pkg = mPackages.get(key);
- ArrayMap<String, NotificationRecord> snoozed =
- mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (snoozed == null) {
- return null;
- }
- return snoozed.get(key);
+ return mSnoozedNotifications.get(key);
}
}
protected @NonNull List<NotificationRecord> getSnoozed() {
synchronized (mLock) {
- // caller filters records based on the current user profiles and listener access, so just
- // return everything
+ // caller filters records based on the current user profiles and listener access,
+ // so just return everything
List<NotificationRecord> snoozed = new ArrayList<>();
- for (String userPkgKey : mSnoozedNotifications.keySet()) {
- ArrayMap<String, NotificationRecord> snoozedRecords =
- mSnoozedNotifications.get(userPkgKey);
- snoozed.addAll(snoozedRecords.values());
- }
+ snoozed.addAll(mSnoozedNotifications.values());
return snoozed;
}
}
@@ -233,15 +194,13 @@ public class SnoozeHelper {
* Snoozes a notification and schedules an alarm to repost at that time.
*/
protected void snooze(NotificationRecord record, long duration) {
- String pkg = record.getSbn().getPackageName();
String key = record.getKey();
- int userId = record.getUser().getIdentifier();
snooze(record);
- scheduleRepost(pkg, key, userId, duration);
+ scheduleRepost(key, duration);
Long activateAt = System.currentTimeMillis() + duration;
synchronized (mLock) {
- storeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications, activateAt);
+ mPersistedSnoozedNotifications.put(key, activateAt);
}
}
@@ -249,66 +208,33 @@ public class SnoozeHelper {
* Records a snoozed notification.
*/
protected void snooze(NotificationRecord record, String contextId) {
- int userId = record.getUser().getIdentifier();
if (contextId != null) {
synchronized (mLock) {
- storeRecordLocked(record.getSbn().getPackageName(), record.getKey(),
- userId, mPersistedSnoozedNotificationsWithContext, contextId);
+ mPersistedSnoozedNotificationsWithContext.put(record.getKey(), contextId);
}
}
snooze(record);
}
private void snooze(NotificationRecord record) {
- int userId = record.getUser().getIdentifier();
if (DEBUG) {
Slog.d(TAG, "Snoozing " + record.getKey());
}
synchronized (mLock) {
- storeRecordLocked(record.getSbn().getPackageName(), record.getKey(),
- userId, mSnoozedNotifications, record);
- }
- }
-
- private <T> void storeRecordLocked(String pkg, String key, Integer userId,
- ArrayMap<String, ArrayMap<String, T>> targets, T object) {
-
- mPackages.put(key, pkg);
- mUsers.put(key, userId);
- ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
- if (keyToValue == null) {
- keyToValue = new ArrayMap<>();
- }
- keyToValue.put(key, object);
- targets.put(getPkgKey(userId, pkg), keyToValue);
- }
-
- private <T> T removeRecordLocked(String pkg, String key, Integer userId,
- ArrayMap<String, ArrayMap<String, T>> targets) {
- T object = null;
- ArrayMap<String, T> keyToValue = targets.get(getPkgKey(userId, pkg));
- if (keyToValue == null) {
- return null;
- }
- object = keyToValue.remove(key);
- if (keyToValue.size() == 0) {
- targets.remove(getPkgKey(userId, pkg));
+ mSnoozedNotifications.put(record.getKey(), record);
}
- return object;
}
protected boolean cancel(int userId, String pkg, String tag, int id) {
synchronized (mLock) {
- ArrayMap<String, NotificationRecord> recordsForPkg =
- mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (recordsForPkg != null) {
- final Set<Map.Entry<String, NotificationRecord>> records = recordsForPkg.entrySet();
- for (Map.Entry<String, NotificationRecord> record : records) {
- final StatusBarNotification sbn = record.getValue().getSbn();
- if (Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
- record.getValue().isCanceled = true;
- return true;
- }
+ final Set<Map.Entry<String, NotificationRecord>> records =
+ mSnoozedNotifications.entrySet();
+ for (Map.Entry<String, NotificationRecord> record : records) {
+ final StatusBarNotification sbn = record.getValue().getSbn();
+ if (sbn.getPackageName().equals(pkg) && sbn.getUserId() == userId
+ && Objects.equals(sbn.getTag(), tag) && sbn.getId() == id) {
+ record.getValue().isCanceled = true;
+ return true;
}
}
}
@@ -325,11 +251,9 @@ public class SnoozeHelper {
if (includeCurrentProfiles) {
userIds = mUserProfiles.getCurrentProfileIds();
}
- for (ArrayMap<String, NotificationRecord> snoozedRecords : mSnoozedNotifications.values()) {
- for (NotificationRecord r : snoozedRecords.values()) {
- if (userIds.binarySearch(r.getUserId()) >= 0) {
- r.isCanceled = true;
- }
+ for (NotificationRecord r : mSnoozedNotifications.values()) {
+ if (userIds.binarySearch(r.getUserId()) >= 0) {
+ r.isCanceled = true;
}
}
}
@@ -337,14 +261,12 @@ public class SnoozeHelper {
protected boolean cancel(int userId, String pkg) {
synchronized (mLock) {
- ArrayMap<String, NotificationRecord> records =
- mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (records == null) {
- return false;
- }
- int N = records.size();
- for (int i = 0; i < N; i++) {
- records.valueAt(i).isCanceled = true;
+ int n = mSnoozedNotifications.size();
+ for (int i = 0; i < n; i++) {
+ final NotificationRecord r = mSnoozedNotifications.valueAt(i);
+ if (r.getSbn().getPackageName().equals(pkg) && r.getUserId() == userId) {
+ r.isCanceled = true;
+ }
}
return true;
}
@@ -355,20 +277,17 @@ public class SnoozeHelper {
*/
protected void update(int userId, NotificationRecord record) {
synchronized (mLock) {
- ArrayMap<String, NotificationRecord> records =
- mSnoozedNotifications.get(getPkgKey(userId, record.getSbn().getPackageName()));
- if (records == null) {
- return;
+ if (mSnoozedNotifications.containsKey(record.getKey())) {
+ mSnoozedNotifications.put(record.getKey(), record);
}
- records.put(record.getKey(), record);
}
}
protected void repost(String key, boolean muteOnReturn) {
synchronized (mLock) {
- Integer userId = mUsers.get(key);
- if (userId != null) {
- repost(key, userId, muteOnReturn);
+ final NotificationRecord r = mSnoozedNotifications.get(key);
+ if (r != null) {
+ repost(key, r.getUserId(), muteOnReturn);
}
}
}
@@ -376,43 +295,30 @@ public class SnoozeHelper {
protected void repost(String key, int userId, boolean muteOnReturn) {
NotificationRecord record;
synchronized (mLock) {
- final String pkg = mPackages.remove(key);
- mUsers.remove(key);
- removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotifications);
- removeRecordLocked(pkg, key, userId, mPersistedSnoozedNotificationsWithContext);
- ArrayMap<String, NotificationRecord> records =
- mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (records == null) {
- return;
- }
- record = records.remove(key);
-
+ mPersistedSnoozedNotifications.remove(key);
+ mPersistedSnoozedNotificationsWithContext.remove(key);
+ record = mSnoozedNotifications.remove(key);
}
if (record != null && !record.isCanceled) {
- final PendingIntent pi = createPendingIntent(
- record.getSbn().getPackageName(), record.getKey(), userId);
+ final PendingIntent pi = createPendingIntent(record.getKey());
mAm.cancel(pi);
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN));
- mCallback.repost(userId, record, muteOnReturn);
+ mCallback.repost(record.getUserId(), record, muteOnReturn);
}
}
protected void repostGroupSummary(String pkg, int userId, String groupKey) {
synchronized (mLock) {
- ArrayMap<String, NotificationRecord> recordsByKey
- = mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (recordsByKey == null) {
- return;
- }
-
String groupSummaryKey = null;
- int N = recordsByKey.size();
- for (int i = 0; i < N; i++) {
- final NotificationRecord potentialGroupSummary = recordsByKey.valueAt(i);
- if (potentialGroupSummary.getSbn().isGroup()
+ int n = mSnoozedNotifications.size();
+ for (int i = 0; i < n; i++) {
+ final NotificationRecord potentialGroupSummary = mSnoozedNotifications.valueAt(i);
+ if (potentialGroupSummary.getSbn().getPackageName().equals(pkg)
+ && potentialGroupSummary.getUserId() == userId
+ && potentialGroupSummary.getSbn().isGroup()
&& potentialGroupSummary.getNotification().isGroupSummary()
&& groupKey.equals(potentialGroupSummary.getGroupKey())) {
groupSummaryKey = potentialGroupSummary.getKey();
@@ -421,16 +327,14 @@ public class SnoozeHelper {
}
if (groupSummaryKey != null) {
- NotificationRecord record = recordsByKey.remove(groupSummaryKey);
- mPackages.remove(groupSummaryKey);
- mUsers.remove(groupSummaryKey);
+ NotificationRecord record = mSnoozedNotifications.remove(groupSummaryKey);
if (record != null && !record.isCanceled) {
Runnable runnable = () -> {
MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_OPEN));
- mCallback.repost(userId, record, false);
+ mCallback.repost(record.getUserId(), record, false);
};
runnable.run();
}
@@ -440,20 +344,40 @@ public class SnoozeHelper {
protected void clearData(int userId, String pkg) {
synchronized (mLock) {
- ArrayMap<String, NotificationRecord> records =
- mSnoozedNotifications.get(getPkgKey(userId, pkg));
- if (records == null) {
- return;
+ int n = mSnoozedNotifications.size();
+ for (int i = n - 1; i >= 0; i--) {
+ final NotificationRecord record = mSnoozedNotifications.valueAt(i);
+ if (record.getUserId() == userId && record.getSbn().getPackageName().equals(pkg)) {
+ mSnoozedNotifications.removeAt(i);
+ mPersistedSnoozedNotificationsWithContext.remove(record.getKey());
+ mPersistedSnoozedNotifications.remove(record.getKey());
+ Runnable runnable = () -> {
+ final PendingIntent pi = createPendingIntent(record.getKey());
+ mAm.cancel(pi);
+ MetricsLogger.action(record.getLogMaker()
+ .setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
+ .setType(MetricsProto.MetricsEvent.TYPE_DISMISS));
+ };
+ runnable.run();
+ }
}
- for (int i = records.size() - 1; i >= 0; i--) {
- final NotificationRecord r = records.removeAt(i);
- if (r != null) {
- mPackages.remove(r.getKey());
- mUsers.remove(r.getKey());
+ }
+ }
+
+ protected void clearData(int userId) {
+ synchronized (mLock) {
+ int n = mSnoozedNotifications.size();
+ for (int i = n - 1; i >= 0; i--) {
+ final NotificationRecord record = mSnoozedNotifications.valueAt(i);
+ if (record.getUserId() == userId) {
+ mSnoozedNotifications.removeAt(i);
+ mPersistedSnoozedNotificationsWithContext.remove(record.getKey());
+ mPersistedSnoozedNotifications.remove(record.getKey());
+
Runnable runnable = () -> {
- final PendingIntent pi = createPendingIntent(pkg, r.getKey(), userId);
+ final PendingIntent pi = createPendingIntent(record.getKey());
mAm.cancel(pi);
- MetricsLogger.action(r.getLogMaker()
+ MetricsLogger.action(record.getLogMaker()
.setCategory(MetricsProto.MetricsEvent.NOTIFICATION_SNOOZED)
.setType(MetricsProto.MetricsEvent.TYPE_DISMISS));
};
@@ -463,47 +387,38 @@ public class SnoozeHelper {
}
}
- private PendingIntent createPendingIntent(String pkg, String key, int userId) {
+ private PendingIntent createPendingIntent(String key) {
return PendingIntent.getBroadcast(mContext,
REQUEST_CODE_REPOST,
new Intent(REPOST_ACTION)
.setPackage(PackageManagerService.PLATFORM_PACKAGE_NAME)
.setData(new Uri.Builder().scheme(REPOST_SCHEME).appendPath(key).build())
.addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
- .putExtra(EXTRA_KEY, key)
- .putExtra(EXTRA_USER_ID, userId),
+ .putExtra(EXTRA_KEY, key),
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
}
public void scheduleRepostsForPersistedNotifications(long currentTime) {
synchronized (mLock) {
- for (ArrayMap<String, Long> snoozed : mPersistedSnoozedNotifications.values()) {
- for (int i = 0; i < snoozed.size(); i++) {
- String key = snoozed.keyAt(i);
- Long time = snoozed.valueAt(i);
- String pkg = mPackages.get(key);
- Integer userId = mUsers.get(key);
- if (time == null || pkg == null || userId == null) {
- Slog.w(TAG, "data out of sync: " + time + "|" + pkg + "|" + userId);
- continue;
- }
- if (time != null && time > currentTime) {
- scheduleRepostAtTime(pkg, key, userId, time);
- }
+ for (int i = 0; i < mPersistedSnoozedNotifications.size(); i++) {
+ String key = mPersistedSnoozedNotifications.keyAt(i);
+ Long time = mPersistedSnoozedNotifications.valueAt(i);
+ if (time != null && time > currentTime) {
+ scheduleRepostAtTime(key, time);
}
}
}
}
- private void scheduleRepost(String pkg, String key, int userId, long duration) {
- scheduleRepostAtTime(pkg, key, userId, System.currentTimeMillis() + duration);
+ private void scheduleRepost(String key, long duration) {
+ scheduleRepostAtTime(key, System.currentTimeMillis() + duration);
}
- private void scheduleRepostAtTime(String pkg, String key, int userId, long time) {
+ private void scheduleRepostAtTime(String key, long time) {
Runnable runnable = () -> {
final long identity = Binder.clearCallingIdentity();
try {
- final PendingIntent pi = createPendingIntent(pkg, key, userId);
+ final PendingIntent pi = createPendingIntent(key);
mAm.cancel(pi);
if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + new Date(time));
mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi);
@@ -517,37 +432,14 @@ public class SnoozeHelper {
public void dump(PrintWriter pw, NotificationManagerService.DumpFilter filter) {
synchronized (mLock) {
pw.println("\n Snoozed notifications:");
- for (String userPkgKey : mSnoozedNotifications.keySet()) {
+ for (String key : mSnoozedNotifications.keySet()) {
pw.print(INDENT);
- pw.println("key: " + userPkgKey);
- ArrayMap<String, NotificationRecord> snoozedRecords =
- mSnoozedNotifications.get(userPkgKey);
- Set<String> snoozedKeys = snoozedRecords.keySet();
- for (String key : snoozedKeys) {
- pw.print(INDENT);
- pw.print(INDENT);
- pw.print(INDENT);
- pw.println(key);
- }
+ pw.println("key: " + key);
}
pw.println("\n Pending snoozed notifications");
- for (String userPkgKey : mPersistedSnoozedNotifications.keySet()) {
+ for (String key : mPersistedSnoozedNotifications.keySet()) {
pw.print(INDENT);
- pw.println("key: " + userPkgKey);
- ArrayMap<String, Long> snoozedRecords =
- mPersistedSnoozedNotifications.get(userPkgKey);
- if (snoozedRecords == null) {
- continue;
- }
- Set<String> snoozedKeys = snoozedRecords.keySet();
- for (String key : snoozedKeys) {
- pw.print(INDENT);
- pw.print(INDENT);
- pw.print(INDENT);
- pw.print(key);
- pw.print(INDENT);
- pw.println(snoozedRecords.get(key));
- }
+ pw.println("key: " + key + " until: " + mPersistedSnoozedNotifications.get(key));
}
}
}
@@ -578,37 +470,22 @@ public class SnoozeHelper {
void insert(T t) throws IOException;
}
- private <T> void writeXml(TypedXmlSerializer out,
- ArrayMap<String, ArrayMap<String, T>> targets, String tag,
- Inserter<T> attributeInserter)
- throws IOException {
- final int M = targets.size();
- for (int i = 0; i < M; i++) {
+ private <T> void writeXml(TypedXmlSerializer out, ArrayMap<String, T> targets, String tag,
+ Inserter<T> attributeInserter) throws IOException {
+ for (int j = 0; j < targets.size(); j++) {
+ String key = targets.keyAt(j);
// T is a String (snoozed until context) or Long (snoozed until time)
- ArrayMap<String, T> keyToValue = targets.valueAt(i);
- for (int j = 0; j < keyToValue.size(); j++) {
- String key = keyToValue.keyAt(j);
- T value = keyToValue.valueAt(j);
- String pkg = mPackages.get(key);
- Integer userId = mUsers.get(key);
-
- if (pkg == null || userId == null) {
- Slog.w(TAG, "pkg " + pkg + " or user " + userId + " missing for " + key);
- continue;
- }
+ T value = targets.valueAt(j);
- out.startTag(null, tag);
+ out.startTag(null, tag);
- attributeInserter.insert(value);
+ attributeInserter.insert(value);
- out.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
- XML_SNOOZED_NOTIFICATION_VERSION);
- out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
- out.attribute(null, XML_SNOOZED_NOTIFICATION_PKG, pkg);
- out.attributeInt(null, XML_SNOOZED_NOTIFICATION_USER_ID, userId);
+ out.attributeInt(null, XML_SNOOZED_NOTIFICATION_VERSION_LABEL,
+ XML_SNOOZED_NOTIFICATION_VERSION);
+ out.attribute(null, XML_SNOOZED_NOTIFICATION_KEY, key);
- out.endTag(null, tag);
- }
+ out.endTag(null, tag);
}
}
@@ -628,16 +505,12 @@ public class SnoozeHelper {
== XML_SNOOZED_NOTIFICATION_VERSION) {
try {
final String key = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_KEY);
- final String pkg = parser.getAttributeValue(null, XML_SNOOZED_NOTIFICATION_PKG);
- final int userId = parser.getAttributeInt(
- null, XML_SNOOZED_NOTIFICATION_USER_ID, UserHandle.USER_ALL);
if (tag.equals(XML_SNOOZED_NOTIFICATION)) {
final Long time = parser.getAttributeLong(
null, XML_SNOOZED_NOTIFICATION_TIME, 0);
if (time > currentTime) { //only read new stuff
synchronized (mLock) {
- storeRecordLocked(
- pkg, key, userId, mPersistedSnoozedNotifications, time);
+ mPersistedSnoozedNotifications.put(key, time);
}
}
}
@@ -645,9 +518,7 @@ public class SnoozeHelper {
final String creationId = parser.getAttributeValue(
null, XML_SNOOZED_NOTIFICATION_CONTEXT_ID);
synchronized (mLock) {
- storeRecordLocked(
- pkg, key, userId, mPersistedSnoozedNotificationsWithContext,
- creationId);
+ mPersistedSnoozedNotificationsWithContext.put(key, creationId);
}
}
} catch (Exception e) {
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 9e0c97502c4f..6135fe8acbed 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -121,6 +121,7 @@ public class ZenModeHelper {
protected final RingerModeDelegate mRingerModeDelegate = new
RingerModeDelegate();
@VisibleForTesting protected final ZenModeConditions mConditions;
+ Object mConfigsLock = new Object();
@VisibleForTesting final SparseArray<ZenModeConfig> mConfigs = new SparseArray<>();
private final Metrics mMetrics = new Metrics();
private final ConditionProviders.Config mServiceConfig;
@@ -153,7 +154,9 @@ public class ZenModeHelper {
mDefaultConfig = readDefaultConfig(mContext.getResources());
updateDefaultAutomaticRuleNames();
mConfig = mDefaultConfig.copy();
- mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
+ synchronized (mConfigsLock) {
+ mConfigs.put(UserHandle.USER_SYSTEM, mConfig);
+ }
mConsolidatedPolicy = mConfig.toNotificationPolicy();
mSettingsObserver = new SettingsObserver(mHandler);
@@ -233,7 +236,9 @@ public class ZenModeHelper {
public void onUserRemoved(int user) {
if (user < UserHandle.USER_SYSTEM) return;
if (DEBUG) Log.d(TAG, "onUserRemoved u=" + user);
- mConfigs.remove(user);
+ synchronized (mConfigsLock) {
+ mConfigs.remove(user);
+ }
}
public void onUserUnlocked(int user) {
@@ -248,7 +253,12 @@ public class ZenModeHelper {
if (mUser == user || user < UserHandle.USER_SYSTEM) return;
mUser = user;
if (DEBUG) Log.d(TAG, reason + " u=" + user);
- ZenModeConfig config = mConfigs.get(user);
+ ZenModeConfig config = null;
+ synchronized (mConfigsLock) {
+ if (mConfigs.get(user) != null) {
+ config = mConfigs.get(user).copy();
+ }
+ }
if (config == null) {
if (DEBUG) Log.d(TAG, reason + " generating default config for user " + user);
config = mDefaultConfig.copy();
@@ -330,7 +340,8 @@ public class ZenModeHelper {
int newRuleInstanceCount = getCurrentInstanceCount(automaticZenRule.getOwner())
+ getCurrentInstanceCount(automaticZenRule.getConfigurationActivity())
+ 1;
- if (newRuleInstanceCount > RULE_LIMIT_PER_PACKAGE
+ int newPackageRuleCount = getPackageRuleCount(pkg) + 1;
+ if (newPackageRuleCount > RULE_LIMIT_PER_PACKAGE
|| (ruleInstanceLimit > 0 && ruleInstanceLimit < newRuleInstanceCount)) {
throw new IllegalArgumentException("Rule instance limit exceeded");
}
@@ -511,6 +522,23 @@ public class ZenModeHelper {
return count;
}
+ // Equivalent method to getCurrentInstanceCount, but for all rules associated with a specific
+ // package rather than a condition provider service or activity.
+ private int getPackageRuleCount(String pkg) {
+ if (pkg == null) {
+ return 0;
+ }
+ int count = 0;
+ synchronized (mConfig) {
+ for (ZenRule rule : mConfig.automaticRules.values()) {
+ if (pkg.equals(rule.getPkg())) {
+ count++;
+ }
+ }
+ }
+ return count;
+ }
+
public boolean canManageAutomaticZenRule(ZenRule rule) {
final int callingUid = Binder.getCallingUid();
if (callingUid == 0 || callingUid == Process.SYSTEM_UID) {
@@ -685,9 +713,11 @@ public class ZenModeHelper {
pw.println(Global.zenModeToString(mZenMode));
pw.print(prefix);
pw.println("mConsolidatedPolicy=" + mConsolidatedPolicy.toString());
- final int N = mConfigs.size();
- for (int i = 0; i < N; i++) {
- dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
+ synchronized(mConfigsLock) {
+ final int N = mConfigs.size();
+ for (int i = 0; i < N; i++) {
+ dump(pw, prefix, "mConfigs[u=" + mConfigs.keyAt(i) + "]", mConfigs.valueAt(i));
+ }
}
pw.print(prefix); pw.print("mUser="); pw.println(mUser);
synchronized (mConfig) {
@@ -787,7 +817,7 @@ public class ZenModeHelper {
public void writeXml(TypedXmlSerializer out, boolean forBackup, Integer version, int userId)
throws IOException {
- synchronized (mConfigs) {
+ synchronized (mConfigsLock) {
final int n = mConfigs.size();
for (int i = 0; i < n; i++) {
if (forBackup && mConfigs.keyAt(i) != userId) {
@@ -883,14 +913,18 @@ public class ZenModeHelper {
}
if (config.user != mUser) {
// simply store away for background users
- mConfigs.put(config.user, config);
+ synchronized (mConfigsLock) {
+ mConfigs.put(config.user, config);
+ }
if (DEBUG) Log.d(TAG, "setConfigLocked: store config for user " + config.user);
return true;
}
// handle CPS backed conditions - danger! may modify config
mConditions.evaluateConfig(config, null, false /*processSubscriptions*/);
- mConfigs.put(config.user, config);
+ synchronized (mConfigsLock) {
+ mConfigs.put(config.user, config);
+ }
if (DEBUG) Log.d(TAG, "setConfigLocked reason=" + reason, new Throwable());
ZenLog.traceConfig(reason, mConfig, config);
@@ -1211,7 +1245,7 @@ public class ZenModeHelper {
* Generate pulled atoms about do not disturb configurations.
*/
public void pullRules(List<StatsEvent> events) {
- synchronized (mConfig) {
+ synchronized (mConfigsLock) {
final int numConfigs = mConfigs.size();
for (int i = 0; i < numConfigs; i++) {
final int user = mConfigs.keyAt(i);
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index f6b22bc38939..a03a16ad7bc3 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -113,7 +113,7 @@ final class InitAppsHelper {
mScanFlags = scanFlags;
}
mSystemParseFlags = mPm.getDefParseFlags() | ParsingPackageUtils.PARSE_IS_SYSTEM_DIR;
- mSystemScanFlags = scanFlags | SCAN_AS_SYSTEM;
+ mSystemScanFlags = mScanFlags | SCAN_AS_SYSTEM;
mExecutorService = ParallelPackageParser.makeExecutorService();
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 94e8ec5c434d..85b01491a8b7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -1836,8 +1836,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService
mAppDataHelper = new AppDataHelper(this);
mInstallPackageHelper = new InstallPackageHelper(this, mAppDataHelper);
mRemovePackageHelper = new RemovePackageHelper(this, mAppDataHelper);
- mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
- mInjector.getSystemPartitions());
mDeletePackageHelper = new DeletePackageHelper(this, mRemovePackageHelper,
mAppDataHelper);
mSharedLibraries.setDeletePackageHelper(mDeletePackageHelper);
@@ -1958,6 +1956,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService
+ ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
}
+ mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
+ mInjector.getSystemPartitions());
+
// when upgrading from pre-M, promote system app permissions from install to runtime
mPromoteSystemApps =
mIsUpgrade && ver.sdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1;
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 0dc188b75d5e..46f0dbcdfe10 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -427,8 +427,7 @@ final class ScanPackageUtils {
pkgSetting.setLastModifiedTime(scanFileTime);
// TODO(b/135203078): Remove, move to constructor
pkgSetting.setPkg(parsedPackage)
- .setFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting))
- .setPrivateFlags(
+ .setPkgFlags(PackageInfoUtils.appInfoFlags(parsedPackage, pkgSetting),
PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting));
if (parsedPackage.getLongVersionCode() != pkgSetting.getVersionCode()) {
pkgSetting.setLongVersionCode(parsedPackage.getLongVersionCode());
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index b952f80850bb..61a251e19db7 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -146,6 +146,17 @@ public abstract class SettingBase implements Watchable, Snappable {
return this;
}
+ /**
+ * Unconditionally set both mPkgFlags and mPkgPrivateFlags.
+ * Should not be used outside pkgSetting initialization or update.
+ */
+ SettingBase setPkgFlags(int flags, int privateFlags) {
+ this.mPkgFlags = flags;
+ this.mPkgPrivateFlags = privateFlags;
+ onChanged();
+ return this;
+ }
+
public int getFlags() {
return mPkgFlags;
}
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 6400502f1a89..7437b145189f 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -987,8 +987,7 @@ public final class Settings implements Watchable, Snappable {
// Update new package state.
.setLastModifiedTime(codePath.lastModified())
.setDomainSetId(domainSetId);
- pkgSetting.setFlags(pkgFlags)
- .setPrivateFlags(pkgPrivateFlags);
+ pkgSetting.setPkgFlags(pkgFlags, pkgPrivateFlags);
} else {
pkgSetting = new PackageSetting(pkgName, realPkgName, codePath,
legacyNativeLibraryPath, primaryCpuAbi, secondaryCpuAbi,
@@ -1175,15 +1174,15 @@ public final class Settings implements Watchable, Snappable {
.setUsesStaticLibrariesVersions(null);
}
- // These two flags are preserved from the existing PackageSetting. Copied from prior code,
- // unclear if this is actually necessary.
- boolean wasExternalStorage = (pkgSetting.getFlags()
- & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0;
- if (wasExternalStorage) {
- pkgFlags |= ApplicationInfo.FLAG_EXTERNAL_STORAGE;
- } else {
- pkgFlags &= ~ApplicationInfo.FLAG_EXTERNAL_STORAGE;
- }
+ // If what we are scanning is a system (and possibly privileged) package,
+ // then make it so, regardless of whether it was previously installed only
+ // in the data partition. Reset first.
+ int newPkgFlags = pkgSetting.getFlags();
+ newPkgFlags &= ~ApplicationInfo.FLAG_SYSTEM;
+ newPkgFlags |= pkgFlags & ApplicationInfo.FLAG_SYSTEM;
+ // Only set pkgFlags.
+ pkgSetting.setPkgFlags(newPkgFlags, pkgSetting.getPrivateFlags());
+
boolean wasRequiredForSystemUser = (pkgSetting.getPrivateFlags()
& ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER) != 0;
if (wasRequiredForSystemUser) {
@@ -1191,9 +1190,7 @@ public final class Settings implements Watchable, Snappable {
} else {
pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_REQUIRED_FOR_SYSTEM_USER;
}
-
- pkgSetting.setFlags(pkgFlags)
- .setPrivateFlags(pkgPrivateFlags);
+ pkgSetting.setPrivateFlags(pkgPrivateFlags);
}
/**
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index c0c234953297..c6a7dd7499f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -144,8 +144,8 @@ class ShortcutLauncher extends ShortcutPackageItem {
final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
// Actually pin shortcuts.
- // This logic here is to make sure a launcher cannot pin a shortcut that is floating
- // (i.e. not dynamic nor manifest but is pinned) and pinned by another launcher.
+ // This logic here is to make sure a launcher cannot pin a shortcut that is not dynamic
+ // nor long-lived nor manifest but is pinned.
// In this case, technically the shortcut doesn't exist to this launcher, so it can't
// pin it.
// (Maybe unnecessarily strict...)
@@ -158,7 +158,7 @@ class ShortcutLauncher extends ShortcutPackageItem {
if (si == null) {
continue;
}
- if (si.isDynamic()
+ if (si.isDynamic() || si.isLongLived()
|| si.isManifestShortcut()
|| (prevSet != null && prevSet.contains(id))
|| forPinRequest) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 1ea27184abcc..1d3d81788cfe 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -1881,6 +1881,44 @@ public class UserManagerService extends IUserManager.Stub {
}
@Override
+ public boolean setUserEphemeral(@UserIdInt int userId, boolean enableEphemeral) {
+ checkCreateUsersPermission("update ephemeral user flag");
+ UserData userToUpdate = null;
+ synchronized (mPackagesLock) {
+ synchronized (mUsersLock) {
+ final UserData userData = mUsers.get(userId);
+ if (userData == null) {
+ Slog.e(LOG_TAG, "User not found for setting ephemeral mode: u" + userId);
+ return false;
+ }
+ boolean isEphemeralUser = (userData.info.flags & UserInfo.FLAG_EPHEMERAL) != 0;
+ boolean isEphemeralOnCreateUser =
+ (userData.info.flags & UserInfo.FLAG_EPHEMERAL_ON_CREATE) != 0;
+ // when user is created in ephemeral mode via FLAG_EPHEMERAL
+ // its state cannot be changed to non ephemeral.
+ // FLAG_EPHEMERAL_ON_CREATE is used to keep track of this state
+ if (isEphemeralOnCreateUser && !enableEphemeral) {
+ Slog.e(LOG_TAG, "Failed to change user state to non-ephemeral for user "
+ + userId);
+ return false;
+ }
+ if (isEphemeralUser != enableEphemeral) {
+ if (enableEphemeral) {
+ userData.info.flags |= UserInfo.FLAG_EPHEMERAL;
+ } else {
+ userData.info.flags &= ~UserInfo.FLAG_EPHEMERAL;
+ }
+ userToUpdate = userData;
+ }
+ }
+ if (userToUpdate != null) {
+ writeUserLP(userToUpdate);
+ }
+ }
+ return true;
+ }
+
+ @Override
public void setUserIcon(@UserIdInt int userId, Bitmap bitmap) {
try {
checkManageUsersPermission("update users");
@@ -3987,6 +4025,10 @@ public class UserManagerService extends IUserManager.Stub {
flags &= ~UserInfo.FLAG_EPHEMERAL;
}
+ if ((flags & UserInfo.FLAG_EPHEMERAL) != 0) {
+ flags |= UserInfo.FLAG_EPHEMERAL_ON_CREATE;
+ }
+
userInfo = new UserInfo(userId, name, null, flags, userType);
userInfo.serialNumber = mNextSerialNumber++;
userInfo.creationTime = getCreationTime();
diff --git a/services/core/java/com/android/server/policy/AppOpsPolicy.java b/services/core/java/com/android/server/policy/AppOpsPolicy.java
index a6d148c824c9..ebd9126d1439 100644
--- a/services/core/java/com/android/server/policy/AppOpsPolicy.java
+++ b/services/core/java/com/android/server/policy/AppOpsPolicy.java
@@ -202,9 +202,8 @@ public final class AppOpsPolicy implements AppOpsManagerInternal.CheckOpsDelegat
}
private static boolean isHotwordDetectionServiceRequired(PackageManager pm) {
- // The HotwordDetectionService APIs aren't ready yet for Auto or TV.
- return !(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
- || pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+ // Usage of the HotwordDetectionService won't be enforced until a later release.
+ return false;
}
@Override
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index d8e7fbe8b296..d645bb211bc3 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -169,7 +169,6 @@ import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.IDisplayFoldListener;
-import android.view.IWindowManager;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyCharacterMap.FallbackAction;
@@ -381,7 +380,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
private final SparseArray<ScreenOnListener> mScreenOnListeners = new SparseArray<>();
Context mContext;
- IWindowManager mWindowManager;
WindowManagerFuncs mWindowManagerFuncs;
WindowManagerInternal mWindowManagerInternal;
PowerManager mPowerManager;
@@ -951,6 +949,11 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
private void powerPress(long eventTime, int count, boolean beganFromNonInteractive) {
+ // SideFPS still needs to know about suppressed power buttons, in case it needs to block
+ // an auth attempt.
+ if (count == 1) {
+ mSideFpsEventHandler.notifyPowerPressed();
+ }
if (mDefaultDisplayPolicy.isScreenOnEarly() && !mDefaultDisplayPolicy.isScreenOnFully()) {
Slog.i(TAG, "Suppressed redundant power key press while "
+ "already in the process of turning the screen on.");
@@ -970,7 +973,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
} else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
Slog.d(TAG, "No behavior defined for power press count " + count);
} else if (count == 1 && interactive && !beganFromNonInteractive) {
- if (mSideFpsEventHandler.onSinglePressDetected(eventTime)) {
+ if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
Slog.i(TAG, "Suppressing power key because the user is interacting with the "
+ "fingerprint sensor");
return;
@@ -1870,10 +1873,8 @@ public class PhoneWindowManager implements WindowManagerPolicy {
/** {@inheritDoc} */
@Override
- public void init(Context context, IWindowManager windowManager,
- WindowManagerFuncs windowManagerFuncs) {
+ public void init(Context context, WindowManagerFuncs windowManagerFuncs) {
mContext = context;
- mWindowManager = windowManager;
mWindowManagerFuncs = windowManagerFuncs;
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class);
@@ -4534,7 +4535,6 @@ public class PhoneWindowManager implements WindowManagerPolicy {
pmWakeReason)) + ")");
}
- mActivityTaskManagerInternal.notifyWakingUp();
mDefaultDisplayPolicy.setAwake(true);
// Since goToSleep performs these functions synchronously, we must
@@ -4802,10 +4802,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
if (enableScreen) {
- try {
- mWindowManager.enableScreenIfNeeded();
- } catch (RemoteException unhandled) {
- }
+ mWindowManagerFuncs.enableScreenIfNeeded();
}
}
@@ -5262,12 +5259,7 @@ public class PhoneWindowManager implements WindowManagerPolicy {
}
void updateRotation(boolean alwaysSendConfiguration) {
- try {
- // Set orientation on WindowManager.
- mWindowManager.updateRotation(alwaysSendConfiguration, false /* forceRelayout */);
- } catch (RemoteException e) {
- // Ignore
- }
+ mWindowManagerFuncs.updateRotation(alwaysSendConfiguration, false /* forceRelayout */);
}
/**
diff --git a/services/core/java/com/android/server/policy/SideFpsEventHandler.java b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
index 41d02727d9eb..af2b902c97e8 100644
--- a/services/core/java/com/android/server/policy/SideFpsEventHandler.java
+++ b/services/core/java/com/android/server/policy/SideFpsEventHandler.java
@@ -19,6 +19,7 @@ package com.android.server.policy;
import static android.hardware.biometrics.BiometricStateListener.STATE_BP_AUTH;
import static android.hardware.biometrics.BiometricStateListener.STATE_ENROLLING;
import static android.hardware.biometrics.BiometricStateListener.STATE_IDLE;
+import static android.hardware.biometrics.BiometricStateListener.STATE_KEYGUARD_AUTH;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -34,8 +35,12 @@ import android.hardware.biometrics.BiometricStateListener;
import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
+import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.util.Log;
import android.view.WindowManager;
import com.android.internal.R;
@@ -46,14 +51,26 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
/**
- * Defines behavior for handling interactions between power button events and
- * fingerprint-related operations, for devices where the fingerprint sensor (side fps)
- * lives on the power button.
+ * Defines behavior for handling interactions between power button events and fingerprint-related
+ * operations, for devices where the fingerprint sensor (side fps) lives on the power button.
*/
public class SideFpsEventHandler {
private static final int DEBOUNCE_DELAY_MILLIS = 500;
+ private int getTapWaitForPowerDuration(Context context) {
+ int tap = context.getResources().getInteger(
+ R.integer.config_sidefpsEnrollPowerPressWindow);
+ if (Build.isDebuggable()) {
+ tap = Settings.Secure.getIntForUser(context.getContentResolver(),
+ Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW, tap,
+ UserHandle.USER_CURRENT);
+ }
+ return tap;
+ }
+
+ private static final String TAG = "SideFpsEventHandler";
+
@NonNull private final Context mContext;
@NonNull private final Handler mHandler;
@NonNull private final PowerManager mPowerManager;
@@ -61,20 +78,26 @@ public class SideFpsEventHandler {
@NonNull private final AtomicBoolean mSideFpsEventHandlerReady;
@Nullable private Dialog mDialog;
- @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener = (dialog) -> {
- if (mDialog == dialog) {
- mDialog = null;
- }
- };
+ private final Runnable mTurnOffDialog =
+ () -> {
+ dismissDialog("mTurnOffDialog");
+ };
+
+ @NonNull private final DialogInterface.OnDismissListener mDialogDismissListener;
private @BiometricStateListener.State int mBiometricState;
+ private final int mTapWaitForPowerDuration;
+ private FingerprintManager mFingerprintManager;
SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager) {
this(context, handler, powerManager, () -> new AlertDialog.Builder(context));
}
@VisibleForTesting
- SideFpsEventHandler(Context context, Handler handler, PowerManager powerManager,
+ SideFpsEventHandler(
+ Context context,
+ Handler handler,
+ PowerManager powerManager,
Supplier<AlertDialog.Builder> dialogSupplier) {
mContext = context;
mHandler = handler;
@@ -82,91 +105,131 @@ public class SideFpsEventHandler {
mDialogSupplier = dialogSupplier;
mBiometricState = STATE_IDLE;
mSideFpsEventHandlerReady = new AtomicBoolean(false);
+ mDialogDismissListener =
+ (dialog) -> {
+ if (mDialog == dialog) {
+ if (mHandler != null) {
+ mHandler.removeCallbacks(mTurnOffDialog);
+ }
+ mDialog = null;
+ }
+ };
// ensure dialog is dismissed if screen goes off for unrelated reasons
- context.registerReceiver(new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (mDialog != null) {
- mDialog.dismiss();
- mDialog = null;
- }
- }
- }, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ context.registerReceiver(
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (mDialog != null) {
+ mDialog.dismiss();
+ mDialog = null;
+ }
+ }
+ },
+ new IntentFilter(Intent.ACTION_SCREEN_OFF));
+ mTapWaitForPowerDuration = getTapWaitForPowerDuration(context);
}
/**
- * Called from {@link PhoneWindowManager} after the power button is pressed and displays a
- * dialog confirming the user's intent to turn screen off if a fingerprint operation is
- * active. The device goes to sleep if confirmed otherwise the dialog is dismissed.
+ * Called from {@link PhoneWindowManager} to notify FingerprintManager that a single tap power
+ * button has been pressed.
+ */
+ public void notifyPowerPressed() {
+ Log.i(TAG, "notifyPowerPressed");
+ if (mFingerprintManager == null) {
+ mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+ }
+ if (mFingerprintManager == null) {
+ return;
+ }
+ mFingerprintManager.onPowerPressed();
+ }
+
+ /**
+ * Called from {@link PhoneWindowManager} and will dictate if the SideFpsEventHandler should
+ * handle the power press.
*
* @param eventTime powerPress event time
* @return true if powerPress was consumed, false otherwise
*/
- public boolean onSinglePressDetected(long eventTime) {
+ public boolean shouldConsumeSinglePress(long eventTime) {
if (!mSideFpsEventHandlerReady.get()) {
return false;
}
switch (mBiometricState) {
case STATE_ENROLLING:
+ mHandler.post(
+ () -> {
+ if (mHandler.hasCallbacks(mTurnOffDialog)) {
+ Log.v(TAG, "Detected a tap to turn off dialog, ignoring");
+ mHandler.removeCallbacks(mTurnOffDialog);
+ }
+ });
+ showDialog(eventTime, "Enroll Power Press");
+ return true;
case STATE_BP_AUTH:
- mHandler.post(() -> {
- if (mDialog != null) {
- mDialog.dismiss();
- }
- mDialog = showConfirmDialog(mDialogSupplier.get(),
- mPowerManager, eventTime, mBiometricState, mDialogDismissListener);
- });
return true;
+ case STATE_KEYGUARD_AUTH:
default:
return false;
}
}
@NonNull
- private static Dialog showConfirmDialog(@NonNull AlertDialog.Builder dialogBuilder,
- @NonNull PowerManager powerManager, long eventTime,
+ private static Dialog showConfirmDialog(
+ @NonNull AlertDialog.Builder dialogBuilder,
+ @NonNull PowerManager powerManager,
+ long eventTime,
@BiometricStateListener.State int biometricState,
@NonNull DialogInterface.OnDismissListener dismissListener) {
final boolean enrolling = biometricState == STATE_ENROLLING;
- final int title = enrolling ? R.string.fp_power_button_enrollment_title
- : R.string.fp_power_button_bp_title;
- final int message = enrolling ? R.string.fp_power_button_enrollment_message
- : R.string.fp_power_button_bp_message;
- final int positiveText = enrolling ? R.string.fp_power_button_enrollment_positive_button
- : R.string.fp_power_button_bp_positive_button;
- final int negativeText = enrolling ? R.string.fp_power_button_enrollment_negative_button
- : R.string.fp_power_button_bp_negative_button;
-
- final Dialog confirmScreenOffDialog = dialogBuilder
- .setTitle(title)
- .setMessage(message)
- .setPositiveButton(positiveText,
- (dialog, which) -> {
- dialog.dismiss();
- powerManager.goToSleep(
- eventTime,
- PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
- 0 /* flags */
- );
- })
- .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
- .setOnDismissListener(dismissListener)
- .setCancelable(false)
- .create();
- confirmScreenOffDialog.getWindow().setType(
- WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
+ final int title =
+ enrolling
+ ? R.string.fp_power_button_enrollment_title
+ : R.string.fp_power_button_bp_title;
+ final int message =
+ enrolling
+ ? R.string.fp_power_button_enrollment_message
+ : R.string.fp_power_button_bp_message;
+ final int positiveText =
+ enrolling
+ ? R.string.fp_power_button_enrollment_positive_button
+ : R.string.fp_power_button_bp_positive_button;
+ final int negativeText =
+ enrolling
+ ? R.string.fp_power_button_enrollment_negative_button
+ : R.string.fp_power_button_bp_negative_button;
+
+ final Dialog confirmScreenOffDialog =
+ dialogBuilder
+ .setTitle(title)
+ .setMessage(message)
+ .setPositiveButton(
+ positiveText,
+ (dialog, which) -> {
+ dialog.dismiss();
+ powerManager.goToSleep(
+ eventTime,
+ PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON,
+ 0 /* flags */);
+ })
+ .setNegativeButton(negativeText, (dialog, which) -> dialog.dismiss())
+ .setOnDismissListener(dismissListener)
+ .setCancelable(false)
+ .create();
+ confirmScreenOffDialog
+ .getWindow()
+ .setType(WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL);
confirmScreenOffDialog.show();
return confirmScreenOffDialog;
}
/**
- * Awaits notification from PhoneWindowManager that fingerprint service is ready
- * to send updates about power button fps sensor state. Then configures a
- * BiometricStateListener to receive and record updates to fps state, and
- * registers the BiometricStateListener in FingerprintManager.
+ * Awaits notification from PhoneWindowManager that fingerprint service is ready to send updates
+ * about power button fps sensor state. Then configures a BiometricStateListener to receive and
+ * record updates to fps state, and registers the BiometricStateListener in FingerprintManager.
*/
public void onFingerprintSensorReady() {
final PackageManager pm = mContext.getPackageManager();
@@ -184,12 +247,12 @@ public class SideFpsEventHandler {
if (fingerprintManager.isPowerbuttonFps()) {
fingerprintManager.registerBiometricStateListener(
new BiometricStateListener() {
- @Nullable
- private Runnable mStateRunnable = null;
+ @Nullable private Runnable mStateRunnable = null;
@Override
public void onStateChanged(
@BiometricStateListener.State int newState) {
+ Log.d(TAG, "onStateChanged : " + newState);
if (mStateRunnable != null) {
mHandler.removeCallbacks(mStateRunnable);
mStateRunnable = null;
@@ -200,16 +263,58 @@ public class SideFpsEventHandler {
// damper when moving to idle in case auth is first
if (newState == STATE_IDLE) {
mStateRunnable = () -> mBiometricState = newState;
- mHandler.postDelayed(mStateRunnable,
- DEBOUNCE_DELAY_MILLIS);
+ // This is also useful in the case of biometric
+ // prompt.
+ // If a user has recently succeeded/failed auth, we
+ // want to disable the power button for a short
+ // period of time (so ethey are able to view the
+ // prompt)
+ mHandler.postDelayed(
+ mStateRunnable, DEBOUNCE_DELAY_MILLIS);
+ dismissDialog("STATE_IDLE");
} else {
mBiometricState = newState;
}
}
+
+ @Override
+ public void onBiometricAction(
+ @BiometricStateListener.Action int action) {
+ Log.d(TAG, "onBiometricAction " + action);
+ switch (action) {
+ case BiometricStateListener.ACTION_SENSOR_TOUCH:
+ mHandler.postDelayed(
+ mTurnOffDialog,
+ mTapWaitForPowerDuration);
+ break;
+ }
+ }
});
mSideFpsEventHandlerReady.set(true);
}
}
});
}
+
+ private void dismissDialog(String reason) {
+ Log.d(TAG, "Dismissing dialog with reason: " + reason);
+ if (mDialog != null && mDialog.isShowing()) {
+ mDialog.dismiss();
+ }
+ }
+
+ private void showDialog(long time, String reason) {
+ Log.d(TAG, "Showing dialog with reason: " + reason);
+ if (mDialog != null && mDialog.isShowing()) {
+ Log.d(TAG, "Ignoring show dialog");
+ return;
+ }
+ mDialog =
+ showConfirmDialog(
+ mDialogSupplier.get(),
+ mPowerManager,
+ time,
+ mBiometricState,
+ mDialogDismissListener);
+ }
}
diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
index 7ca09ab5154f..e8a3dcd5635f 100644
--- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java
+++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java
@@ -78,7 +78,6 @@ import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.IDisplayFoldListener;
-import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
@@ -343,6 +342,22 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
* @return {@code true} if app transition state is idle on the default display.
*/
boolean isAppTransitionStateIdle();
+
+ /**
+ * Enables the screen if all conditions are met.
+ */
+ void enableScreenIfNeeded();
+
+ /**
+ * Updates the current screen rotation based on the current state of the world.
+ *
+ * @param alwaysSendConfiguration Flag to force a new configuration to be evaluated.
+ * This can be used when there are other parameters in
+ * configuration that are changing.
+ * @param forceRelayout If true, the window manager will always do a relayout of its
+ * windows even if the rotation hasn't changed.
+ */
+ void updateRotation(boolean alwaysSendConfiguration, boolean forceRelayout);
}
/**
@@ -391,8 +406,7 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
*
* @param context The system context we are running in.
*/
- public void init(Context context, IWindowManager windowManager,
- WindowManagerFuncs windowManagerFuncs);
+ void init(Context context, WindowManagerFuncs windowManagerFuncs);
/**
* Check permissions when adding a window.
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 8c52717b1a20..dbf05f1cd7c7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -112,6 +112,7 @@ import com.android.internal.app.IBatteryStats;
import com.android.internal.display.BrightnessSynchronizer;
import com.android.internal.os.BackgroundThread;
import com.android.internal.util.DumpUtils;
+import com.android.internal.util.FrameworkStatsLog;
import com.android.internal.util.LatencyTracker;
import com.android.internal.util.Preconditions;
import com.android.server.EventLogTags;
@@ -4800,6 +4801,11 @@ public final class PowerManagerService extends SystemService
.IS_STAY_ON_WHILE_PLUGGED_IN_WIRELESS,
((mStayOnWhilePluggedInSetting & BatteryManager.BATTERY_PLUGGED_WIRELESS)
!= 0));
+ proto.write(
+ PowerServiceSettingsAndConfigurationDumpProto.StayOnWhilePluggedInProto
+ .IS_STAY_ON_WHILE_PLUGGED_IN_DOCK,
+ ((mStayOnWhilePluggedInSetting & BatteryManager.BATTERY_PLUGGED_DOCK)
+ != 0));
proto.end(stayOnWhilePluggedInToken);
proto.write(
@@ -4960,6 +4966,7 @@ public final class PowerManagerService extends SystemService
int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
Intent.EXTRA_DOCK_STATE_UNDOCKED);
if (mDockState != dockState) {
+ FrameworkStatsLog.write(FrameworkStatsLog.DOCK_STATE_CHANGED, dockState);
mDockState = dockState;
mDirty |= DIRTY_DOCK_STATE;
updatePowerStateLocked();
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
index 13fe14caa1f9..6318a99fc9a2 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewarePermission.java
@@ -21,6 +21,7 @@ import static android.Manifest.permission.RECORD_AUDIO;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.content.Context;
import android.content.PermissionChecker;
import android.media.permission.Identity;
@@ -115,8 +116,10 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware
* originator temporarily doesn't have the right permissions to use this service.
*/
private void enforcePermissionsForPreflight(@NonNull Identity identity) {
- enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO);
- enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD);
+ enforcePermissionForPreflight(mContext, identity, RECORD_AUDIO,
+ /* allowSoftDenial= */ true);
+ enforcePermissionForPreflight(mContext, identity, CAPTURE_AUDIO_HOTWORD,
+ /* allowSoftDenial= */ true);
}
/**
@@ -124,8 +127,7 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware
*/
void enforcePermissionsForDataDelivery(@NonNull Identity identity, @NonNull String reason) {
enforceSoundTriggerRecordAudioPermissionForDataDelivery(identity, reason);
- enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD,
- reason);
+ enforcePermissionForDataDelivery(mContext, identity, CAPTURE_AUDIO_HOTWORD, reason);
}
/**
@@ -165,20 +167,25 @@ public class SoundTriggerMiddlewarePermission implements ISoundTriggerMiddleware
/**
* Throws a {@link SecurityException} if originator permanently doesn't have the given
* permission.
- * Soft (temporary) denials are considered OK for preflight purposes.
*
- * @param context A {@link Context}, used for permission checks.
- * @param identity The identity to check.
- * @param permission The identifier of the permission we want to check.
+ * @param context A {@link Context}, used for permission checks.
+ * @param identity The identity to check.
+ * @param permission The identifier of the permission we want to check.
+ * @param allowSoftDenial If true, the operation succeeds even for soft (temporary) denials.
*/
+ // TODO: Consider splitting up this method instead of using `allowSoftDenial`, to make it
+ // clearer when soft denials are not allowed.
private static void enforcePermissionForPreflight(@NonNull Context context,
- @NonNull Identity identity, @NonNull String permission) {
+ @NonNull Identity identity, @NonNull String permission, boolean allowSoftDenial) {
final int status = PermissionUtil.checkPermissionForPreflight(context, identity,
permission);
switch (status) {
case PermissionChecker.PERMISSION_GRANTED:
- case PermissionChecker.PERMISSION_SOFT_DENIED:
return;
+ case PermissionChecker.PERMISSION_SOFT_DENIED:
+ if (allowSoftDenial) {
+ return;
+ } // else fall through
case PermissionChecker.PERMISSION_HARD_DENIED:
throw new SecurityException(
String.format("Failed to obtain permission %s for identity %s", permission,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index b00d8b47906a..53b8b53e2b6c 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -27,6 +27,7 @@ import android.view.InsetsVisibilities;
import android.view.WindowInsetsController.Appearance;
import android.view.WindowInsetsController.Behavior;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.view.AppearanceRegion;
import com.android.server.notification.NotificationDelegate;
@@ -133,7 +134,8 @@ public interface StatusBarManagerInternal {
/** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
- @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName);
+ @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+ LetterboxDetails[] letterboxDetails);
/** @see com.android.internal.statusbar.IStatusBar#showTransient */
void showTransient(int displayId, @InternalInsetsType int[] types,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index 46e7574e1c8a..bec3754536e2 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -96,6 +96,7 @@ import com.android.internal.statusbar.ISessionListener;
import com.android.internal.statusbar.IStatusBar;
import com.android.internal.statusbar.IStatusBarService;
import com.android.internal.statusbar.IUndoMediaTransferCallback;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.statusbar.NotificationVisibility;
import com.android.internal.statusbar.RegisterStatusBarResult;
import com.android.internal.statusbar.StatusBarIcon;
@@ -596,13 +597,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
@Behavior int behavior, InsetsVisibilities requestedVisibilities,
- String packageName) {
+ String packageName, LetterboxDetails[] letterboxDetails) {
getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
+ navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ letterboxDetails);
if (mBar != null) {
try {
mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
- navbarColorManagedByIme, behavior, requestedVisibilities, packageName);
+ navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+ letterboxDetails);
} catch (RemoteException ex) { }
}
}
@@ -1204,17 +1207,20 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
private int mImeBackDisposition = 0;
private boolean mShowImeSwitcher = false;
private IBinder mImeToken = null;
+ private LetterboxDetails[] mLetterboxDetails = new LetterboxDetails[0];
private void setBarAttributes(@Appearance int appearance,
AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
@Behavior int behavior, InsetsVisibilities requestedVisibilities,
- String packageName) {
+ String packageName,
+ LetterboxDetails[] letterboxDetails) {
mAppearance = appearance;
mAppearanceRegions = appearanceRegions;
mNavbarColorManagedByIme = navbarColorManagedByIme;
mBehavior = behavior;
mRequestedVisibilities = requestedVisibilities;
mPackageName = packageName;
+ mLetterboxDetails = letterboxDetails;
}
private void showTransient(@InternalInsetsType int[] types) {
@@ -1341,7 +1347,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D
state.mImeBackDisposition, state.mShowImeSwitcher,
gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
- state.mPackageName, transientBarTypes);
+ state.mPackageName, transientBarTypes, state.mLetterboxDetails);
}
}
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 78b1c20ac4b2..d79837be3583 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -61,12 +61,11 @@ final class Vibration {
IGNORED_BACKGROUND,
IGNORED_UNKNOWN_VIBRATION,
IGNORED_UNSUPPORTED,
- IGNORED_FOR_ALARM,
IGNORED_FOR_EXTERNAL,
+ IGNORED_FOR_HIGHER_IMPORTANCE,
IGNORED_FOR_ONGOING,
IGNORED_FOR_POWER,
IGNORED_FOR_RINGER_MODE,
- IGNORED_FOR_RINGTONE,
IGNORED_FOR_SETTINGS,
IGNORED_SUPERSEDED,
}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index f0911ca62027..5ac2f4f27452 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -713,14 +713,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
case IGNORED_ERROR_APP_OPS:
Slog.w(TAG, "Would be an error: vibrate from uid " + uid);
break;
- case IGNORED_FOR_ALARM:
+ case IGNORED_FOR_EXTERNAL:
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of alarm vibration");
+ Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
}
break;
- case IGNORED_FOR_EXTERNAL:
+ case IGNORED_FOR_HIGHER_IMPORTANCE:
if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration for current external vibration");
+ Slog.d(TAG, "Ignoring incoming vibration in favor of ongoing vibration"
+ + " with higher importance");
}
break;
case IGNORED_FOR_ONGOING:
@@ -734,12 +735,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
+ attrs);
}
break;
- case IGNORED_FOR_RINGTONE:
- if (DEBUG) {
- Slog.d(TAG, "Ignoring incoming vibration in favor of ringtone vibration");
- }
- break;
-
default:
if (DEBUG) {
Slog.d(TAG, "Vibration for uid=" + uid + " and with attrs=" + attrs
@@ -812,20 +807,43 @@ public class VibratorManagerService extends IVibratorManagerService.Stub {
return null;
}
- if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_ALARM) {
- return Vibration.Status.IGNORED_FOR_ALARM;
- }
-
- if (currentVibration.attrs.getUsage() == VibrationAttributes.USAGE_RINGTONE) {
- return Vibration.Status.IGNORED_FOR_RINGTONE;
+ int currentUsage = currentVibration.attrs.getUsage();
+ int newUsage = vib.attrs.getUsage();
+ if (getVibrationImportance(currentUsage) > getVibrationImportance(newUsage)) {
+ // Current vibration has higher importance than this one and should not be cancelled.
+ return Vibration.Status.IGNORED_FOR_HIGHER_IMPORTANCE;
}
if (currentVibration.isRepeating()) {
+ // Current vibration is repeating, assume it's more important.
return Vibration.Status.IGNORED_FOR_ONGOING;
}
+
return null;
}
+ private static int getVibrationImportance(@VibrationAttributes.Usage int usage) {
+ switch (usage) {
+ case VibrationAttributes.USAGE_RINGTONE:
+ return 5;
+ case VibrationAttributes.USAGE_ALARM:
+ return 4;
+ case VibrationAttributes.USAGE_NOTIFICATION:
+ return 3;
+ case VibrationAttributes.USAGE_COMMUNICATION_REQUEST:
+ case VibrationAttributes.USAGE_ACCESSIBILITY:
+ return 2;
+ case VibrationAttributes.USAGE_HARDWARE_FEEDBACK:
+ case VibrationAttributes.USAGE_PHYSICAL_EMULATION:
+ return 1;
+ case VibrationAttributes.USAGE_MEDIA:
+ case VibrationAttributes.USAGE_TOUCH:
+ case VibrationAttributes.USAGE_UNKNOWN:
+ default:
+ return 0;
+ }
+ }
+
/**
* Check if given vibration should be ignored by this service.
*
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index f6748de660e2..d2a00af245f6 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -43,6 +43,7 @@ import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_N
import static com.android.server.wm.ActivityTaskManagerService.TAG_SWITCH;
import static com.android.server.wm.ActivityTaskManagerService.enforceNotIsolatedCaller;
+import android.Manifest;
import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -462,8 +463,8 @@ class ActivityClientController extends IActivityClientController.Stub {
// Explicitly dismissing the activity so reset its relaunch flag.
r.mRelaunchReason = RELAUNCH_REASON_NONE;
} else {
- r.finishIfPossible(resultCode, resultData, resultGrants,
- "app-request", true /* oomAdj */);
+ r.finishIfPossible(resultCode, resultData, resultGrants, "app-request",
+ true /* oomAdj */);
res = r.finishing;
if (!res) {
Slog.i(TAG, "Failed to finish by app-request");
@@ -525,6 +526,23 @@ class ActivityClientController extends IActivityClientController.Stub {
}
@Override
+ public void setForceSendResultForMediaProjection(IBinder token) {
+ // Require that this is invoked only during MediaProjection setup.
+ mService.mAmInternal.enforceCallingPermission(
+ Manifest.permission.MANAGE_MEDIA_PROJECTION,
+ "setForceSendResultForMediaProjection");
+
+ final ActivityRecord r;
+ synchronized (mGlobalLock) {
+ r = ActivityRecord.isInRootTaskLocked(token);
+ if (r == null || !r.isInHistory()) {
+ return;
+ }
+ r.setForceSendResultForMediaProjection();
+ }
+ }
+
+ @Override
public boolean isTopOfTask(IBinder token) {
synchronized (mGlobalLock) {
final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token);
@@ -559,6 +577,21 @@ class ActivityClientController extends IActivityClientController.Stub {
}
}
+ /**
+ * Returns the windowing mode of the task that hosts the activity, or {@code -1} if task is not
+ * found.
+ */
+ @Override
+ public int getTaskWindowingMode(IBinder activityToken) {
+ synchronized (mGlobalLock) {
+ final ActivityRecord ar = ActivityRecord.isInAnyTask(activityToken);
+ if (ar == null) {
+ return -1;
+ }
+ return ar.getTask().getWindowingMode();
+ }
+ }
+
@Override
@Nullable
public IBinder getActivityTokenBelow(IBinder activityToken) {
@@ -737,7 +770,7 @@ class ActivityClientController extends IActivityClientController.Stub {
synchronized (mGlobalLock) {
final ActivityRecord r = ensureValidPictureInPictureActivityParams(
"enterPictureInPictureMode", token, params);
- return mService.enterPictureInPictureMode(r, params);
+ return mService.enterPictureInPictureMode(r, params, true /* fromClient */);
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -853,22 +886,23 @@ class ActivityClientController extends IActivityClientController.Stub {
/**
* Requests that an activity should enter picture-in-picture mode if possible. This method may
* be used by the implementation of non-phone form factors.
+ *
+ * @return false if the activity cannot enter PIP mode.
*/
- void requestPictureInPictureMode(@NonNull ActivityRecord r) {
+ boolean requestPictureInPictureMode(@NonNull ActivityRecord r) {
if (r.inPinnedWindowingMode()) {
- throw new IllegalStateException("Activity is already in PIP mode");
+ return false;
}
final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
"requestPictureInPictureMode", /* beforeStopping */ false);
if (!canEnterPictureInPicture) {
- throw new IllegalStateException(
- "Requested PIP on an activity that doesn't support it");
+ return false;
}
if (r.pictureInPictureArgs.isAutoEnterEnabled()) {
- mService.enterPictureInPictureMode(r, r.pictureInPictureArgs);
- return;
+ return mService.enterPictureInPictureMode(r, r.pictureInPictureArgs,
+ false /* fromClient */);
}
try {
@@ -876,9 +910,11 @@ class ActivityClientController extends IActivityClientController.Stub {
r.app.getThread(), r.token);
transaction.addCallback(EnterPipRequestedItem.obtain());
mService.getLifecycleManager().scheduleTransaction(transaction);
+ return true;
} catch (Exception e) {
Slog.w(TAG, "Failed to send enter pip requested item: "
+ r.intent.getComponent(), e);
+ return false;
}
}
@@ -928,6 +964,7 @@ class ActivityClientController extends IActivityClientController.Stub {
if (rootTask.inFreeformWindowingMode()) {
rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ rootTask.setBounds(null);
} else if (!r.supportsFreeform()) {
throw new IllegalStateException(
"This activity is currently not freeform-enabled");
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index d6c0ab6b124b..891654979965 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -56,6 +56,10 @@ import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_T
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_NO_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_REPORTED_DRAWN_WITH_BUNDLE;
import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_LETTERBOXED;
import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE;
import static com.android.internal.util.FrameworkStatsLog.CAMERA_COMPAT_CONTROL_EVENT_REPORTED__EVENT__APPEARED_APPLY_TREATMENT;
@@ -68,6 +72,7 @@ import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_METRICS;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_TIMEOUT;
import static com.android.server.wm.EventLogTags.WM_ACTIVITY_LAUNCH_TIME;
@@ -101,6 +106,7 @@ import android.util.TimeUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.MetricsLogger;
import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.LatencyTracker;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.FgThread;
import com.android.server.LocalServices;
@@ -143,6 +149,12 @@ class ActivityMetricsLogger {
private static final long UNKNOWN_VISIBILITY_CHECK_DELAY_MS = 3000;
/**
+ * If the recents animation is finished before the delay since the window drawn, do not log the
+ * action because the duration is too small that may be just an accidentally touch.
+ */
+ private static final long LATENCY_TRACKER_RECENTS_DELAY_MS = 300;
+
+ /**
* The flag for {@link #notifyActivityLaunching} to skip associating a new launch with an active
* transition, in the case the launch is standalone (e.g. from recents).
*/
@@ -732,10 +744,6 @@ class ActivityMetricsLogger {
if (info.mLoggedTransitionStarting) {
done(false /* abort */, info, "notifyWindowsDrawn", timestampNs);
}
- if (r.mWmService.isRecentsAnimationTarget(r)) {
- r.mWmService.getRecentsAnimationController().logRecentsAnimationStartTime(
- info.mSourceEventDelayMs + info.mWindowsDrawnDelayMs);
- }
return infoSnapshot;
}
@@ -947,6 +955,9 @@ class ActivityMetricsLogger {
launchObserverNotifyActivityLaunchFinished(info, timestampNs);
}
logAppTransitionFinished(info, isHibernating != null ? isHibernating : false);
+ if (info.mReason == APP_TRANSITION_RECENTS_ANIM) {
+ logRecentsAnimationLatency(info);
+ }
}
mTransitionInfoList.remove(info);
}
@@ -1102,6 +1113,22 @@ class ActivityMetricsLogger {
Log.i(TAG, sb.toString());
}
+ private void logRecentsAnimationLatency(TransitionInfo info) {
+ final int duration = info.mSourceEventDelayMs + info.mWindowsDrawnDelayMs;
+ final ActivityRecord r = info.mLastLaunchedActivity;
+ final long lastTopLossTime = r.topResumedStateLossTime;
+ final WindowManagerService wm = mSupervisor.mService.mWindowManager;
+ final Object controller = wm.getRecentsAnimationController();
+ mLoggerHandler.postDelayed(() -> {
+ if (lastTopLossTime != r.topResumedStateLossTime
+ || controller != wm.getRecentsAnimationController()) {
+ // Skip if the animation was finished in a short time.
+ return;
+ }
+ wm.mLatencyTracker.logAction(LatencyTracker.ACTION_START_RECENTS_ANIMATION, duration);
+ }, LATENCY_TRACKER_RECENTS_DELAY_MS);
+ }
+
private static int getAppStartTransitionType(int tronType, boolean relaunched) {
if (tronType == TYPE_TRANSITION_COLD_LAUNCH) {
return FrameworkStatsLog.APP_START_OCCURRED__TYPE__COLD;
@@ -1368,7 +1395,7 @@ class ActivityMetricsLogger {
return;
}
- logAppCompatStateInternal(activity, state, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activity, state, compatStateInfo);
}
/**
@@ -1408,18 +1435,61 @@ class ActivityMetricsLogger {
}
}
if (activityToLog != null && stateToLog != APP_COMPAT_STATE_CHANGED__STATE__NOT_VISIBLE) {
- logAppCompatStateInternal(activityToLog, stateToLog, packageUid, compatStateInfo);
+ logAppCompatStateInternal(activityToLog, stateToLog, compatStateInfo);
}
}
+ private static boolean isAppCompateStateChangedToLetterboxed(int state) {
+ return state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_ASPECT_RATIO
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_FIXED_ORIENTATION
+ || state == APP_COMPAT_STATE_CHANGED__STATE__LETTERBOXED_FOR_SIZE_COMPAT_MODE;
+ }
+
private void logAppCompatStateInternal(@NonNull ActivityRecord activity, int state,
- int packageUid, PackageCompatStateInfo compatStateInfo) {
+ PackageCompatStateInfo compatStateInfo) {
compatStateInfo.mLastLoggedState = state;
compatStateInfo.mLastLoggedActivity = activity;
- FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED, packageUid, state);
+ int packageUid = activity.info.applicationInfo.uid;
+
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__NOT_LETTERBOXED_POSITION;
+ if (isAppCompateStateChangedToLetterboxed(state)) {
+ positionToLog = activity.mLetterboxUiController.getLetterboxPositionForLogging();
+ }
+ FrameworkStatsLog.write(FrameworkStatsLog.APP_COMPAT_STATE_CHANGED,
+ packageUid, state, positionToLog);
+
+ if (DEBUG_METRICS) {
+ Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s, %s)",
+ packageUid, state, positionToLog));
+ }
+ }
+
+ /**
+ * Logs the changing of the letterbox position along with its package UID
+ */
+ void logLetterboxPositionChange(@NonNull ActivityRecord activity, int position) {
+ int packageUid = activity.info.applicationInfo.uid;
+ FrameworkStatsLog.write(FrameworkStatsLog.LETTERBOX_POSITION_CHANGED, packageUid, position);
+
+ if (!mPackageUidToCompatStateInfo.contains(packageUid)) {
+ // There is no last logged activity for this packageUid so we should not log the
+ // position change as we can only log the position change for the current activity
+ return;
+ }
+ final PackageCompatStateInfo compatStateInfo = mPackageUidToCompatStateInfo.get(packageUid);
+ final ActivityRecord lastLoggedActivity = compatStateInfo.mLastLoggedActivity;
+ if (activity != lastLoggedActivity) {
+ // Only log the position change for the current activity to be consistent with
+ // findAppCompatStateToLog and ensure that metrics for the state changes are computed
+ // correctly
+ return;
+ }
+ int state = activity.getAppCompatState();
+ logAppCompatStateInternal(activity, state, compatStateInfo);
if (DEBUG_METRICS) {
- Slog.i(TAG, String.format("APP_COMPAT_STATE_CHANGED(%s, %s)", packageUid, state));
+ Slog.i(TAG, String.format("LETTERBOX_POSITION_CHANGED(%s, %s)",
+ packageUid, position));
}
}
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 4f222a4f8b83..a044e60b40ae 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -224,6 +224,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_STARTING_WIND
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
+import static com.android.server.wm.WindowManagerService.sEnableShellTransitions;
import static com.android.server.wm.WindowState.LEGACY_POLICY_VISIBILITY;
import static com.android.server.wm.WindowStateAnimator.HAS_DRAWN;
@@ -595,6 +596,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// pre-NYC apps that don't have a sense of being resized.
int mRelaunchReason = RELAUNCH_REASON_NONE;
+ private boolean mForceSendResultForMediaProjection = false;
+
TaskDescription taskDescription; // the recents information for this activity
// The locusId associated with this activity, if set.
@@ -925,7 +928,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
// SystemUi sets the pinned mode on activity after transition is done.
boolean mWaitForEnteringPinnedMode;
- private final ActivityRecordInputSink mActivityRecordInputSink;
+ final ActivityRecordInputSink mActivityRecordInputSink;
// Activities with this uid are allowed to not create an input sink while being in the same
// task and directly above this ActivityRecord. This field is updated whenever a new activity
@@ -1990,6 +1993,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (options.getLaunchIntoPipParams() != null) {
pictureInPictureArgs = options.getLaunchIntoPipParams();
+ if (sourceRecord != null) {
+ adjustPictureInPictureParamsIfNeeded(sourceRecord.getBounds());
+ }
}
mOverrideTaskTransition = options.getOverrideTaskTransition();
@@ -2066,6 +2072,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
launchMode = aInfo.launchMode;
+ // Don't move below setActivityType since it triggers onConfigurationChange ->
+ // resolveOverrideConfiguration that requires having mLetterboxUiController initialised.
+ mLetterboxUiController = new LetterboxUiController(mWmService, this);
+ mCameraCompatControlEnabled = mWmService.mContext.getResources()
+ .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
+
setActivityType(_componentSpecified, _launchedFromUid, _intent, options, sourceRecord);
immersive = (aInfo.flags & FLAG_IMMERSIVE) != 0;
@@ -2094,9 +2106,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mPersistentState = persistentState;
taskDescription = _taskDescription;
- mLetterboxUiController = new LetterboxUiController(mWmService, this);
- mCameraCompatControlEnabled = mWmService.mContext.getResources()
- .getBoolean(R.bool.config_isCameraCompatControlForStretchedIssuesEnabled);
shouldDockBigOverlays = mWmService.mContext.getResources()
.getBoolean(R.bool.config_dockBigOverlayWindows);
@@ -2795,7 +2804,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@VisibleForTesting
boolean canLaunchHomeActivity(int uid, ActivityRecord sourceRecord) {
- if (uid == Process.myUid() || uid == 0) {
+ if (uid == SYSTEM_UID || uid == 0) {
// System process can launch home activity.
return true;
}
@@ -3014,25 +3023,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
&& info.supportsPictureInPicture();
}
- /**
- * @return whether this activity supports split-screen multi-window and can be put in
- * split-screen.
- */
- @Override
- public boolean supportsSplitScreenWindowingMode() {
- return supportsSplitScreenWindowingModeInDisplayArea(getDisplayArea());
- }
-
- /**
- * @return whether this activity supports split-screen multi-window and can be put in
- * split-screen if it is in the given {@link TaskDisplayArea}.
- */
- boolean supportsSplitScreenWindowingModeInDisplayArea(@Nullable TaskDisplayArea tda) {
- return super.supportsSplitScreenWindowingMode()
- && mAtmService.mSupportsSplitScreenMultiWindow
- && supportsMultiWindowInDisplayArea(tda);
- }
-
boolean supportsFreeform() {
return supportsFreeformInDisplayArea(getDisplayArea());
}
@@ -3163,15 +3153,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mWillCloseOrEnterPip = willCloseOrEnterPip;
}
- /**
- * Returns whether this {@link ActivityRecord} is considered closing. Conditions are either
- * 1. Is this app animating and was requested to be hidden
- * 2. App is delayed closing since it might enter PIP.
- */
- boolean isClosingOrEnteringPip() {
- return (isAnimating(TRANSITION | PARENTS, ANIMATION_TYPE_APP_TRANSITION)
- && !mVisibleRequested) || mWillCloseOrEnterPip;
+ boolean willCloseOrEnterPip() {
+ return mWillCloseOrEnterPip;
}
+
/**
* @return Whether AppOps allows this package to enter picture-in-picture.
*/
@@ -3229,12 +3214,29 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return false;
}
- if (mRootWindowContainer.getTopResumedActivity() == this
- && getDisplayContent().mFocusedApp == this) {
- ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: already on top, "
- + "activity=%s", this);
- return !isState(RESUMED);
+ // If this activity already positions on the top focused task, moving the task to front
+ // is not needed. But we still need to ensure this activity is focused because the
+ // current focused activity could be another activity in the same Task if activities are
+ // displayed on adjacent TaskFragments.
+ final ActivityRecord currentFocusedApp = mDisplayContent.mFocusedApp;
+ if (currentFocusedApp != null && currentFocusedApp.task == task) {
+ final Task topFocusableTask = mDisplayContent.getTask(
+ (t) -> t.isLeafTask() && t.isFocusable(), true /* traverseTopToBottom */);
+ if (task == topFocusableTask) {
+ if (currentFocusedApp == this) {
+ ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: already on top "
+ + "and focused, activity=%s", this);
+ } else {
+ ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: set focused, "
+ + "activity=%s", this);
+ mDisplayContent.setFocusedApp(this);
+ mAtmService.mWindowManager.updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL,
+ true /* updateInputWindows */);
+ }
+ return !isState(RESUMED);
+ }
}
+
ProtoLog.d(WM_DEBUG_FOCUS, "moveFocusableActivityToTop: activity=%s", this);
rootTask.moveToFront(reason, task);
@@ -3284,7 +3286,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(resultGrants,
resultTo.getUriPermissionsLocked());
}
- resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
+ if (mForceSendResultForMediaProjection) {
+ resultTo.sendResult(this.getUid(), resultWho, requestCode, resultCode,
+ resultData, resultGrants, true /* forceSendForMediaProjection */);
+ } else {
+ resultTo.addResultLocked(this, resultWho, requestCode, resultCode, resultData);
+ }
resultTo = null;
} else if (DEBUG_RESULTS) {
Slog.v(TAG_RESULTS, "No result destination from " + this);
@@ -3478,6 +3485,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ void setForceSendResultForMediaProjection() {
+ mForceSendResultForMediaProjection = true;
+ }
+
private void prepareActivityHideTransitionAnimationIfOvarlay() {
if (mTaskOverlay) {
prepareActivityHideTransitionAnimation();
@@ -3517,7 +3528,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
final boolean isCurrentVisible = mVisibleRequested || isState(PAUSED, STARTED);
- if (updateVisibility && isCurrentVisible) {
+ if (updateVisibility && isCurrentVisible
+ // Avoid intermediate lifecycle change when launching with clearing task.
+ && !task.isClearingToReuseTask()) {
boolean ensureVisibility = false;
if (occludesParent(true /* includingFinishing */)) {
// If the current activity is not opaque, we need to make sure the visibilities of
@@ -4489,12 +4502,17 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/**
* @return Whether we are allowed to show non-starting windows at the moment. We disallow
- * showing windows during transitions in case we have windows that have wide-color-gamut
- * color mode set to avoid jank in the middle of the transition.
+ * showing windows while the transition animation is playing in case we have windows
+ * that have wide-color-gamut color mode set to avoid jank in the middle of the
+ * animation.
*/
boolean canShowWindows() {
- return allDrawn && !(isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION)
- && hasNonDefaultColorWindow());
+ final boolean drawn = mTransitionController.isShellTransitionsEnabled()
+ ? mSyncState != SYNC_STATE_WAITING_FOR_DRAW : allDrawn;
+ final boolean animating = mTransitionController.isShellTransitionsEnabled()
+ ? mTransitionController.inPlayingTransition(this)
+ : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
+ return drawn && !(animating && hasNonDefaultColorWindow());
}
/**
@@ -4569,6 +4587,12 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
Intent data, NeededUriGrants dataGrants) {
+ sendResult(callingUid, resultWho, requestCode, resultCode, data, dataGrants,
+ false /* forceSendForMediaProjection */);
+ }
+
+ private void sendResult(int callingUid, String resultWho, int requestCode, int resultCode,
+ Intent data, NeededUriGrants dataGrants, boolean forceSendForMediaProjection) {
if (callingUid > 0) {
mAtmService.mUgmInternal.grantUriPermissionUncheckedFromIntent(dataGrants,
getUriPermissionsLocked());
@@ -4577,8 +4601,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (DEBUG_RESULTS) {
Slog.v(TAG, "Send activity result to " + this
+ " : who=" + resultWho + " req=" + requestCode
- + " res=" + resultCode + " data=" + data);
+ + " res=" + resultCode + " data=" + data
+ + " forceSendForMediaProjection=" + forceSendForMediaProjection);
}
+
if (isState(RESUMED) && attachedToProcess()) {
try {
final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
@@ -4591,9 +4617,63 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
}
+ // Schedule sending results now for Media Projection setup.
+ if (forceSendForMediaProjection && attachedToProcess() && isState(STARTED, PAUSING, PAUSED,
+ STOPPING, STOPPED)) {
+ final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
+ // Build result to be returned immediately.
+ transaction.addCallback(ActivityResultItem.obtain(
+ List.of(new ResultInfo(resultWho, requestCode, resultCode, data))));
+ // When the activity result is delivered, the activity will transition to RESUMED.
+ // Since the activity is only resumed so the result can be immediately delivered,
+ // return it to its original lifecycle state.
+ ActivityLifecycleItem lifecycleItem = getLifecycleItemForCurrentStateForResult();
+ if (lifecycleItem != null) {
+ transaction.setLifecycleStateRequest(lifecycleItem);
+ } else {
+ Slog.w(TAG, "Unable to get the lifecycle item for state " + mState
+ + " so couldn't immediately send result");
+ }
+ try {
+ mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+ } catch (RemoteException e) {
+ Slog.w(TAG, "Exception thrown sending result to " + this, e);
+ }
+ }
+
addResultLocked(null /* from */, resultWho, requestCode, resultCode, data);
}
+ /**
+ * Provides a lifecycle item for the current stat. Only to be used when force sending an
+ * activity result (as part of MeidaProjection setup). Does not support the following states:
+ * {@link State#INITIALIZING}, {@link State#RESTARTING_PROCESS},
+ * {@link State#FINISHING}, {@link State#DESTROYING}, {@link State#DESTROYED}. It does not make
+ * sense to force send a result to an activity in these states. Does not support
+ * {@link State#RESUMED} since a resumed activity will end in the resumed state after handling
+ * the result.
+ *
+ * @return an {@link ActivityLifecycleItem} for the current state, or {@code null} if the
+ * state is not valid.
+ */
+ @Nullable
+ private ActivityLifecycleItem getLifecycleItemForCurrentStateForResult() {
+ switch (mState) {
+ case STARTED:
+ return StartActivityItem.obtain(null);
+ case PAUSING:
+ case PAUSED:
+ return PauseActivityItem.obtain();
+ case STOPPING:
+ case STOPPED:
+ return StopActivityItem.obtain(configChangeFlags);
+ default:
+ // Do not send a result immediately if the activity is in state INITIALIZING,
+ // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
+ return null;
+ }
+ }
+
private void addNewIntentLocked(ReferrerIntent intent) {
if (newIntents == null) {
newIntents = new ArrayList<>();
@@ -4673,6 +4753,8 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (mPendingRemoteAnimation != null) {
mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
mPendingRemoteAnimation);
+ mTransitionController.setStatusBarTransitionDelay(
+ mPendingRemoteAnimation.getStatusBarTransitionDelay());
} else {
if (mPendingOptions == null
|| mPendingOptions.getAnimationType() == ANIM_SCENE_TRANSITION) {
@@ -5191,12 +5273,19 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
final int windowsCount = mChildren.size();
+ // With Shell-Transition, the activity will running a transition when it is visible.
+ // It won't be included when fromTransition is true means the call from finishTransition.
+ final boolean runningAnimation = sEnableShellTransitions ? visible
+ : isAnimating(PARENTS, ANIMATION_TYPE_APP_TRANSITION);
for (int i = 0; i < windowsCount; i++) {
- mChildren.get(i).onAppVisibilityChanged(visible, isAnimating(PARENTS,
- ANIMATION_TYPE_APP_TRANSITION));
+ mChildren.get(i).onAppVisibilityChanged(visible, runningAnimation);
}
setVisible(visible);
setVisibleRequested(visible);
+ ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "commitVisibility: %s: visible=%b"
+ + " visibleRequested=%b, isInTransition=%b, runningAnimation=%b, caller=%s",
+ this, isVisible(), mVisibleRequested, isInTransition(), runningAnimation,
+ Debug.getCallers(5));
if (!visible) {
stopFreezingScreen(true, true);
} else {
@@ -5219,9 +5308,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
task.dispatchTaskInfoChangedIfNeeded(false /* force */);
task = task.getParent().asTask();
}
- ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
- "commitVisibility: %s: visible=%b mVisibleRequested=%b", this,
- isVisible(), mVisibleRequested);
final DisplayContent displayContent = getDisplayContent();
displayContent.getInputMonitor().setUpdateInputWindowsNeededLw();
if (performLayout) {
@@ -5256,17 +5342,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
final boolean delayed = isAnimating(PARENTS | CHILDREN,
ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_WINDOW_ANIMATION
| ANIMATION_TYPE_RECENTS);
- if (!delayed && !usingShellTransitions) {
- // We aren't delayed anything, but exiting windows rely on the animation finished
- // callback being called in case the ActivityRecord was pretending to be delayed,
- // which we might have done because we were in closing/opening apps list.
- onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, null /* AnimationAdapter */);
- if (visible) {
- // The token was made immediately visible, there will be no entrance animation.
- // We need to inform the client the enter animation was finished.
- mEnteringAnimation = true;
- mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
- token);
+ if (!delayed) {
+ if (!usingShellTransitions) {
+ if (visible) {
+ // The token was made immediately visible, there will be no entrance animation.
+ // We need to inform the client the enter animation was finished.
+ mEnteringAnimation = true;
+ mWmService.mActivityManagerAppTransitionNotifier.onAppTransitionFinishedLocked(
+ token);
+ }
+ } else {
+ // update wallpaper target
+ setAppLayoutChanges(FINISH_LAYOUT_REDO_WALLPAPER, "ActivityRecord");
}
}
@@ -5550,7 +5637,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
wasStopped, this);
mAppStopped = false;
// Allow the window to turn the screen on once the app is resumed again.
- setCurrentLaunchCanTurnScreenOn(true);
+ if (mAtmService.getActivityStartController().isInExecution()) {
+ setCurrentLaunchCanTurnScreenOn(true);
+ }
+
if (!wasStopped) {
destroySurfaces(true /*cleanupOnResume*/);
}
@@ -7726,11 +7816,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
newParentConfiguration.windowConfiguration.getWindowingMode();
final boolean isFixedOrientationLetterboxAllowed =
parentWindowingMode == WINDOWING_MODE_MULTI_WINDOW
- || parentWindowingMode == WINDOWING_MODE_FULLSCREEN;
+ || parentWindowingMode == WINDOWING_MODE_FULLSCREEN
+ // Switching from PiP to fullscreen.
+ || (parentWindowingMode == WINDOWING_MODE_PINNED
+ && resolvedConfig.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN);
// TODO(b/181207944): Consider removing the if condition and always run
// resolveFixedOrientationConfiguration() since this should be applied for all cases.
if (isFixedOrientationLetterboxAllowed) {
- resolveFixedOrientationConfiguration(newParentConfiguration, parentWindowingMode);
+ resolveFixedOrientationConfiguration(newParentConfiguration);
}
if (mCompatDisplayInsets != null) {
@@ -7754,7 +7848,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (isFixedOrientationLetterboxAllowed || mCompatDisplayInsets != null
// In fullscreen, can be letterboxed for aspect ratio.
|| !inMultiWindowMode()) {
- updateResolvedBoundsHorizontalPosition(newParentConfiguration);
+ updateResolvedBoundsPosition(newParentConfiguration);
}
if (mVisibleRequested) {
@@ -7857,39 +7951,60 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
/**
- * Adjusts horizontal position of resolved bounds if they doesn't fill the parent using gravity
+ * Adjusts position of resolved bounds if they doesn't fill the parent using gravity
* requested in the config or via an ADB command. For more context see {@link
- * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)}.
+ * LetterboxUiController#getHorizontalPositionMultiplier(Configuration)} and
+ * {@link LetterboxUiController#getVerticalPositionMultiplier(Configuration)}
*/
- private void updateResolvedBoundsHorizontalPosition(Configuration newParentConfiguration) {
+ private void updateResolvedBoundsPosition(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
final Rect resolvedBounds = resolvedConfig.windowConfiguration.getBounds();
final Rect screenResolvedBounds =
mSizeCompatBounds != null ? mSizeCompatBounds : resolvedBounds;
final Rect parentAppBounds = newParentConfiguration.windowConfiguration.getAppBounds();
final Rect parentBounds = newParentConfiguration.windowConfiguration.getBounds();
- if (resolvedBounds.isEmpty() || parentBounds.width() == screenResolvedBounds.width()) {
+ if (resolvedBounds.isEmpty()) {
return;
}
-
+ // Horizontal position
int offsetX = 0;
- if (screenResolvedBounds.width() >= parentAppBounds.width()) {
- // If resolved bounds overlap with insets, center within app bounds.
- offsetX = getHorizontalCenterOffset(
- parentAppBounds.width(), screenResolvedBounds.width());
- } else {
- float positionMultiplier =
- mLetterboxUiController.getHorizontalPositionMultiplier(newParentConfiguration);
- offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
- * positionMultiplier);
+ if (parentBounds.width() != screenResolvedBounds.width()) {
+ if (screenResolvedBounds.width() >= parentAppBounds.width()) {
+ // If resolved bounds overlap with insets, center within app bounds.
+ offsetX = getCenterOffset(
+ parentAppBounds.width(), screenResolvedBounds.width());
+ } else {
+ float positionMultiplier =
+ mLetterboxUiController.getHorizontalPositionMultiplier(
+ newParentConfiguration);
+ offsetX = (int) Math.ceil((parentAppBounds.width() - screenResolvedBounds.width())
+ * positionMultiplier);
+ }
+ }
+
+ // Vertical position
+ int offsetY = 0;
+ if (parentBounds.height() != screenResolvedBounds.height()) {
+ if (screenResolvedBounds.height() >= parentAppBounds.height()) {
+ // If resolved bounds overlap with insets, center within app bounds.
+ offsetY = getCenterOffset(
+ parentAppBounds.height(), screenResolvedBounds.height());
+ } else {
+ float positionMultiplier =
+ mLetterboxUiController.getVerticalPositionMultiplier(
+ newParentConfiguration);
+ offsetY = (int) Math.ceil((parentAppBounds.height() - screenResolvedBounds.height())
+ * positionMultiplier);
+ }
}
if (mSizeCompatBounds != null) {
- mSizeCompatBounds.offset(offsetX, 0 /* offsetY */);
+ mSizeCompatBounds.offset(offsetX , offsetY);
+ final int dy = mSizeCompatBounds.top - resolvedBounds.top;
final int dx = mSizeCompatBounds.left - resolvedBounds.left;
- offsetBounds(resolvedConfig, dx, 0 /* offsetY */);
+ offsetBounds(resolvedConfig, dx, dy);
} else {
- offsetBounds(resolvedConfig, offsetX, 0 /* offsetY */);
+ offsetBounds(resolvedConfig, offsetX, offsetY);
}
// Since bounds has changed, the configuration needs to be computed accordingly.
@@ -7901,8 +8016,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
}
boolean isInTransition() {
- return mTransitionController.inTransition() // Shell transitions.
- || isAnimating(PARENTS | TRANSITION); // Legacy transitions.
+ return inTransitionSelfOrParent();
}
/**
@@ -7917,6 +8031,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return mLetterboxBoundsForFixedOrientationAndAspectRatio != null;
}
+ boolean isAspectRatioApplied() {
+ return mIsAspectRatioApplied;
+ }
+
/**
* Whether this activity is eligible for letterbox eduction.
*
@@ -7998,8 +8116,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
* <p>If letterboxed due to fixed orientation then aspect ratio restrictions are also applied
* in this method.
*/
- private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig,
- int windowingMode) {
+ private void resolveFixedOrientationConfiguration(@NonNull Configuration newParentConfig) {
mLetterboxBoundsForFixedOrientationAndAspectRatio = null;
mIsEligibleForFixedOrientationLetterbox = false;
final Rect parentBounds = newParentConfig.windowConfiguration.getBounds();
@@ -8019,11 +8136,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
if (organizedTf != null && !organizedTf.fillsParent()) {
return;
}
- if (windowingMode == WINDOWING_MODE_PINNED) {
- // PiP bounds have higher priority than the requested orientation. Otherwise the
- // activity may be squeezed into a small piece.
- return;
- }
final Rect resolvedBounds =
getResolvedOverrideConfiguration().windowConfiguration.getBounds();
@@ -8088,21 +8200,13 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
resolvedBounds.set(containingBounds);
final float letterboxAspectRatioOverride =
- mLetterboxUiController.getFixedOrientationLetterboxAspectRatio(newParentConfig);
+ mLetterboxUiController.getFixedOrientationLetterboxAspectRatio();
final float desiredAspectRatio =
letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
? letterboxAspectRatioOverride : computeAspectRatio(parentBounds);
// Apply aspect ratio to resolved bounds
mIsAspectRatioApplied = applyAspectRatio(resolvedBounds, containingBoundsWithInsets,
- containingBounds, desiredAspectRatio, true);
-
- // Vertically center if orientation is landscape. Center within parent bounds with insets
- // to ensure that insets do not trim height. Bounds will later be horizontally centered in
- // {@link updateResolvedBoundsHorizontalPosition()} regardless of orientation.
- if (forcedOrientation == ORIENTATION_LANDSCAPE) {
- final int offsetY = parentBoundsWithInsets.centerY() - resolvedBounds.centerY();
- resolvedBounds.offset(0, offsetY);
- }
+ containingBounds, desiredAspectRatio);
if (mCompatDisplayInsets != null) {
mCompatDisplayInsets.getBoundsByRotation(
@@ -8126,10 +8230,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
/**
* Resolves aspect ratio restrictions for an activity. If the bounds are restricted by
- * aspect ratio, the position will be adjusted later in {@link
- * updateResolvedBoundsHorizontalPosition} within parent's app bounds to balance the visual
- * appearance. The policy of aspect ratio has higher priority than the requested override
- * bounds.
+ * aspect ratio, the position will be adjusted later in {@link #updateResolvedBoundsPosition
+ * within parent's app bounds to balance the visual appearance. The policy of aspect ratio has
+ * higher priority than the requested override bounds.
*/
private void resolveAspectRatioRestriction(Configuration newParentConfiguration) {
final Configuration resolvedConfig = getResolvedOverrideConfiguration();
@@ -8141,7 +8244,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
mTmpBounds.setEmpty();
mIsAspectRatioApplied = applyAspectRatio(mTmpBounds, parentAppBounds, parentBounds);
// If the out bounds is not empty, it means the activity cannot fill parent's app bounds,
- // then they should be aligned later in #updateResolvedBoundsHorizontalPosition().
+ // then they should be aligned later in #updateResolvedBoundsPosition()
if (!mTmpBounds.isEmpty()) {
resolvedBounds.set(mTmpBounds);
}
@@ -8275,22 +8378,11 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
forAllWindows(WindowState::updateGlobalScale, false /* traverseTopToBottom */);
}
- // Vertically center within parent (bounds) - this is a UX choice and exclude the horizontal
- // decor if needed. Horizontal position is adjusted in
- // updateResolvedBoundsHorizontalPosition.
+ // The position will be later adjusted in updateResolvedBoundsPosition.
// Above coordinates are in "@" space, now place "*" and "#" to screen space.
final boolean fillContainer = resolvedBounds.equals(containingBounds);
final int screenPosX = fillContainer ? containerBounds.left : containerAppBounds.left;
- // If the activity is not in size compat mode, calculate vertical centering
- // from the container and resolved bounds.
- // If the activity is in size compat mode, calculate vertical centering
- // from the container and size compat bounds.
- // The container bounds contain the parent bounds offset in the display, for
- // example when an activity is in the lower split of split screen.
- final int screenPosY = (mSizeCompatBounds == null
- ? (containerBounds.height() - resolvedBounds.height()) / 2
- : (containerBounds.height() - mSizeCompatBounds.height()) / 2)
- + containerBounds.top;
+ final int screenPosY = fillContainer ? containerBounds.top : containerAppBounds.top;
if (screenPosX != 0 || screenPosY != 0) {
if (mSizeCompatBounds != null) {
@@ -8350,9 +8442,9 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return true;
}
- /** @return The horizontal offset of putting the content in the center of viewport. */
- private static int getHorizontalCenterOffset(int viewportW, int contentW) {
- return (int) ((viewportW - contentW + 1) * 0.5f);
+ /** @return The horizontal / vertical offset of putting the content in the center of viewport.*/
+ private static int getCenterOffset(int viewportDim, int contentDim) {
+ return (int) ((viewportDim - contentDim + 1) * 0.5f);
}
private static void offsetBounds(Configuration inOutConfig, int offsetX, int offsetY) {
@@ -8540,7 +8632,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
Rect containingBounds) {
return applyAspectRatio(outBounds, containingAppBounds, containingBounds,
- 0 /* desiredAspectRatio */, false /* fixedOrientationLetterboxed */);
+ 0 /* desiredAspectRatio */);
}
/**
@@ -8551,23 +8643,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
*/
// TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
private boolean applyAspectRatio(Rect outBounds, Rect containingAppBounds,
- Rect containingBounds, float desiredAspectRatio, boolean fixedOrientationLetterboxed) {
+ Rect containingBounds, float desiredAspectRatio) {
final float maxAspectRatio = info.getMaxAspectRatio();
final Task rootTask = getRootTask();
final float minAspectRatio = getMinAspectRatio();
- // Not using ActivityRecord#isResizeable() directly because app compatibility testing
- // showed that android:supportsPictureInPicture="true" alone is not sufficient signal for
- // not letterboxing an app.
- // TODO(214602463): Remove multi-window check since orientation and aspect ratio
- // restrictions should always be applied in multi-window.
+ final TaskFragment organizedTf = getOrganizedTaskFragment();
if (task == null || rootTask == null
- || (inMultiWindowMode() && isResizeable(/* checkPictureInPictureSupport */ false)
- && !fixedOrientationLetterboxed)
|| (maxAspectRatio < 1 && minAspectRatio < 1 && desiredAspectRatio < 1)
- || isInVrUiMode(getConfiguration())) {
- // We don't enforce aspect ratio if the activity task is in multiwindow unless it is in
- // size-compat mode or is letterboxed from fixed orientation. We also don't set it if we
- // are in VR mode.
+ // Don't set aspect ratio if we are in VR mode.
+ || isInVrUiMode(getConfiguration())
+ // TODO(b/232898850): Always respect aspect ratio requests.
+ // Don't set aspect ratio for activity in ActivityEmbedding split.
+ || (organizedTf != null && !organizedTf.fillsParent())) {
return false;
}
@@ -9615,7 +9702,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
outBounds.bottom = dH;
outBounds.right = (int) ((float) dH * dH / dW);
}
- outBounds.offset(getHorizontalCenterOffset(mWidth, outBounds.width()), 0 /* dy */);
+ outBounds.offset(getCenterOffset(mWidth, outBounds.width()), 0 /* dy */);
}
outAppBounds.set(outBounds);
@@ -9663,6 +9750,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
false /*isNotInRecents*/,
record.mThumbnailAdapter != null ? record.mThumbnailAdapter.mCapturedLeash : null,
record.mStartBounds, task.getTaskInfo(), checkEnterPictureInPictureAppOpsState());
+ target.setShowBackdrop(record.mShowBackdrop);
target.hasAnimatingParent = record.hasAnimatingParent();
return target;
}
@@ -9684,6 +9772,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
void setPictureInPictureParams(PictureInPictureParams p) {
pictureInPictureArgs.copyOnlySet(p);
+ adjustPictureInPictureParamsIfNeeded(getBounds());
getTask().getRootTask().onPictureInPictureParamsChanged();
}
@@ -9695,6 +9784,10 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
@Override
boolean isSyncFinished() {
if (!super.isSyncFinished()) return false;
+ if (mDisplayContent != null && mDisplayContent.mUnknownAppVisibilityController
+ .isVisibilityUnknown(this)) {
+ return false;
+ }
if (!isVisibleRequested()) return true;
// Wait for attach. That is the earliest time where we know if there will be an associated
// display rotation. If we don't wait, the starting-window can finishDrawing first and
@@ -9731,6 +9824,18 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A
return new Point(windowLayout.minWidth, windowLayout.minHeight);
}
+ /**
+ * Adjust the source rect hint in {@link #pictureInPictureArgs} by window bounds since
+ * it is relative to its root view (see also b/235599028).
+ * It is caller's responsibility to make sure this is called exactly once when we update
+ * {@link #pictureInPictureArgs} to avoid double offset.
+ */
+ private void adjustPictureInPictureParamsIfNeeded(Rect windowBounds) {
+ if (pictureInPictureArgs != null && pictureInPictureArgs.hasSourceBoundsHint()) {
+ pictureInPictureArgs.getSourceRectHint().offset(windowBounds.left, windowBounds.top);
+ }
+ }
+
static class Builder {
private final ActivityTaskManagerService mAtmService;
private WindowProcessController mCallerApp;
diff --git a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
index fc22e2decbef..5d038dcab73a 100644
--- a/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
+++ b/services/core/java/com/android/server/wm/ActivityRecordInputSink.java
@@ -19,7 +19,6 @@ package com.android.server.wm;
import android.app.compat.CompatChanges;
import android.compat.annotation.ChangeId;
import android.os.InputConfig;
-import android.os.Process;
import android.view.InputWindowHandle;
import android.view.SurfaceControl;
import android.view.WindowManager;
@@ -82,8 +81,8 @@ class ActivityRecordInputSink {
// Don't block touches from passing through to an activity below us in the same task, if
// that activity is either from the same uid or if that activity has launched an activity
// in our uid.
- final ActivityRecord activityBelowInTask =
- mActivityRecord.getTask().getActivityBelow(mActivityRecord);
+ final ActivityRecord activityBelowInTask = mActivityRecord.getTask() != null
+ ? mActivityRecord.getTask().getActivityBelow(mActivityRecord) : null;
final boolean allowPassthrough = activityBelowInTask != null && (
activityBelowInTask.mAllowedTouchUid == mActivityRecord.getUid()
|| activityBelowInTask.isUid(mActivityRecord.getUid()));
@@ -102,8 +101,8 @@ class ActivityRecordInputSink {
inputWindowHandle.replaceTouchableRegionWithCrop = true;
inputWindowHandle.name = mName;
inputWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
- inputWindowHandle.ownerUid = Process.myUid();
- inputWindowHandle.ownerPid = Process.myPid();
+ inputWindowHandle.ownerPid = WindowManagerService.MY_PID;
+ inputWindowHandle.ownerUid = WindowManagerService.MY_UID;
inputWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.NO_INPUT_CHANNEL;
return inputWindowHandle;
}
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index b0ad5a9e4bb2..77d6097a9c69 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -19,7 +19,9 @@ package com.android.server.wm;
import static android.app.ActivityManager.START_CANCELED;
import static android.app.ActivityManager.START_SUCCESS;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL;
@@ -29,6 +31,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.IApplicationThread;
import android.content.ComponentName;
@@ -46,6 +49,7 @@ import android.provider.Settings;
import android.util.Slog;
import android.util.SparseArray;
import android.view.RemoteAnimationAdapter;
+import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
@@ -90,6 +94,8 @@ public class ActivityStartController {
boolean mCheckedForSetup = false;
+ private boolean mInExecution = false;
+
/**
* TODO(b/64750076): Capture information necessary for dump and
* {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -123,7 +129,15 @@ public class ActivityStartController {
return mFactory.obtain().setIntent(intent).setReason(reason);
}
+ void onExecutionStarted(ActivityStarter starter) {
+ mInExecution = true;
+ }
+
+ boolean isInExecution() {
+ return mInExecution;
+ }
void onExecutionComplete(ActivityStarter starter) {
+ mInExecution = false;
if (mLastStarter == null) {
mLastStarter = mFactory.obtain();
}
@@ -526,6 +540,42 @@ public class ActivityStartController {
.execute();
}
+ boolean startExistingRecentsIfPossible(Intent intent, ActivityOptions options) {
+ final int activityType = mService.getRecentTasks().getRecentsComponent()
+ .equals(intent.getComponent()) ? ACTIVITY_TYPE_RECENTS : ACTIVITY_TYPE_HOME;
+ final Task rootTask = mService.mRootWindowContainer.getDefaultTaskDisplayArea()
+ .getRootTask(WINDOWING_MODE_UNDEFINED, activityType);
+ if (rootTask == null) return false;
+ final ActivityRecord r = rootTask.topRunningActivity();
+ if (r == null || r.mVisibleRequested || !r.attachedToProcess()
+ || !r.mActivityComponent.equals(intent.getComponent())
+ // Recents keeps invisible while device is locked.
+ || r.mDisplayContent.isKeyguardLocked()) {
+ return false;
+ }
+ mService.mRootWindowContainer.startPowerModeLaunchIfNeeded(true /* forceSend */, r);
+ final ActivityMetricsLogger.LaunchingState launchingState =
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunching(intent);
+ final Task task = r.getTask();
+ mService.deferWindowLayout();
+ try {
+ task.mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_TO_FRONT,
+ 0 /* flags */, task, task /* readyGroupRef */,
+ options.getRemoteTransition(), null /* displayChange */);
+ r.mTransitionController.setTransientLaunch(r,
+ TaskDisplayArea.getRootTaskAbove(rootTask));
+ task.moveToFront("startExistingRecents");
+ task.mInResumeTopActivity = true;
+ task.resumeTopActivity(null /* prev */, options, true /* deferPause */);
+ mSupervisor.getActivityMetricsLogger().notifyActivityLaunched(launchingState,
+ ActivityManager.START_TASK_TO_FRONT, false, r, options);
+ } finally {
+ task.mInResumeTopActivity = false;
+ mService.continueWindowLayout();
+ }
+ return true;
+ }
+
void registerRemoteAnimationForNextActivityStart(String packageName,
RemoteAnimationAdapter adapter, @Nullable IBinder launchCookie) {
mPendingRemoteAnimationRegistry.addPendingAnimation(packageName, adapter, launchCookie);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index a452013bf42a..7d84bdf78056 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -49,6 +49,7 @@ import android.content.pm.ResolveInfo;
import android.content.pm.SuspendDialogInfo;
import android.content.pm.UserInfo;
import android.os.Bundle;
+import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -64,7 +65,7 @@ import com.android.server.am.ActivityManagerService;
import com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult;
/**
- * A class that contains activity intercepting logic for {@link ActivityStarter#startActivityLocked}
+ * A class that contains activity intercepting logic for {@link ActivityStarter#execute()}
* It's initialized via setStates and interception occurs via the intercept method.
*
* Note that this class is instantiated when {@link ActivityManagerService} gets created so there
@@ -104,6 +105,7 @@ class ActivityStartInterceptor {
ActivityInfo mAInfo;
String mResolvedType;
Task mInTask;
+ TaskFragment mInTaskFragment;
ActivityOptions mActivityOptions;
ActivityStartInterceptor(
@@ -135,15 +137,46 @@ class ActivityStartInterceptor {
}
private IntentSender createIntentSenderForOriginalIntent(int callingUid, int flags) {
- Bundle activityOptions = deferCrossProfileAppsAnimationIfNecessary();
+ Bundle bOptions = deferCrossProfileAppsAnimationIfNecessary();
+ final TaskFragment taskFragment = getLaunchTaskFragment();
+ // If the original intent is going to be embedded, try to forward the embedding TaskFragment
+ // and its task id to embed back the original intent.
+ if (taskFragment != null) {
+ ActivityOptions activityOptions = bOptions != null
+ ? ActivityOptions.fromBundle(bOptions)
+ : ActivityOptions.makeBasic();
+ activityOptions.setLaunchTaskFragmentToken(taskFragment.getFragmentToken());
+ bOptions = activityOptions.toBundle();
+ }
final IIntentSender target = mService.getIntentSenderLocked(
INTENT_SENDER_ACTIVITY, mCallingPackage, mCallingFeatureId, callingUid, mUserId,
null /*token*/, null /*resultCode*/, 0 /*requestCode*/,
new Intent[] { mIntent }, new String[] { mResolvedType },
- flags, activityOptions);
+ flags, bOptions);
return new IntentSender(target);
}
+
+ /**
+ * A helper function to obtain the targeted {@link TaskFragment} during
+ * {@link #intercept(Intent, ResolveInfo, ActivityInfo, String, Task, TaskFragment, int, int,
+ * ActivityOptions)} if any.
+ */
+ @Nullable
+ private TaskFragment getLaunchTaskFragment() {
+ if (mInTaskFragment != null) {
+ return mInTaskFragment;
+ }
+ if (mActivityOptions == null) {
+ return null;
+ }
+ final IBinder taskFragToken = mActivityOptions.getLaunchTaskFragmentToken();
+ if (taskFragToken == null) {
+ return null;
+ }
+ return TaskFragment.fromTaskFragmentToken(taskFragToken, mService);
+ }
+
/**
* Intercept the launch intent based on various signals. If an interception happened the
* internal variables get assigned and need to be read explicitly by the caller.
@@ -151,7 +184,8 @@ class ActivityStartInterceptor {
* @return true if an interception occurred
*/
boolean intercept(Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType,
- Task inTask, int callingPid, int callingUid, ActivityOptions activityOptions) {
+ Task inTask, TaskFragment inTaskFragment, int callingPid, int callingUid,
+ ActivityOptions activityOptions) {
mUserManager = UserManager.get(mServiceContext);
mIntent = intent;
@@ -161,6 +195,7 @@ class ActivityStartInterceptor {
mAInfo = aInfo;
mResolvedType = resolvedType;
mInTask = inTask;
+ mInTaskFragment = inTaskFragment;
mActivityOptions = activityOptions;
if (interceptQuietProfileIfNeeded()) {
@@ -332,12 +367,21 @@ class ActivityStartInterceptor {
mCallingPid = mRealCallingPid;
mCallingUid = mRealCallingUid;
mResolvedType = null;
+ final TaskFragment taskFragment = getLaunchTaskFragment();
// If we are intercepting and there was a task, convert it into an extra for the
// ConfirmCredentials intent and unassign it, as otherwise the task will move to
// front even if ConfirmCredentials is cancelled.
if (mInTask != null) {
mIntent.putExtra(EXTRA_TASK_ID, mInTask.mTaskId);
mInTask = null;
+ } else if (taskFragment != null) {
+ // If the original intent is started to an embedded TaskFragment, append its parent task
+ // id to extra. It is to embed back the original intent to the TaskFragment with the
+ // same task.
+ final Task parentTask = taskFragment.getTask();
+ if (parentTask != null) {
+ mIntent.putExtra(EXTRA_TASK_ID, parentTask.mTaskId);
+ }
}
if (mActivityOptions == null) {
mActivityOptions = ActivityOptions.makeBasic();
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index ec9babf09ef3..550d21acc53f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -51,10 +51,12 @@ import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TASK;
import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_TOP;
+import static android.content.pm.ActivityInfo.launchModeToString;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.Process.INVALID_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
@@ -216,6 +218,7 @@ class ActivityStarter {
// The task which was above the targetTask before starting this activity. null if the targetTask
// was already on top or if the activity is in a new task.
private Task mPriorAboveTask;
+ private boolean mDisplayLockAndOccluded;
// We must track when we deliver the new intent since multiple code paths invoke
// {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used
@@ -647,6 +650,8 @@ class ActivityStarter {
*/
int execute() {
try {
+ onExecutionStarted();
+
// Refuse possible leaked file descriptors
if (mRequest.intent != null && mRequest.intent.hasFileDescriptors()) {
throw new IllegalArgumentException("File descriptors passed in Intent");
@@ -1056,8 +1061,8 @@ class ActivityStarter {
mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage,
callingFeatureId);
- if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, callingPid,
- callingUid, checkedOptions)) {
+ if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment,
+ callingPid, callingUid, checkedOptions)) {
// activity start was intercepted, e.g. because the target user is currently in quiet
// mode (turn off work) or the target application is suspended
intent = mInterceptor.mIntent;
@@ -1261,6 +1266,10 @@ class ActivityStarter {
mController.onExecutionComplete(this);
}
+ private void onExecutionStarted() {
+ mController.onExecutionStarted(this);
+ }
+
private boolean isHomeApp(int uid, @Nullable String packageName) {
if (mService.mHomeProcess != null) {
// Fast check
@@ -1657,14 +1666,17 @@ class ActivityStarter {
transitionController.collect(r);
try {
mService.deferWindowLayout();
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
- result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
- startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
- intentGrants);
+ try {
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "startActivityInner");
+ result = startActivityInner(r, sourceRecord, voiceSession, voiceInteractor,
+ startFlags, doResume, options, inTask, inTaskFragment, restrictedBgActivity,
+ intentGrants);
+ } finally {
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
+ startedActivityRootTask = handleStartResult(r, options, result, newTransition,
+ remoteTransition);
+ }
} finally {
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- startedActivityRootTask = handleStartResult(r, options, result, newTransition,
- remoteTransition);
mService.continueWindowLayout();
}
postStartActivityProcessing(r, result, startedActivityRootTask);
@@ -1681,6 +1693,7 @@ class ActivityStarter {
private @Nullable Task handleStartResult(@NonNull ActivityRecord started,
ActivityOptions options, int result, Transition newTransition,
RemoteTransition remoteTransition) {
+ final boolean userLeaving = mSupervisor.mUserLeaving;
mSupervisor.mUserLeaving = false;
final Task currentRootTask = started.getRootTask();
final Task startedActivityRootTask =
@@ -1699,7 +1712,8 @@ class ActivityStarter {
// Root task should also be detached from display and be removed if it's empty.
if (startedActivityRootTask != null && startedActivityRootTask.isAttached()
&& !startedActivityRootTask.hasActivity()
- && !startedActivityRootTask.isActivityTypeHome()) {
+ && !startedActivityRootTask.isActivityTypeHome()
+ && !startedActivityRootTask.mCreatedByOrganizer) {
startedActivityRootTask.removeIfPossible("handleStartResult");
}
if (newTransition != null) {
@@ -1740,19 +1754,42 @@ class ActivityStarter {
// Transition housekeeping.
final TransitionController transitionController = started.mTransitionController;
final boolean isStarted = result == START_SUCCESS || result == START_TASK_TO_FRONT;
+ final boolean isTransientLaunch = options != null && options.getTransientLaunch();
+ // Start transient launch while keyguard locked and occluded by other app, for this
+ // condition we would like to play the remote transition without modify any visible state
+ // for the hierarchy in core, so here will force execute this transition.
+ final boolean forceTransientTransition = isTransientLaunch && mPriorAboveTask != null
+ && mDisplayLockAndOccluded;
if (isStarted) {
// The activity is started new rather than just brought forward, so record it as an
// existence change.
transitionController.collectExistenceChange(started);
} else if (result == START_DELIVERED_TO_TOP && newTransition != null) {
// We just delivered to top, so there isn't an actual transition here.
- newTransition.abort();
- newTransition = null;
+ if (!forceTransientTransition) {
+ newTransition.abort();
+ newTransition = null;
+ }
}
- if (options != null && options.getTransientLaunch()) {
+ if (isTransientLaunch) {
+ if (forceTransientTransition && newTransition != null) {
+ newTransition.collect(mLastStartActivityRecord);
+ newTransition.collect(mPriorAboveTask);
+ }
// `started` isn't guaranteed to be the actual relevant activity, so we must wait
// until after we launched to identify the relevant activity.
transitionController.setTransientLaunch(mLastStartActivityRecord, mPriorAboveTask);
+ if (forceTransientTransition && newTransition != null) {
+ final DisplayContent dc = mLastStartActivityRecord.getDisplayContent();
+ // update wallpaper target to TransientHide
+ dc.mWallpaperController.adjustWallpaperWindows();
+ // execute transition because there is no change
+ newTransition.setReady(dc, true /* ready */);
+ }
+ }
+ if (!userLeaving) {
+ // no-user-leaving implies not entering PiP.
+ transitionController.setCanPipOnFinish(false /* canPipOnFinish */);
}
if (newTransition != null) {
transitionController.requestStartTransition(newTransition,
@@ -2389,6 +2426,7 @@ class ActivityStarter {
mAvoidMoveToFront = false;
mFrozeTaskList = false;
mTransientLaunch = false;
+ mDisplayLockAndOccluded = false;
mVoiceSession = null;
mVoiceInteractor = null;
@@ -2492,6 +2530,16 @@ class ActivityStarter {
mAvoidMoveToFront = true;
}
mTransientLaunch = mOptions.getTransientLaunch();
+ final KeyguardController kc = mSupervisor.getKeyguardController();
+ final int displayId = mPreferredTaskDisplayArea.getDisplayId();
+ mDisplayLockAndOccluded = kc.isKeyguardLocked(displayId)
+ && kc.isDisplayOccluded(displayId);
+ // Recents animation on lock screen, do not resume & move launcher to top.
+ if (mTransientLaunch && mDisplayLockAndOccluded
+ && mService.getTransitionController().isShellTransitionsEnabled()) {
+ mDoResume = false;
+ mAvoidMoveToFront = true;
+ }
mTargetRootTask = Task.fromWindowContainerToken(mOptions.getLaunchRootTask());
if (inTaskFragment == null) {
@@ -2640,6 +2688,12 @@ class ActivityStarter {
mLaunchFlags |= FLAG_ACTIVITY_NEW_TASK;
}
}
+
+ if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0
+ && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) {
+ // ignore the flag if there is no the sourceRecord or without new_task flag
+ mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT;
+ }
}
private void computeSourceRootTask() {
@@ -2759,17 +2813,15 @@ class ActivityStarter {
mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask,
mOptions);
}
- } else {
- // If a launch target indicated, and the matching task is already in the adjacent task
- // of the launch target. Adjust to use the adjacent task as its launch target. So the
- // existing task will be launched into the closer one and won't be reparent redundantly.
- // TODO(b/231541706): Migrate the logic to wm-shell after having proper APIs to help
- // resolve target task without actually starting the activity.
- final Task adjacentTargetTask = mTargetRootTask.getAdjacentTaskFragment() != null
- ? mTargetRootTask.getAdjacentTaskFragment().asTask() : null;
- if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)) {
- mTargetRootTask = adjacentTargetTask;
- }
+ }
+
+ // If the matching task is already in the adjacent task of the launch target. Adjust to use
+ // the adjacent task as its launch target. So the existing task will be launched into the
+ // closer one and won't be reparent redundantly.
+ final Task adjacentTargetTask = mTargetRootTask.getAdjacentTaskFragment() != null
+ ? mTargetRootTask.getAdjacentTaskFragment().asTask() : null;
+ if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)) {
+ mTargetRootTask = adjacentTargetTask;
}
// If the target task is not in the front, then we need to bring it to the front...
@@ -2922,6 +2974,10 @@ class ActivityStarter {
newParent = candidateTf;
}
}
+ if (newParent.canHaveEmbeddingActivityTransition(mStartActivity)) {
+ // Make sure the embedded TaskFragment is included in the start activity transition.
+ newParent.collectEmbeddedTaskFragmentIfNeeded();
+ }
if (mStartActivity.getTaskFragment() == null
|| mStartActivity.getTaskFragment() == newParent) {
newParent.addChild(mStartActivity, POSITION_TOP);
@@ -2940,7 +2996,7 @@ class ActivityStarter {
switch(result) {
case EMBEDDING_DISALLOWED_NEW_TASK: {
errMsg = "Cannot embed " + mStartActivity + " that launched on another task"
- + ",mLaunchMode=" + mLaunchMode
+ + ",mLaunchMode=" + launchModeToString(mLaunchMode)
+ ",mLaunchFlag=" + Integer.toHexString(mLaunchFlags);
break;
}
@@ -2960,6 +3016,7 @@ class ActivityStarter {
if (taskFragment.isOrganized()) {
mService.mWindowOrganizerController.sendTaskFragmentOperationFailure(
taskFragment.getTaskFragmentOrganizer(), mRequest.errorCallbackToken,
+ taskFragment, HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT,
new SecurityException(errMsg));
} else {
// If the taskFragment is not organized, just dump error message as warning logs.
@@ -3270,12 +3327,8 @@ class ActivityStarter {
pw.println(mOptions);
}
pw.print(prefix);
- pw.print("mLaunchSingleTop=");
- pw.print(LAUNCH_SINGLE_TOP == mLaunchMode);
- pw.print(" mLaunchSingleInstance=");
- pw.print(LAUNCH_SINGLE_INSTANCE == mLaunchMode);
- pw.print(" mLaunchSingleTask=");
- pw.println(LAUNCH_SINGLE_TASK == mLaunchMode);
+ pw.print("mLaunchMode=");
+ pw.print(launchModeToString(mLaunchMode));
pw.print(prefix);
pw.print("mLaunchFlags=0x");
pw.print(Integer.toHexString(mLaunchFlags));
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 8e7dde20955b..e7b62b0d66d3 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -675,9 +675,6 @@ public abstract class ActivityTaskManagerInternal {
public abstract boolean hasSystemAlertWindowPermission(int callingUid, int callingPid,
String callingPackage);
- /** Called when the device is waking up */
- public abstract void notifyWakingUp();
-
/**
* Registers a callback which can intercept activity starts.
* @throws IllegalArgumentException if duplicate ids are provided
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index aa154292fe7e..9d8e0877584f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -64,8 +64,8 @@ import static android.provider.Settings.Global.HIDE_ERROR_DIALOGS;
import static android.provider.Settings.System.FONT_SCALE;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT;
-import static android.view.WindowManager.TRANSIT_WAKE;
+import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -73,7 +73,6 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_IMMERSIVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
import static com.android.server.am.ActivityManagerService.ANR_TRACE_DIR;
-import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.am.ActivityManagerService.STOCK_PM_FLAGS;
import static com.android.server.am.ActivityManagerService.dumpStackTraces;
import static com.android.server.am.ActivityManagerServiceDumpActivitiesProto.ROOT_WINDOW_CONTAINER;
@@ -95,6 +94,7 @@ import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen;
import static com.android.server.am.EventLogTags.writeConfigurationChanged;
import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID;
import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH;
@@ -116,6 +116,7 @@ import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_O
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_ONLY;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
+import static com.android.server.wm.WindowManagerService.MY_PID;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import android.Manifest;
@@ -238,6 +239,7 @@ import com.android.internal.notification.SystemNotificationChannels;
import com.android.internal.os.TransferPipe;
import com.android.internal.policy.AttributeCache;
import com.android.internal.policy.KeyguardDismissCallback;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.FastPrintWriter;
@@ -400,6 +402,26 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/** The time at which the previous process was last visible. */
private long mPreviousProcessVisibleTime;
+ /** It is set from keyguard-going-away to set-keyguard-shown. */
+ static final int DEMOTE_TOP_REASON_DURING_UNLOCKING = 1;
+ /** It is set if legacy recents animation is running. */
+ static final int DEMOTE_TOP_REASON_ANIMATING_RECENTS = 1 << 1;
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({
+ DEMOTE_TOP_REASON_DURING_UNLOCKING,
+ DEMOTE_TOP_REASON_ANIMATING_RECENTS,
+ })
+ @interface DemoteTopReason {}
+
+ /**
+ * If non-zero, getTopProcessState() will
+ * return {@link ActivityManager#PROCESS_STATE_IMPORTANT_FOREGROUND} to avoid top app from
+ * preempting CPU while another process is running an important animation.
+ */
+ @DemoteTopReason
+ volatile int mDemoteTopAppReasons;
+
/** List of intents that were used to start the most recent tasks. */
private RecentTasks mRecentTasks;
/** State of external calls telling us if the device is awake or asleep. */
@@ -1209,6 +1231,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Nullable String callingFeatureId, Intent intent, String resolvedType,
IBinder resultTo, String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
+
+ final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
+ // A quick path (skip general intent/task resolving) to start recents animation if the
+ // recents (or home) activity is available in background.
+ if (opts != null && opts.getOriginalOptions().getTransientLaunch()
+ && isCallerRecents(Binder.getCallingUid())) {
+ final long origId = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "startExistingRecents");
+ if (mActivityStartController.startExistingRecentsIfPossible(
+ intent, opts.getOriginalOptions())) {
+ return ActivityManager.START_TASK_TO_FRONT;
+ }
+ // Else follow the standard launch procedure.
+ }
+ } finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ Binder.restoreCallingIdentity(origId);
+ }
+ }
+
assertPackageMatchesCallingUid(callingPackage);
enforceNotIsolatedCaller("startActivityAsUser");
if (Process.isSdkSandboxUid(Binder.getCallingUid())) {
@@ -1235,7 +1279,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
.setRequestCode(requestCode)
.setStartFlags(startFlags)
.setProfilerInfo(profilerInfo)
- .setActivityOptions(bOptions)
+ .setActivityOptions(opts)
.setUserId(userId)
.execute();
@@ -2157,14 +2201,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
: null;
mTaskSupervisor.findTaskToMoveToFront(task, flags, realOptions, "moveTaskToFront",
false /* forceNonResizable */);
-
- final ActivityRecord topActivity = task.getTopNonFinishingActivity();
- if (topActivity != null) {
-
- // We are reshowing a task, use a starting window to hide the initial draw delay
- // so the transition can start earlier.
- topActivity.showStartingWindow(true /* taskSwitch */);
- }
} finally {
Binder.restoreCallingIdentity(origId);
}
@@ -2830,12 +2866,24 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
keyguardShowing);
mH.sendMessage(msg);
}
+ // Always reset the state regardless of keyguard-showing change, because that means the
+ // unlock is either completed or canceled.
+ if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) {
+ mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
+ // The scheduling group of top process was demoted by unlocking, so recompute
+ // to restore its real top priority if possible.
+ if (mTopApp != null) {
+ mTopApp.scheduleUpdateOomAdj();
+ }
+ }
try {
+ Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown");
mRootWindowContainer.forAllDisplays(displayContent -> {
mKeyguardController.setKeyguardShown(displayContent.getDisplayId(),
keyguardShowing, aodShowing);
});
} finally {
+ Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
Binder.restoreCallingIdentity(ident);
}
}
@@ -2862,6 +2910,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
// animation of system UI. Even if AOD is not enabled, it should be no harm.
final WindowProcessController proc;
synchronized (mGlobalLockWithoutBoost) {
+ mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING;
final WindowState notificationShade = mRootWindowContainer.getDefaultDisplay()
.getDisplayPolicy().getNotificationShade();
proc = notificationShade != null
@@ -3399,8 +3448,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
try {
synchronized (mGlobalLock) {
// Keyguard asked us to clear the home task snapshot before going away, so do that.
- if ((flags & TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
+ if ((flags & KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT) != 0) {
mActivityClientController.invalidateHomeTaskSnapshot(null /* token */);
+ } else if (mKeyguardShown) {
+ // Only set if it is not unlocking to launcher which may also animate.
+ mDemoteTopAppReasons |= DEMOTE_TOP_REASON_DURING_UNLOCKING;
}
mRootWindowContainer.forAllDisplays(displayContent -> {
@@ -3448,10 +3500,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
/**
* Puts the given activity in picture in picture mode if possible.
*
+ * @param fromClient true if this comes from a client call (eg. Activity.enterPip).
* @return true if the activity is now in picture-in-picture mode, or false if it could not
* enter picture-in-picture mode.
*/
- boolean enterPictureInPictureMode(@NonNull ActivityRecord r, PictureInPictureParams params) {
+ boolean enterPictureInPictureMode(@NonNull ActivityRecord r,
+ @NonNull PictureInPictureParams params, boolean fromClient) {
// If the activity is already in picture in picture mode, then just return early
if (r.inPinnedWindowingMode()) {
return true;
@@ -3464,6 +3518,21 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
return false;
}
+ // If the app is using legacy-entry (not auto-enter), then we will get a client-request
+ // that was actually a server-request (via pause(userLeaving=true)). This happens when
+ // the app is PAUSING, so detect that case here.
+ boolean originallyFromClient = fromClient
+ && (!r.isState(PAUSING) || params.isAutoEnterEnabled());
+
+ // Create a transition only for this pip entry if it is coming from the app without the
+ // system requesting that the app enter-pip. If the system requested it, that means it
+ // should be part of that transition if possible.
+ final Transition transition =
+ (getTransitionController().isShellTransitionsEnabled() && originallyFromClient)
+ ? new Transition(TRANSIT_PIP, 0 /* flags */,
+ getTransitionController(), mWindowManager.mSyncEngine)
+ : null;
+
final Runnable enterPipRunnable = () -> {
synchronized (mGlobalLock) {
if (r.getParent() == null) {
@@ -3472,11 +3541,11 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
r.setPictureInPictureParams(params);
mRootWindowContainer.moveActivityToPinnedRootTask(r,
- null /* launchIntoPipHostActivity */, "enterPictureInPictureMode");
- final Task task = r.getTask();
+ null /* launchIntoPipHostActivity */, "enterPictureInPictureMode",
+ transition);
// Continue the pausing process after entering pip.
- if (task.getPausingActivity() == r) {
- task.schedulePauseActivity(r, false /* userLeaving */,
+ if (r.isState(PAUSING)) {
+ r.getTask().schedulePauseActivity(r, false /* userLeaving */,
false /* pauseImmediately */, "auto-pip");
}
}
@@ -3489,12 +3558,36 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mActivityClientController.dismissKeyguard(r.token, new KeyguardDismissCallback() {
@Override
public void onDismissSucceeded() {
- mH.post(enterPipRunnable);
+ if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Creating Pending Pip-Enter: %s", transition);
+ mWindowManager.mSyncEngine.queueSyncSet(
+ () -> getTransitionController().moveToCollecting(transition),
+ enterPipRunnable);
+ } else {
+ // Move to collecting immediately to "claim" the sync-engine for this
+ // transition.
+ if (transition != null) {
+ getTransitionController().moveToCollecting(transition);
+ }
+ mH.post(enterPipRunnable);
+ }
}
}, null /* message */);
} else {
// Enter picture in picture immediately otherwise
- enterPipRunnable.run();
+ if (transition != null && mWindowManager.mSyncEngine.hasActiveSync()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Creating Pending Pip-Enter: %s", transition);
+ mWindowManager.mSyncEngine.queueSyncSet(
+ () -> getTransitionController().moveToCollecting(transition),
+ enterPipRunnable);
+ } else {
+ if (transition != null) {
+ getTransitionController().moveToCollecting(transition);
+ }
+ enterPipRunnable.run();
+ }
}
return true;
}
@@ -3588,7 +3681,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) {
+ public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution,
+ boolean takeSnapshotIfNeeded) {
mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()");
final long ident = Binder.clearCallingIdentity();
try {
@@ -3602,8 +3696,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
}
// Don't call this while holding the lock as this operation might hit the disk.
- return mWindowManager.mTaskSnapshotController.getSnapshot(taskId, task.mUserId,
- true /* restoreFromDisk */, isLowResolution);
+ TaskSnapshot taskSnapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId,
+ task.mUserId, true /* restoreFromDisk */, isLowResolution);
+ if (taskSnapshot == null && takeSnapshotIfNeeded) {
+ taskSnapshot = takeTaskSnapshot(taskId);
+ }
+ return taskSnapshot;
} finally {
Binder.restoreCallingIdentity(ident);
}
@@ -3968,6 +4066,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mTaskOrganizerController.dump(pw, " ");
mVisibleActivityProcessTracker.dump(pw, " ");
mActiveUids.dump(pw, " ");
+ if (mDemoteTopAppReasons != 0) {
+ pw.println(" mDemoteTopAppReasons=" + mDemoteTopAppReasons);
+ }
}
if (!printedAnything) {
@@ -5595,12 +5696,17 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@HotPath(caller = HotPath.OOM_ADJUSTMENT)
@Override
public int getTopProcessState() {
+ final int topState = mTopProcessState;
+ if (mDemoteTopAppReasons != 0 && topState == ActivityManager.PROCESS_STATE_TOP) {
+ // There may be a more important UI/animation than the top app.
+ return ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
+ }
if (mRetainPowerModeAndTopProcessState) {
// There is a launching app while device may be sleeping, force the top state so
// the launching process can have top-app scheduling group.
return ActivityManager.PROCESS_STATE_TOP;
}
- return mTopProcessState;
+ return topState;
}
@HotPath(caller = HotPath.PROCESS_CHANGE)
@@ -6568,7 +6674,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
@Override
public TaskSnapshot getTaskSnapshotBlocking(
int taskId, boolean isLowResolution) {
- return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution);
+ return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution,
+ false /* takeSnapshotIfNeeded */);
}
@Override
@@ -6626,15 +6733,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
- public void notifyWakingUp() {
- synchronized (mGlobalLock) {
- // Start a transition for waking. This is needed for showWhenLocked activities.
- getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */,
- null /* trigger */, mRootWindowContainer.getDefaultDisplay());
- }
- }
-
- @Override
public void registerActivityStartInterceptor(
@ActivityInterceptorCallback.OrderedId int id,
ActivityInterceptorCallback callback) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 35f977de8290..84740683f6aa 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -42,6 +42,7 @@ import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
import static android.os.Process.INVALID_UID;
+import static android.os.Process.SYSTEM_UID;
import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.TRANSIT_TO_FRONT;
@@ -73,13 +74,9 @@ import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE
import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS;
import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS_AND_RESTORE;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.REPARENT_KEEP_ROOT_TASK_AT_FRONT;
import static com.android.server.wm.Task.TAG_CLEANUP;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.Manifest;
@@ -93,6 +90,7 @@ import android.app.AppOpsManagerInternal;
import android.app.IActivityClientController;
import android.app.ProfilerInfo;
import android.app.ResultInfo;
+import android.app.TaskInfo;
import android.app.WaitResult;
import android.app.servertransaction.ActivityLifecycleItem;
import android.app.servertransaction.ClientTransaction;
@@ -121,7 +119,6 @@ import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.Trace;
@@ -155,6 +152,7 @@ import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
+import java.util.function.Consumer;
// TODO: This class has become a dumping ground. Let's
// - Move things relating to the hierarchy to RootWindowContainer
@@ -246,6 +244,9 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
/** Helper class to abstract out logic for fetching the set of currently running tasks */
private RunningTasks mRunningTasks;
+ /** Helper for {@link Task#fillTaskInfo}. */
+ final TaskInfoHelper mTaskInfoHelper = new TaskInfoHelper();
+
private final ActivityTaskSupervisorHandler mHandler;
final Looper mLooper;
@@ -1316,7 +1317,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
void acquireLaunchWakelock() {
- if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
+ if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != SYSTEM_UID) {
throw new IllegalStateException("Calling must be system uid");
}
mLaunchingActivityWakeLock.acquire();
@@ -1389,8 +1390,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (mLaunchingActivityWakeLock.isHeld()) {
mHandler.removeMessages(LAUNCH_TIMEOUT_MSG);
- if (VALIDATE_WAKE_LOCK_CALLER &&
- Binder.getCallingUid() != Process.myUid()) {
+ if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != SYSTEM_UID) {
throw new IllegalStateException("Calling must be system uid");
}
mLaunchingActivityWakeLock.release();
@@ -1434,10 +1434,10 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
mUserLeaving = true;
}
- task.mTransitionController.requestTransitionIfNeeded(TRANSIT_TO_FRONT,
- 0 /* flags */, task, task /* readyGroupRef */,
- options != null ? options.getRemoteTransition() : null,
- null /* displayChange */);
+ final Transition newTransition = task.mTransitionController.isShellTransitionsEnabled()
+ ? task.mTransitionController.isCollecting() ? null
+ : task.mTransitionController.createTransition(TRANSIT_TO_FRONT) : null;
+ task.mTransitionController.collect(task);
reason = reason + " findTaskToMoveToFront";
boolean reparented = false;
if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
@@ -1480,6 +1480,17 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
mRootWindowContainer.getDefaultTaskDisplayArea(), currentRootTask,
forceNonResizeable);
+ if (r != null) {
+ // Use a starting window to reduce the transition latency for reshowing the task.
+ // Note that with shell transition, this should be executed before requesting
+ // transition to avoid delaying the starting window.
+ r.showStartingWindow(true /* taskSwitch */);
+ }
+ if (newTransition != null) {
+ task.mTransitionController.requestStartTransition(newTransition, task,
+ options != null ? options.getRemoteTransition() : null,
+ null /* displayChange */);
+ }
} finally {
mUserLeaving = false;
}
@@ -1800,7 +1811,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
if (!mGoingToSleepWakeLock.isHeld()) {
mGoingToSleepWakeLock.acquire();
if (mLaunchingActivityWakeLock.isHeld()) {
- if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) {
+ if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != SYSTEM_UID) {
throw new IllegalStateException("Calling must be system uid");
}
mLaunchingActivityWakeLock.release();
@@ -1921,9 +1932,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
ArrayList<ActivityRecord> readyToStopActivities = null;
for (int i = mStoppingActivities.size() - 1; i >= 0; --i) {
final ActivityRecord s = mStoppingActivities.get(i);
- final boolean animating = s.isAnimating(TRANSITION | PARENTS,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS)
- || s.inTransition();
+ final boolean animating = s.isInTransition();
ProtoLog.v(WM_DEBUG_STATES, "Stopping %s: nowVisible=%b animating=%b "
+ "finishing=%s", s, s.nowVisible, animating, s.finishing);
if (!animating || mService.mShuttingDown) {
@@ -2461,8 +2470,7 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
case LAUNCH_TIMEOUT_MSG: {
if (mLaunchingActivityWakeLock.isHeld()) {
Slog.w(TAG, "Launch timeout has expired, giving up wake lock!");
- if (VALIDATE_WAKE_LOCK_CALLER
- && Binder.getCallingUid() != Process.myUid()) {
+ if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != SYSTEM_UID) {
throw new IllegalStateException("Calling must be system uid");
}
mLaunchingActivityWakeLock.release();
@@ -2623,6 +2631,41 @@ public class ActivityTaskSupervisor implements RecentTasks.Callbacks {
}
/**
+ * Fills the info that needs to iterate all activities of task, such as the number of
+ * non-finishing activities and collecting launch cookies.
+ */
+ static class TaskInfoHelper implements Consumer<ActivityRecord> {
+ private TaskInfo mInfo;
+ private ActivityRecord mTopRunning;
+
+ ActivityRecord fillAndReturnTop(Task task, TaskInfo info) {
+ info.numActivities = 0;
+ info.baseActivity = null;
+ mInfo = info;
+ task.forAllActivities(this);
+ final ActivityRecord top = mTopRunning;
+ mTopRunning = null;
+ mInfo = null;
+ return top;
+ }
+
+ @Override
+ public void accept(ActivityRecord r) {
+ if (r.finishing) {
+ return;
+ }
+ if (r.mLaunchCookie != null) {
+ mInfo.addLaunchCookie(r.mLaunchCookie);
+ }
+ mInfo.numActivities++;
+ mInfo.baseActivity = r.mActivityComponent;
+ if (mTopRunning == null) {
+ mTopRunning = r;
+ }
+ }
+ }
+
+ /**
* Internal container to store a match qualifier alongside a WaitResult.
*/
private static class WaitInfo {
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index 6befefda8a12..b23f50154257 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -256,7 +256,7 @@ class AnrController {
Slog.i(TAG_WM, "Pre-dump for unresponsive");
final ArrayList<Integer> firstPids = new ArrayList<>(1);
- firstPids.add(ActivityManagerService.MY_PID);
+ firstPids.add(WindowManagerService.MY_PID);
ArrayList<Integer> nativePids = null;
final int[] pids = shouldDumpSf[0]
? Process.getPidsForCommands(new String[] { "/system/bin/surfaceflinger" })
diff --git a/services/core/java/com/android/server/wm/AppTransition.java b/services/core/java/com/android/server/wm/AppTransition.java
index 5410dd8508f1..53f2c7146656 100644
--- a/services/core/java/com/android/server/wm/AppTransition.java
+++ b/services/core/java/com/android/server/wm/AppTransition.java
@@ -34,6 +34,8 @@ import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
@@ -64,6 +66,9 @@ import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnte
import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
import static com.android.internal.R.styleable.WindowAnimation_activityOpenExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_dreamActivityCloseExitAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenEnterAnimation;
+import static com.android.internal.R.styleable.WindowAnimation_dreamActivityOpenExitAnimation;
import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindSourceAnimation;
import static com.android.internal.R.styleable.WindowAnimation_launchTaskBehindTargetAnimation;
import static com.android.internal.R.styleable.WindowAnimation_taskCloseEnterAnimation;
@@ -309,6 +314,10 @@ public class AppTransition implements Dump {
setAppTransitionState(APP_STATE_TIMEOUT);
}
+ @ColorInt int getNextAppTransitionBackgroundColor() {
+ return mNextAppTransitionBackgroundColor;
+ }
+
HardwareBuffer getAppTransitionThumbnailHeader(WindowContainer container) {
AppTransitionAnimationSpec spec = mNextAppTransitionAnimationsSpecs.get(
container.hashCode());
@@ -518,6 +527,95 @@ public class AppTransition implements Dump {
return TransitionAnimation.loadAnimationSafely(context, resId, TAG);
}
+ static int mapOpenCloseTransitTypes(int transit, boolean enter) {
+ int animAttr = 0;
+ switch (transit) {
+ case TRANSIT_OLD_ACTIVITY_OPEN:
+ case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN:
+ animAttr = enter
+ ? WindowAnimation_activityOpenEnterAnimation
+ : WindowAnimation_activityOpenExitAnimation;
+ break;
+ case TRANSIT_OLD_ACTIVITY_CLOSE:
+ case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_activityCloseEnterAnimation
+ : WindowAnimation_activityCloseExitAnimation;
+ break;
+ case TRANSIT_OLD_TASK_OPEN:
+ animAttr = enter
+ ? WindowAnimation_taskOpenEnterAnimation
+ : WindowAnimation_taskOpenExitAnimation;
+ break;
+ case TRANSIT_OLD_TASK_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_taskCloseEnterAnimation
+ : WindowAnimation_taskCloseExitAnimation;
+ break;
+ case TRANSIT_OLD_TASK_TO_FRONT:
+ animAttr = enter
+ ? WindowAnimation_taskToFrontEnterAnimation
+ : WindowAnimation_taskToFrontExitAnimation;
+ break;
+ case TRANSIT_OLD_TASK_TO_BACK:
+ animAttr = enter
+ ? WindowAnimation_taskToBackEnterAnimation
+ : WindowAnimation_taskToBackExitAnimation;
+ break;
+ case TRANSIT_OLD_WALLPAPER_OPEN:
+ animAttr = enter
+ ? WindowAnimation_wallpaperOpenEnterAnimation
+ : WindowAnimation_wallpaperOpenExitAnimation;
+ break;
+ case TRANSIT_OLD_WALLPAPER_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_wallpaperCloseEnterAnimation
+ : WindowAnimation_wallpaperCloseExitAnimation;
+ break;
+ case TRANSIT_OLD_WALLPAPER_INTRA_OPEN:
+ animAttr = enter
+ ? WindowAnimation_wallpaperIntraOpenEnterAnimation
+ : WindowAnimation_wallpaperIntraOpenExitAnimation;
+ break;
+ case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_wallpaperIntraCloseEnterAnimation
+ : WindowAnimation_wallpaperIntraCloseExitAnimation;
+ break;
+ case TRANSIT_OLD_TASK_OPEN_BEHIND:
+ animAttr = enter
+ ? WindowAnimation_launchTaskBehindSourceAnimation
+ : WindowAnimation_launchTaskBehindTargetAnimation;
+ break;
+ // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
+ // need new TaskFragment transition.
+ case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
+ animAttr = enter
+ ? WindowAnimation_activityOpenEnterAnimation
+ : WindowAnimation_activityOpenExitAnimation;
+ break;
+ // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
+ // need new TaskFragment transition.
+ case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
+ animAttr = enter
+ ? WindowAnimation_activityCloseEnterAnimation
+ : WindowAnimation_activityCloseExitAnimation;
+ break;
+ case TRANSIT_OLD_DREAM_ACTIVITY_OPEN:
+ animAttr = enter
+ ? WindowAnimation_dreamActivityOpenEnterAnimation
+ : WindowAnimation_dreamActivityOpenExitAnimation;
+ break;
+ case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE:
+ animAttr = enter
+ ? 0
+ : WindowAnimation_dreamActivityCloseExitAnimation;
+ break;
+ }
+
+ return animAttr;
+ }
+
@Nullable
Animation loadAnimationAttr(LayoutParams lp, int animAttr, int transit) {
return mTransitionAnimation.loadAnimationAttr(lp, animAttr, transit);
@@ -645,9 +743,15 @@ public class AppTransition implements Dump {
@Nullable Rect surfaceInsets, @Nullable Rect stableInsets, boolean isVoiceInteraction,
boolean freeform, WindowContainer container) {
- if (mNextAppTransitionOverrideRequested
- && (container.canCustomizeAppTransition() || mOverrideTaskTransition)) {
- mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+ final boolean canCustomizeAppTransition = container.canCustomizeAppTransition();
+
+ if (mNextAppTransitionOverrideRequested) {
+ if (canCustomizeAppTransition || mOverrideTaskTransition) {
+ mNextAppTransitionType = NEXT_TRANSIT_TYPE_CUSTOM;
+ } else {
+ ProtoLog.e(WM_DEBUG_APP_TRANSITIONS_ANIM, "applyAnimation: "
+ + " override requested, but it is prohibited by policy.");
+ }
}
Animation a;
@@ -762,87 +866,16 @@ public class AppTransition implements Dump {
"applyAnimation: anim=%s transit=%s isEntrance=%b Callers=%s",
a, appTransitionOldToString(transit), enter, Debug.getCallers(3));
} else {
- int animAttr = 0;
- switch (transit) {
- case TRANSIT_OLD_ACTIVITY_OPEN:
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_OPEN:
- animAttr = enter
- ? WindowAnimation_activityOpenEnterAnimation
- : WindowAnimation_activityOpenExitAnimation;
- break;
- case TRANSIT_OLD_ACTIVITY_CLOSE:
- case TRANSIT_OLD_TRANSLUCENT_ACTIVITY_CLOSE:
- animAttr = enter
- ? WindowAnimation_activityCloseEnterAnimation
- : WindowAnimation_activityCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_OPEN:
- animAttr = enter
- ? WindowAnimation_taskOpenEnterAnimation
- : WindowAnimation_taskOpenExitAnimation;
- break;
- case TRANSIT_OLD_TASK_CLOSE:
- animAttr = enter
- ? WindowAnimation_taskCloseEnterAnimation
- : WindowAnimation_taskCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_TO_FRONT:
- animAttr = enter
- ? WindowAnimation_taskToFrontEnterAnimation
- : WindowAnimation_taskToFrontExitAnimation;
- break;
- case TRANSIT_OLD_TASK_TO_BACK:
- animAttr = enter
- ? WindowAnimation_taskToBackEnterAnimation
- : WindowAnimation_taskToBackExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_OPEN:
- animAttr = enter
- ? WindowAnimation_wallpaperOpenEnterAnimation
- : WindowAnimation_wallpaperOpenExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_CLOSE:
- animAttr = enter
- ? WindowAnimation_wallpaperCloseEnterAnimation
- : WindowAnimation_wallpaperCloseExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_INTRA_OPEN:
- animAttr = enter
- ? WindowAnimation_wallpaperIntraOpenEnterAnimation
- : WindowAnimation_wallpaperIntraOpenExitAnimation;
- break;
- case TRANSIT_OLD_WALLPAPER_INTRA_CLOSE:
- animAttr = enter
- ? WindowAnimation_wallpaperIntraCloseEnterAnimation
- : WindowAnimation_wallpaperIntraCloseExitAnimation;
- break;
- case TRANSIT_OLD_TASK_OPEN_BEHIND:
- animAttr = enter
- ? WindowAnimation_launchTaskBehindSourceAnimation
- : WindowAnimation_launchTaskBehindTargetAnimation;
- break;
- // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
- // need new TaskFragment transition.
- case TRANSIT_OLD_TASK_FRAGMENT_OPEN:
- animAttr = enter
- ? WindowAnimation_activityOpenEnterAnimation
- : WindowAnimation_activityOpenExitAnimation;
- break;
- // TODO(b/189386466): Use activity transition as the fallback. Investigate if we
- // need new TaskFragment transition.
- case TRANSIT_OLD_TASK_FRAGMENT_CLOSE:
- animAttr = enter
- ? WindowAnimation_activityCloseEnterAnimation
- : WindowAnimation_activityCloseExitAnimation;
- break;
- }
- a = animAttr != 0 ? loadAnimationAttr(lp, animAttr, transit) : null;
+ int animAttr = mapOpenCloseTransitTypes(transit, enter);
+ a = animAttr == 0 ? null : (canCustomizeAppTransition
+ ? loadAnimationAttr(lp, animAttr, transit)
+ : mTransitionAnimation.loadDefaultAnimationAttr(animAttr, transit));
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS_ANIM,
"applyAnimation: anim=%s animAttr=0x%x transit=%s isEntrance=%b "
- + "Callers=%s",
+ + " canCustomizeAppTransition=%b Callers=%s",
a, animAttr, appTransitionOldToString(transit), enter,
- Debug.getCallers(3));
+ canCustomizeAppTransition, Debug.getCallers(3));
}
setAppTransitionFinishedCallbackIfNeeded(a);
@@ -988,18 +1021,19 @@ public class AppTransition implements Dump {
}
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
- overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */);
+ overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */,
+ false /* isActivityEmbedding*/);
}
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
- boolean sync) {
+ boolean sync, boolean isActivityEmbedding) {
ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
isTransitionSet(), remoteAnimationAdapter);
if (isTransitionSet() && !mNextAppTransitionIsSync) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
- remoteAnimationAdapter, mHandler);
+ remoteAnimationAdapter, mHandler, isActivityEmbedding);
mNextAppTransitionIsSync = sync;
}
}
@@ -1157,6 +1191,12 @@ public class AppTransition implements Dump {
case TRANSIT_OLD_TASK_FRAGMENT_CHANGE: {
return "TRANSIT_OLD_TASK_FRAGMENT_CHANGE";
}
+ case TRANSIT_OLD_DREAM_ACTIVITY_OPEN: {
+ return "TRANSIT_OLD_DREAM_ACTIVITY_OPEN";
+ }
+ case TRANSIT_OLD_DREAM_ACTIVITY_CLOSE: {
+ return "TRANSIT_OLD_DREAM_ACTIVITY_CLOSE";
+ }
default: {
return "<UNKNOWN: " + transition + ">";
}
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index e60ea1260868..fb9d7e602210 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
@@ -32,6 +33,8 @@ import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_RELAUNCH;
import static android.view.WindowManager.TRANSIT_OLD_CRASHING_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_OCCLUDE;
@@ -339,6 +342,9 @@ public class AppTransitionController {
ArraySet<WindowContainer> changingContainers, @Nullable WindowState wallpaperTarget,
@Nullable WindowState oldWallpaper, boolean skipAppTransitionAnimation) {
+ final ActivityRecord topOpeningApp = getTopApp(openingApps, false /* ignoreHidden */);
+ final ActivityRecord topClosingApp = getTopApp(closingApps, true /* ignoreHidden */);
+
// Determine if closing and opening app token sets are wallpaper targets, in which case
// special animations are needed.
final boolean openingAppHasWallpaper = canBeWallpaperTarget(openingApps)
@@ -346,7 +352,7 @@ public class AppTransitionController {
final boolean closingAppHasWallpaper = canBeWallpaperTarget(closingApps)
&& wallpaperTarget != null;
- // Keyguard transit has highest priority.
+ // Keyguard transit has high priority.
switch (appTransition.getKeyguardTransition()) {
case TRANSIT_KEYGUARD_GOING_AWAY:
return openingAppHasWallpaper ? TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER
@@ -361,6 +367,15 @@ public class AppTransitionController {
return TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
}
+ // Determine whether the top opening and closing activity is a dream activity. If so, this
+ // has higher priority than others except keyguard transit.
+ if (topOpeningApp != null && topOpeningApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
+ return TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
+ } else if (topClosingApp != null
+ && topClosingApp.getActivityType() == ACTIVITY_TYPE_DREAM) {
+ return TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+ }
+
// This is not keyguard transition and one of the app has request to skip app transition.
if (skipAppTransitionAnimation) {
return WindowManager.TRANSIT_OLD_UNSET;
@@ -424,11 +439,6 @@ public class AppTransitionController {
}
}
- final ActivityRecord topOpeningApp = getTopApp(openingApps,
- false /* ignoreHidden */);
- final ActivityRecord topClosingApp = getTopApp(closingApps,
- true /* ignoreHidden */);
-
if (closingAppHasWallpaper && openingAppHasWallpaper) {
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "Wallpaper animation!");
switch (firstTransit) {
@@ -682,7 +692,8 @@ public class AppTransitionController {
if (adapter == null) {
return false;
}
- mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(adapter);
+ mDisplayContent.mAppTransition.overridePendingAppTransitionRemote(
+ adapter, false /* sync */, true /*isActivityEmbedding*/);
ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
"Override with TaskFragment remote animation for transit=%s",
AppTransition.appTransitionOldToString(transit));
diff --git a/services/core/java/com/android/server/wm/AsyncRotationController.java b/services/core/java/com/android/server/wm/AsyncRotationController.java
index 61deb59084c5..1898cc65b107 100644
--- a/services/core/java/com/android/server/wm/AsyncRotationController.java
+++ b/services/core/java/com/android/server/wm/AsyncRotationController.java
@@ -18,7 +18,6 @@ package com.android.server.wm;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM;
@@ -121,7 +120,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
} else {
mTransitionOp = OP_CHANGE;
}
- } else if (transitionType != WindowManager.TRANSIT_NONE) {
+ } else if (displayContent.mTransitionController.isShellTransitionsEnabled()) {
mTransitionOp = OP_APP_SWITCH;
} else {
mTransitionOp = OP_LEGACY;
@@ -144,8 +143,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
// Legacy animation doesn't need to wait for the start transaction.
if (mTransitionOp == OP_LEGACY) {
mIsStartTransactionCommitted = true;
- } else if (displayContent.mTransitionController.useShellTransitionsRotation()
- || displayContent.mTransitionController.isCollecting(displayContent)) {
+ } else if (displayContent.mTransitionController.isCollecting(displayContent)) {
keepAppearanceInPreviousRotation();
}
}
@@ -153,8 +151,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
/** Assigns the operation for the window tokens which can update rotation asynchronously. */
@Override
public void accept(WindowState w) {
- if (w.mActivityRecord != null || !w.mHasSurface || w.mIsWallpaper || w.mIsImWindow
- || w.mAttrs.type == TYPE_NOTIFICATION_SHADE) {
+ if (!w.mHasSurface || !canBeAsync(w.mToken)) {
return;
}
if (mTransitionOp == OP_LEGACY && w.mForceSeamlesslyRotate) {
@@ -186,24 +183,32 @@ class AsyncRotationController extends FadeAnimationController implements Consume
mTargetWindowTokens.put(w.mToken, new Operation(action));
}
+ /** Returns {@code true} if the window token can update rotation independently. */
+ static boolean canBeAsync(WindowToken token) {
+ final int type = token.windowType;
+ return type > WindowManager.LayoutParams.LAST_APPLICATION_WINDOW
+ && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD
+ && type != WindowManager.LayoutParams.TYPE_WALLPAPER
+ && type != WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
+ }
+
/**
* Enables {@link #handleFinishDrawing(WindowState, SurfaceControl.Transaction)} to capture the
* draw transactions of the target windows if needed.
*/
void keepAppearanceInPreviousRotation() {
+ if (mIsSyncDrawRequested) return;
// The transition sync group may be finished earlier because it doesn't wait for these
// target windows. But the windows still need to use sync transaction to keep the appearance
// in previous rotation, so request a no-op sync to keep the state.
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
- if (mHasScreenRotationAnimation
- && mTargetWindowTokens.valueAt(i).mAction == Operation.ACTION_FADE) {
- // The windows are hidden (leash is alpha 0) before finishing drawing so it is
- // unnecessary to request sync.
- continue;
- }
final WindowToken token = mTargetWindowTokens.keyAt(i);
for (int j = token.getChildCount() - 1; j >= 0; j--) {
- token.getChildAt(j).applyWithNextDraw(t -> {});
+ // TODO(b/234585256): The consumer should be handleFinishDrawing(). And check why
+ // the local window might easily time out.
+ final WindowState w = token.getChildAt(j);
+ if (w.isClientLocal()) continue;
+ w.applyWithNextDraw(t -> {});
}
}
mIsSyncDrawRequested = true;
@@ -214,10 +219,10 @@ class AsyncRotationController extends FadeAnimationController implements Consume
private void finishOp(WindowToken windowToken) {
final Operation op = mTargetWindowTokens.remove(windowToken);
if (op == null) return;
- if (op.mCapturedDrawTransaction != null) {
+ if (op.mDrawTransaction != null) {
// Unblock the window to show its latest content.
- mDisplayContent.getPendingTransaction().merge(op.mCapturedDrawTransaction);
- op.mCapturedDrawTransaction = null;
+ mDisplayContent.getPendingTransaction().merge(op.mDrawTransaction);
+ op.mDrawTransaction = null;
if (DEBUG) Slog.d(TAG, "finishOp merge transaction " + windowToken.getTopChild());
}
if (op.mAction == Operation.ACTION_FADE) {
@@ -351,14 +356,34 @@ class AsyncRotationController extends FadeAnimationController implements Consume
}
/**
- * Whether the insets animation leash should use previous position when running fade out
- * animation in rotated display.
+ * Whether the insets animation leash should use previous position when running fade animation
+ * or seamless transformation in a rotated display.
*/
boolean shouldFreezeInsetsPosition(WindowState w) {
- return mTransitionOp == OP_APP_SWITCH && w.mTransitionController.inTransition()
+ return mTransitionOp != OP_LEGACY && w.mTransitionController.inTransition()
&& isTargetToken(w.mToken);
}
+ /**
+ * Returns the transaction which will be applied after the window redraws in new rotation.
+ * This is used to update the position of insets animation leash synchronously.
+ */
+ SurfaceControl.Transaction getDrawTransaction(WindowToken token) {
+ if (mTransitionOp == OP_LEGACY) {
+ // Legacy transition uses startSeamlessRotation and finishSeamlessRotation of
+ // InsetsSourceProvider.
+ return null;
+ }
+ final Operation op = mTargetWindowTokens.get(token);
+ if (op != null) {
+ if (op.mDrawTransaction == null) {
+ op.mDrawTransaction = new SurfaceControl.Transaction();
+ }
+ return op.mDrawTransaction;
+ }
+ return null;
+ }
+
void setOnShowRunnable(Runnable onShowRunnable) {
mOnShowRunnable = onShowRunnable;
}
@@ -368,6 +393,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
* transition starts. And associate transaction callback to consume pending animations.
*/
void setupStartTransaction(SurfaceControl.Transaction t) {
+ if (mIsStartTransactionCommitted) return;
for (int i = mTargetWindowTokens.size() - 1; i >= 0; i--) {
final Operation op = mTargetWindowTokens.valueAt(i);
final SurfaceControl leash = op.mLeash;
@@ -395,7 +421,6 @@ class AsyncRotationController extends FadeAnimationController implements Consume
}
}
- if (mIsStartTransactionCommitted) return;
// If there are windows have redrawn in new rotation but the start transaction has not
// been applied yet, the fade-in animation will be deferred. So once the transaction is
// committed, the fade-in animation can run with screen rotation animation.
@@ -452,23 +477,18 @@ class AsyncRotationController extends FadeAnimationController implements Consume
* by this controller.
*/
boolean handleFinishDrawing(WindowState w, SurfaceControl.Transaction postDrawTransaction) {
- if (mTransitionOp == OP_LEGACY || postDrawTransaction == null
- || !mIsSyncDrawRequested || !w.mTransitionController.inTransition()) {
+ if (mTransitionOp == OP_LEGACY || postDrawTransaction == null || !mIsSyncDrawRequested) {
return false;
}
final Operation op = mTargetWindowTokens.get(w.mToken);
if (op == null) return false;
- final boolean keepUntilTransitionFinish =
- mTransitionOp == OP_APP_SWITCH && op.mAction == Operation.ACTION_FADE;
- final boolean keepUntilStartTransaction =
- !mIsStartTransactionCommitted && op.mAction == Operation.ACTION_SEAMLESS;
- if (!keepUntilTransitionFinish && !keepUntilStartTransaction) return false;
- if (op.mCapturedDrawTransaction == null) {
- op.mCapturedDrawTransaction = postDrawTransaction;
+ if (DEBUG) Slog.d(TAG, "handleFinishDrawing " + w);
+ if (op.mDrawTransaction == null) {
+ op.mDrawTransaction = postDrawTransaction;
} else {
- op.mCapturedDrawTransaction.merge(postDrawTransaction);
+ op.mDrawTransaction.merge(postDrawTransaction);
}
- if (DEBUG) Slog.d(TAG, "Capture draw transaction " + w);
+ mDisplayContent.finishAsyncRotation(w.mToken);
return true;
}
@@ -512,7 +532,7 @@ class AsyncRotationController extends FadeAnimationController implements Consume
* the start transaction of transition, so there won't be a flickering such as the window
* has redrawn during fading out.
*/
- SurfaceControl.Transaction mCapturedDrawTransaction;
+ SurfaceControl.Transaction mDrawTransaction;
Operation(@Action int action) {
mAction = action;
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 46ce43335f01..9a94a4f54b61 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -236,12 +236,18 @@ class BLASTSyncEngine {
private void onTimeout() {
if (!mActiveSyncs.contains(mSyncId)) return;
+ boolean allFinished = true;
for (int i = mRootMembers.size() - 1; i >= 0; --i) {
final WindowContainer<?> wc = mRootMembers.valueAt(i);
if (!wc.isSyncFinished()) {
+ allFinished = false;
Slog.i(TAG, "Unfinished container: " + wc);
}
}
+ if (allFinished && !mReady) {
+ Slog.w(TAG, "Sync group " + mSyncId + " timed-out because not ready. If you see "
+ + "this, please file a bug.");
+ }
finishNow();
}
}
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index d07cc68af890..9295c18fe80e 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -112,6 +112,7 @@ class BackNavigationController {
RemoteAnimationTarget topAppTarget = null;
int prevTaskId;
int prevUserId;
+ boolean prepareAnimation;
BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
synchronized (wmService.mGlobalLock) {
@@ -257,7 +258,8 @@ class BackNavigationController {
BackNavigationInfo.typeToString(backType));
// For now, we only animate when going home.
- boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+ && requestAnimation
// Only create a new leash if no leash has been created.
// Otherwise return null for animation target to avoid conflict.
&& !removedWindowContainer.hasCommittedReparentToAnimationLeash();
@@ -292,7 +294,7 @@ class BackNavigationController {
}
// Special handling for back to home animation
- if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && requestAnimation
+ if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation
&& prevTask != null) {
currentTask.mBackGestureStarted = true;
// Make launcher show from behind by marking its top activity as visible and
@@ -347,7 +349,7 @@ class BackNavigationController {
Task finalTask = currentTask;
RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
result, finalRemovedWindowContainer, finalBackType, finalTask,
- finalprevActivity, requestAnimation));
+ finalprevActivity, prepareAnimation));
infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
}
@@ -381,14 +383,14 @@ class BackNavigationController {
private void onBackNavigationDone(
Bundle result, WindowContainer<?> windowContainer, int backType,
- Task task, ActivityRecord prevActivity, boolean requestAnimation) {
+ Task task, ActivityRecord prevActivity, boolean prepareAnimation) {
SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "task=%s, prevActivity=%s", backType, task, prevActivity);
- if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && requestAnimation) {
+ if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) {
if (triggerBack) {
if (surfaceControl != null && surfaceControl.isValid()) {
// When going back to home, hide the task surface before it is re-parented to
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index fcdf175cb809..0c6cea82e00c 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -486,14 +486,6 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
return WindowConfiguration.inMultiWindowMode(windowingMode);
}
- /**
- * Returns true if this container supports split-screen multi-window and can be put in
- * split-screen based on its current state.
- */
- public boolean supportsSplitScreenWindowingMode() {
- return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
- }
-
public boolean inPinnedWindowingMode() {
return mFullConfiguration.windowConfiguration.getWindowingMode() == WINDOWING_MODE_PINNED;
}
diff --git a/services/core/java/com/android/server/wm/ContentRecordingController.java b/services/core/java/com/android/server/wm/ContentRecordingController.java
index fca4942d4b79..fff7637acc7e 100644
--- a/services/core/java/com/android/server/wm/ContentRecordingController.java
+++ b/services/core/java/com/android/server/wm/ContentRecordingController.java
@@ -63,6 +63,7 @@ final class ContentRecordingController {
*/
void setContentRecordingSessionLocked(@Nullable ContentRecordingSession incomingSession,
@NonNull WindowManagerService wmService) {
+ // TODO(b/219761722) handle a null session arriving due to task setup failing
if (incomingSession != null && (!ContentRecordingSession.isValid(incomingSession)
|| ContentRecordingSession.isSameDisplay(mSession, incomingSession))) {
// Ignore an invalid session, or a session for the same display as currently recording.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 79aef00750a1..14898917caae 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -67,6 +67,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
@@ -130,7 +131,6 @@ import static com.android.server.wm.DisplayContentProto.RESUMED_ACTIVITY;
import static com.android.server.wm.DisplayContentProto.ROOT_DISPLAY_AREA;
import static com.android.server.wm.DisplayContentProto.SCREEN_ROTATION_ANIMATION;
import static com.android.server.wm.DisplayContentProto.SLEEP_TOKENS;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -162,6 +162,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityManagerInternal;
+import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.ScreenOrientation;
@@ -558,7 +559,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final FixedRotationTransitionListener mFixedRotationTransitionListener =
new FixedRotationTransitionListener();
- private PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
+ private final PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher;
+ final RemoteDisplayChangeController mRemoteDisplayChangeController;
/** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
final ArrayList<WindowState> mWinAddedSinceNullFocus = new ArrayList<>();
@@ -756,8 +758,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null
&& !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME)
- && mImeLayeringTarget.isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION)) {
+ && !mImeLayeringTarget.isVisibleRequested()) {
return false;
}
@@ -1054,6 +1055,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this);
mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this,
mTransitionController);
+ mRemoteDisplayChangeController = new RemoteDisplayChangeController(mWmService, mDisplayId);
final InputChannel inputChannel = mWmService.mInputManager.monitorInput(
"PointerEventDispatcher" + mDisplayId, mDisplayId);
@@ -1362,7 +1364,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void setInsetProvider(@InternalInsetsType int type, WindowContainer win,
@Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider) {
- setInsetProvider(type, win, frameProvider, null /* imeFrameProvider */);
+ setInsetProvider(type, win, frameProvider, null /* overrideFrameProviders */);
}
/**
@@ -1371,15 +1373,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* @param type The type of inset this window provides.
* @param win The window.
* @param frameProvider Function to compute the frame, or {@code null} if the just the frame of
- * the window should be taken.
- * @param imeFrameProvider Function to compute the frame when dispatching insets to the IME, or
- * {@code null} if the normal frame should be taken.
+ * the window should be taken. Only for non-WindowState providers, nav bar
+ * and status bar.
+ * @param overrideFrameProviders Functions to compute the frame when dispatching insets to the
+ * given window types, or {@code null} if the normal frame should
+ * be taken.
*/
void setInsetProvider(@InternalInsetsType int type, WindowContainer win,
@Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider,
- @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider) {
+ @Nullable SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
+ overrideFrameProviders) {
mInsetsStateController.getSourceProvider(type).setWindowContainer(win, frameProvider,
- imeFrameProvider);
+ overrideFrameProviders);
}
InsetsStateController getInsetsStateController() {
@@ -1435,7 +1440,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (!isReady()) {
return;
}
- if (mDisplayRotation.isWaitingForRemoteRotation()) {
+ if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
return;
}
@@ -1535,8 +1540,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
config = new Configuration();
computeScreenConfiguration(config);
} else if (!(mTransitionController.isCollecting(this)
- // If waiting for a remote rotation, don't prematurely update configuration.
- || mDisplayRotation.isWaitingForRemoteRotation())) {
+ // If waiting for a remote display change, don't prematurely update configuration.
+ || mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange())) {
// No obvious action we need to take, but if our current state mismatches the
// activity manager's, update it, disregarding font scale, which should remain set
// to the value of the previous configuration.
@@ -1583,8 +1588,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAtmService.getTaskChangeNotificationController()
.notifyTaskRequestedOrientationChanged(task.mTaskId, orientation);
}
- // Currently there is no use case from non-activity.
- if (handleTopActivityLaunchingInDifferentOrientation(r, true /* checkOpening */)) {
+ // The orientation source may not be the top if it uses SCREEN_ORIENTATION_BEHIND.
+ final ActivityRecord topCandidate = !r.mVisibleRequested ? topRunningActivity() : r;
+ if (handleTopActivityLaunchingInDifferentOrientation(
+ topCandidate, r, true /* checkOpening */)) {
// Display orientation should be deferred until the top fixed rotation is finished.
return false;
}
@@ -1596,21 +1603,31 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
boolean isSyncFinished() {
// Do not consider children because if they are requested to be synced, they should be
// added to sync group explicitly.
- return !mDisplayRotation.isWaitingForRemoteRotation();
+ return !mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange();
}
/**
* Returns a valid rotation if the activity can use different orientation than the display.
- * Otherwise {@link #ROTATION_UNDEFINED}.
+ * Otherwise {@link android.app.WindowConfiguration#ROTATION_UNDEFINED}.
*/
@Rotation
int rotationForActivityInDifferentOrientation(@NonNull ActivityRecord r) {
if (mTransitionController.useShellTransitionsRotation()) {
return ROTATION_UNDEFINED;
}
- if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
+ if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM
+ || getIgnoreOrientationRequest()) {
return ROTATION_UNDEFINED;
}
+ if (r.mOrientation == ActivityInfo.SCREEN_ORIENTATION_BEHIND) {
+ final ActivityRecord nextCandidate = getActivity(
+ a -> a.mOrientation != SCREEN_ORIENTATION_UNSET
+ && a.mOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND,
+ r, false /* includeBoundary */, true /* traverseTopToBottom */);
+ if (nextCandidate != null) {
+ r = nextCandidate;
+ }
+ }
if (r.inMultiWindowMode() || r.getRequestedConfigurationOrientation(true /* forDisplay */)
== getConfiguration().orientation) {
return ROTATION_UNDEFINED;
@@ -1624,18 +1641,25 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return rotation;
}
+ boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
+ boolean checkOpening) {
+ return handleTopActivityLaunchingInDifferentOrientation(r, r, checkOpening);
+ }
+
/**
* We need to keep display rotation fixed for a while when the activity in different orientation
* is launching until the launch animation is done to avoid showing the previous activity
* inadvertently in a wrong orientation.
*
* @param r The launching activity which may change display orientation.
+ * @param orientationSrc It may be different from {@param r} if the launching activity uses
+ * "behind" orientation.
* @param checkOpening Whether to check if the activity is animating by transition. Set to
* {@code true} if the caller is not sure whether the activity is launching.
* @return {@code true} if the fixed rotation is started.
*/
- boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
- boolean checkOpening) {
+ private boolean handleTopActivityLaunchingInDifferentOrientation(@NonNull ActivityRecord r,
+ @NonNull ActivityRecord orientationSrc, boolean checkOpening) {
if (!WindowManagerService.ENABLE_FIXED_ROTATION_TRANSFORM) {
return false;
}
@@ -1665,7 +1689,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return false;
}
}
- if (r.isState(RESUMED) && !r.getRootTask().mInResumeTopActivity) {
+ if (r.isState(RESUMED) && !r.getTask().mInResumeTopActivity) {
// If the activity is executing or has done the lifecycle callback, use normal
// rotation animation so the display info can be updated immediately (see
// updateDisplayAndOrientation). This prevents a compatibility issue such as
@@ -1684,7 +1708,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// animation is not running (it may be swiping to home).
return false;
}
- final int rotation = rotationForActivityInDifferentOrientation(r);
+ final int rotation = rotationForActivityInDifferentOrientation(orientationSrc);
if (rotation == ROTATION_UNDEFINED) {
// The display rotation won't be changed by current top activity. The client side
// adjustments of previous rotated activity should be cleared earlier. Otherwise if
@@ -1762,7 +1786,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
* rotation transform to it and indicate that the display may be rotated after it is launched.
*/
void setFixedRotationLaunchingApp(@NonNull ActivityRecord r, @Rotation int rotation) {
- final WindowToken prevRotatedLaunchingApp = mFixedRotationLaunchingApp;
+ final ActivityRecord prevRotatedLaunchingApp = mFixedRotationLaunchingApp;
if (prevRotatedLaunchingApp == r
&& r.getWindowConfiguration().getRotation() == rotation) {
// The given launching app and target rotation are the same as the existing ones.
@@ -1771,8 +1795,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
if (prevRotatedLaunchingApp != null
&& prevRotatedLaunchingApp.getWindowConfiguration().getRotation() == rotation
// It is animating so we can expect there will have a transition callback.
- && (prevRotatedLaunchingApp.isAnimating(TRANSITION | PARENTS)
- || mTransitionController.inTransition(prevRotatedLaunchingApp))) {
+ && (prevRotatedLaunchingApp.isInTransition())) {
// It may be the case that multiple activities launch consecutively. Because their
// rotation are the same, the transformed state can be shared to avoid duplicating
// the heavy operations. This also benefits that the states of multiple activities
@@ -1813,8 +1836,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
sendNewConfiguration();
return;
}
- if (mDisplayRotation.isWaitingForRemoteRotation()) {
- // There is pending rotation change to apply.
+ if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) {
+ // There is pending display change to apply.
return;
}
// The orientation of display is not changed.
@@ -1862,7 +1885,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/** Returns {@code true} if the decided new rotation has not applied to configuration yet. */
- private boolean isRotationChanging() {
+ boolean isRotationChanging() {
return mDisplayRotation.getRotation() != getWindowConfiguration().getRotation();
}
@@ -2015,7 +2038,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
ProtoLog.v(WM_DEBUG_ORIENTATION, "Set mOrientationChanging of %s", w);
w.setOrientationChanging(true);
}
- w.mReportOrientationChanged = true;
}, true /* traverseTopToBottom */);
for (int i = mWmService.mRotationWatchers.size() - 1; i >= 0; i--) {
@@ -2205,9 +2227,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
final float density = mDisplayMetrics.density;
outConfig.screenWidthDp = (int) (mDisplayPolicy.getConfigDisplayWidth(dw, dh, rotation,
- uiMode, displayCutout) / density);
+ uiMode, displayCutout) / density + 0.5f);
outConfig.screenHeightDp = (int) (mDisplayPolicy.getConfigDisplayHeight(dw, dh, rotation,
- uiMode, displayCutout) / density);
+ uiMode, displayCutout) / density + 0.5f);
outConfig.compatScreenWidthDp = (int) (outConfig.screenWidthDp / mCompatibleScreenScale);
outConfig.compatScreenHeightDp = (int) (outConfig.screenHeightDp / mCompatibleScreenScale);
@@ -2390,7 +2412,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
sl = reduceConfigLayout(sl, Surface.ROTATION_90, density, unrotDh, unrotDw, uiMode);
sl = reduceConfigLayout(sl, Surface.ROTATION_180, density, unrotDw, unrotDh, uiMode);
sl = reduceConfigLayout(sl, Surface.ROTATION_270, density, unrotDh, unrotDw, uiMode);
- outConfig.smallestScreenWidthDp = (int)(displayInfo.smallestNominalAppWidth / density);
+ outConfig.smallestScreenWidthDp =
+ (int) (displayInfo.smallestNominalAppWidth / density + 0.5f);
outConfig.screenLayout = sl;
}
@@ -2412,8 +2435,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
longSize = shortSize;
shortSize = tmp;
}
- longSize = (int)(longSize/density);
- shortSize = (int)(shortSize/density);
+ longSize = (int) (longSize / density + 0.5f);
+ shortSize = (int) (shortSize / density + 0.5f);
return Configuration.reduceScreenLayout(curLayout, longSize, shortSize);
}
@@ -2758,6 +2781,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private void updateBaseDisplayMetricsIfNeeded() {
// Get real display metrics without overrides from WM.
mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo(mDisplayId, mDisplayInfo);
+ final int currentRotation = getRotation();
final int orientation = mDisplayInfo.rotation;
final boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
final int newWidth = rotated ? mDisplayInfo.logicalHeight : mDisplayInfo.logicalWidth;
@@ -2818,7 +2842,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
reconfigureDisplayLocked();
if (physicalDisplayChanged) {
- mDisplaySwitchTransitionLauncher.onDisplayUpdated();
+ mDisplaySwitchTransitionLauncher.onDisplayUpdated(currentRotation, getRotation(),
+ getDisplayAreaInfo());
}
}
}
@@ -2922,9 +2947,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Set some sort of reasonable bounds on the size of the display that we will try
// to emulate.
final int minSize = 200;
- final int maxScale = 2;
- width = Math.min(Math.max(width, minSize), mInitialDisplayWidth * maxScale);
- height = Math.min(Math.max(height, minSize), mInitialDisplayHeight * maxScale);
+ final int maxScale = 3;
+ final int maxSize = Math.max(mInitialDisplayWidth, mInitialDisplayHeight) * maxScale;
+ width = Math.min(Math.max(width, minSize), maxSize);
+ height = Math.min(Math.max(height, minSize), maxSize);
}
Slog.i(TAG_WM, "Using new display size: " + width + "x" + height);
@@ -3845,7 +3871,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mAtmService.onImeWindowSetOnDisplayArea(imePid, mImeWindowsContainer);
}
mInsetsStateController.getSourceProvider(ITYPE_IME).setWindowContainer(win,
- mDisplayPolicy.getImeSourceFrameProvider(), null /* imeFrameProvider */);
+ mDisplayPolicy.getImeSourceFrameProvider(), null);
computeImeTarget(true /* updateImeTarget */);
updateImeControlTarget();
}
@@ -4226,6 +4252,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void detach(Transaction t) {
removeImeSurface(t);
}
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(64);
+ sb.append("ImeScreenshot{");
+ sb.append(Integer.toHexString(System.identityHashCode(this)));
+ sb.append(" imeTarget=" + mImeTarget);
+ sb.append(" surface=" + mImeSurface);
+ sb.append('}');
+ return sb.toString();
+ }
}
private void attachAndShowImeScreenshotOnTarget() {
@@ -4258,15 +4295,23 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
/**
- * Removes the IME screenshot when necessary.
- *
- * Used when app transition animation finished or obsoleted screenshot surface like size
- * changed by rotation.
+ * Removes the IME screenshot when the caller is a part of the attached target window.
*/
- void removeImeScreenshotIfPossible() {
- if (mImeLayeringTarget == null
- || mImeLayeringTarget.mAttrs.type != TYPE_APPLICATION_STARTING
- && !mImeLayeringTarget.inTransitionSelfOrParent()) {
+ void removeImeSurfaceByTarget(WindowContainer win) {
+ if (mImeScreenshot == null || win == null) {
+ return;
+ }
+ // The starting window shouldn't be the input target to attach the IME screenshot during
+ // transitioning.
+ if (win.asWindowState() != null
+ && win.asWindowState().mAttrs.type == TYPE_APPLICATION_STARTING) {
+ return;
+ }
+
+ final WindowState screenshotTarget = mImeScreenshot.getImeTarget();
+ final boolean winIsOrContainsScreenshotTarget = (win == screenshotTarget
+ || win.getWindow(w -> w == screenshotTarget) != null);
+ if (winIsOrContainsScreenshotTarget) {
removeImeSurfaceImmediately();
}
}
@@ -4386,8 +4431,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
boolean imeLayeringTargetMayUseIme =
LayoutParams.mayUseInputMethod(mImeLayeringTarget.mAttrs.flags)
|| mImeLayeringTarget.mAttrs.type == TYPE_APPLICATION_STARTING;
- if (imeLayeringTargetMayUseIme && mImeInputTarget != null
- && mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) {
+ if (imeLayeringTargetMayUseIme && (mImeInputTarget == null
+ || mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord())) {
// Do not change parent if the window hasn't requested IME.
return null;
}
@@ -4614,10 +4659,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
wc, SurfaceAnimator.animationTypeToString(type), mImeScreenshot,
mImeScreenshot.getImeTarget());
}
- if (mImeScreenshot != null && (wc == mImeScreenshot.getImeTarget()
- || wc.getWindow(w -> w == mImeScreenshot.getImeTarget()) != null)
- && (type & WindowState.EXIT_ANIMATING_TYPES) != 0) {
- removeImeSurfaceImmediately();
+ if ((type & WindowState.EXIT_ANIMATING_TYPES) != 0) {
+ removeImeSurfaceByTarget(wc);
}
}
@@ -4859,9 +4902,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
return true;
}
- // TODO(b/165794880): Freeform task organizer doesn't support drag-resize yet. Remove
- // the special case when it does.
- if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+ if (task.isOrganized()) {
return true;
}
@@ -5552,11 +5593,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
private static boolean needsGestureExclusionRestrictions(WindowState win,
boolean ignoreRequest) {
final int type = win.mAttrs.type;
+ final int privateFlags = win.mAttrs.privateFlags;
final boolean stickyHideNav =
!win.getRequestedVisibility(ITYPE_NAVIGATION_BAR)
&& win.mAttrs.insetsFlags.behavior == BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
return (!stickyHideNav || ignoreRequest) && type != TYPE_INPUT_METHOD
- && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME;
+ && type != TYPE_NOTIFICATION_SHADE && win.getActivityType() != ACTIVITY_TYPE_HOME
+ && (privateFlags & PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION) == 0;
}
/**
@@ -5668,7 +5711,16 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
void getKeepClearAreas(Set<Rect> outRestricted, Set<Rect> outUnrestricted) {
final Matrix tmpMatrix = new Matrix();
final float[] tmpFloat9 = new float[9];
+ final RecentsAnimationController recentsAnimationController =
+ mWmService.getRecentsAnimationController();
forAllWindows(w -> {
+ // Skip the window if it is part of Recents animation
+ final boolean ignoreRecentsAnimationTarget = recentsAnimationController != null
+ && recentsAnimationController.shouldApplyInputConsumer(w.getActivityRecord());
+ if (ignoreRecentsAnimationTarget) {
+ return false; // continue traversal
+ }
+
if (w.isVisible() && !w.inPinnedWindowingMode()) {
w.getKeepClearAreas(outRestricted, outUnrestricted, tmpMatrix, tmpFloat9);
}
@@ -6092,7 +6144,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
rootTask.ensureActivitiesVisible(starting, configChanges, preserveWindows,
notifyClients);
});
- if (mTransitionController.isCollecting()
+ if (mTransitionController.useShellTransitionsRotation()
+ && mTransitionController.isCollecting()
&& mWallpaperController.getWallpaperTarget() != null) {
// Also update wallpapers so that their requestedVisibility immediately reflects
// the changes to activity visibility.
@@ -6269,7 +6322,6 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* Sets if Display APIs should be sandboxed to the activity window bounds.
*/
- @VisibleForTesting
void setSandboxDisplayApis(boolean sandboxDisplayApis) {
mSandboxDisplayApis = sandboxDisplayApis;
}
@@ -6457,7 +6509,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
// Different tasks won't be in one activity transition animation.
return;
}
- if (task.isAppTransitioning()) {
+ if (task.getActivity(ActivityRecord::isInTransition) != null) {
return;
// Continue to update orientation because the transition of the top rotated
// launching activity is done.
@@ -6490,12 +6542,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
/**
* Notifies the remote insets controller that the top focused window has changed.
*
- * @param packageName The name of the package that is open in the top focused window.
+ * @param component The application component that is open in the top focussed window.
* @param requestedVisibilities The insets visibilities requested by the focussed window.
*/
- void topFocusedWindowChanged(String packageName, InsetsVisibilities requestedVisibilities) {
+ void topFocusedWindowChanged(ComponentName component,
+ InsetsVisibilities requestedVisibilities) {
try {
- mRemoteInsetsController.topFocusedWindowChanged(packageName, requestedVisibilities);
+ mRemoteInsetsController.topFocusedWindowChanged(component, requestedVisibilities);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to deliver package in top focused window change", e);
}
diff --git a/services/core/java/com/android/server/wm/DisplayFrames.java b/services/core/java/com/android/server/wm/DisplayFrames.java
index fd0631320520..7ca38b8fcc00 100644
--- a/services/core/java/com/android/server/wm/DisplayFrames.java
+++ b/services/core/java/com/android/server/wm/DisplayFrames.java
@@ -99,19 +99,28 @@ public class DisplayFrames {
state.setRoundedCorners(roundedCorners);
state.setPrivacyIndicatorBounds(indicatorBounds);
state.getDisplayCutoutSafe(safe);
- if (!cutout.isEmpty()) {
+ if (safe.left > unrestricted.left) {
state.getSource(ITYPE_LEFT_DISPLAY_CUTOUT).setFrame(
unrestricted.left, unrestricted.top, safe.left, unrestricted.bottom);
+ } else {
+ state.removeSource(ITYPE_LEFT_DISPLAY_CUTOUT);
+ }
+ if (safe.top > unrestricted.top) {
state.getSource(ITYPE_TOP_DISPLAY_CUTOUT).setFrame(
unrestricted.left, unrestricted.top, unrestricted.right, safe.top);
+ } else {
+ state.removeSource(ITYPE_TOP_DISPLAY_CUTOUT);
+ }
+ if (safe.right < unrestricted.right) {
state.getSource(ITYPE_RIGHT_DISPLAY_CUTOUT).setFrame(
safe.right, unrestricted.top, unrestricted.right, unrestricted.bottom);
+ } else {
+ state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
+ }
+ if (safe.bottom < unrestricted.bottom) {
state.getSource(ITYPE_BOTTOM_DISPLAY_CUTOUT).setFrame(
unrestricted.left, safe.bottom, unrestricted.right, unrestricted.bottom);
} else {
- state.removeSource(ITYPE_LEFT_DISPLAY_CUTOUT);
- state.removeSource(ITYPE_TOP_DISPLAY_CUTOUT);
- state.removeSource(ITYPE_RIGHT_DISPLAY_CUTOUT);
state.removeSource(ITYPE_BOTTOM_DISPLAY_CUTOUT);
}
return true;
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 7e91989a9105..4a7a8bd99419 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -47,7 +47,9 @@ import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_M
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -61,6 +63,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.TRANSIT_WAKE;
import static android.view.WindowManagerGlobal.ADD_OKAY;
import static android.view.WindowManagerPolicyConstants.ACTION_HDMI_PLUGGED;
import static android.view.WindowManagerPolicyConstants.ALT_BAR_BOTTOM;
@@ -119,6 +122,7 @@ import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.InsetsFlags;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsState;
import android.view.InsetsState.InternalInsetsType;
@@ -143,6 +147,7 @@ import com.android.internal.policy.GestureNavigationSettingsObserver;
import com.android.internal.policy.ScreenDecorationsUtils;
import com.android.internal.policy.SystemBarUtils;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.internal.statusbar.LetterboxDetails;
import com.android.internal.util.ScreenshotHelper;
import com.android.internal.util.function.TriConsumer;
import com.android.internal.view.AppearanceRegion;
@@ -778,7 +783,23 @@ public class DisplayPolicy {
}
public void setAwake(boolean awake) {
+ if (awake == mAwake) {
+ return;
+ }
mAwake = awake;
+ synchronized (mService.mGlobalLock) {
+ if (!mDisplayContent.isDefaultDisplay) {
+ return;
+ }
+ if (mAwake && mDisplayContent.mTransitionController.isShellTransitionsEnabled()
+ && !mDisplayContent.mTransitionController.isCollecting()) {
+ // Start a transition for waking. This is needed for showWhenLocked activities.
+ mDisplayContent.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
+ 0 /* flags */, null /* trigger */, mDisplayContent);
+ }
+ mService.mAtmService.mKeyguardController.updateDeferWakeTransition(
+ mAwake /* waiting */);
+ }
}
public boolean isAwake() {
@@ -954,6 +975,10 @@ public class DisplayPolicy {
}
}
+ if (!win.mSession.mCanSetUnrestrictedGestureExclusion) {
+ attrs.privateFlags &= ~PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+ }
+
// Check if alternate bars positions were updated.
if (mStatusBarAlt == win) {
mStatusBarAltPosition = getAltBarPosition(attrs);
@@ -1062,17 +1087,18 @@ public class DisplayPolicy {
return WindowManagerGlobal.ADD_INVALID_TYPE;
}
- if (attrs.providesInsetsTypes != null) {
+ if (attrs.providedInsets != null) {
// Recents component is allowed to add inset types.
if (!mService.mAtmService.isCallerRecents(callingUid)) {
mContext.enforcePermission(
android.Manifest.permission.STATUS_BAR_SERVICE, callingPid, callingUid,
"DisplayPolicy");
}
- enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providesInsetsTypes);
+ enforceSingleInsetsTypeCorrespondingToWindowType(attrs.providedInsets);
- for (@InternalInsetsType int insetType : attrs.providesInsetsTypes) {
- switch (insetType) {
+ for (InsetsFrameProvider provider : attrs.providedInsets) {
+ @InternalInsetsType int insetsType = provider.type;
+ switch (insetsType) {
case ITYPE_STATUS_BAR:
if ((mStatusBar != null && mStatusBar.isAlive())
|| (mStatusBarAlt != null && mStatusBarAlt.isAlive())) {
@@ -1135,23 +1161,32 @@ public class DisplayPolicy {
break;
case TYPE_NAVIGATION_BAR:
mNavigationBar = win;
- mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
+ final TriConsumer<DisplayFrames, WindowContainer, Rect> navFrameProvider =
(displayFrames, windowContainer, inOutFrame) -> {
if (!mNavButtonForcedVisible) {
- final Insets[] providedInternalInsets = win.getLayoutingAttrs(
- displayFrames.mRotation).providedInternalInsets;
- if (providedInternalInsets != null
- && providedInternalInsets.length > ITYPE_NAVIGATION_BAR
- && providedInternalInsets[ITYPE_NAVIGATION_BAR] != null) {
- inOutFrame.inset(providedInternalInsets[ITYPE_NAVIGATION_BAR]);
+ final LayoutParams lp =
+ win.mAttrs.forRotation(displayFrames.mRotation);
+ if (lp.providedInsets != null) {
+ for (InsetsFrameProvider provider : lp.providedInsets) {
+ if (provider.type != ITYPE_NAVIGATION_BAR) {
+ continue;
+ }
+ InsetsFrameProvider.calculateInsetsFrame(
+ displayFrames.mUnrestricted,
+ win.getBounds(), displayFrames.mDisplayCutoutSafe,
+ inOutFrame, provider.source,
+ provider.insetsSize, lp.privateFlags);
+ }
}
inOutFrame.inset(win.mGivenContentInsets);
}
- },
-
- (displayFrames, windowContainer, inOutFrame) -> {
- // For IME, we don't modify the frame.
- });
+ };
+ final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverride =
+ new SparseArray<>();
+ // For IME, we don't modify the frame.
+ imeOverride.put(TYPE_INPUT_METHOD, null);
+ mDisplayContent.setInsetProvider(ITYPE_NAVIGATION_BAR, win,
+ navFrameProvider, imeOverride);
mDisplayContent.setInsetProvider(ITYPE_BOTTOM_MANDATORY_GESTURES, win,
(displayFrames, windowContainer, inOutFrame) -> {
@@ -1187,21 +1222,10 @@ public class DisplayPolicy {
if (DEBUG_LAYOUT) Slog.i(TAG, "NAVIGATION BAR: " + mNavigationBar);
break;
default:
- if (attrs.providesInsetsTypes != null) {
- for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
- final TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider =
- win.getAttrs().providedInternalImeInsets != null
- ? (displayFrames, windowContainer, inOutFrame) -> {
- final Insets[] providedInternalImeInsets =
- win.getLayoutingAttrs(displayFrames.mRotation)
- .providedInternalImeInsets;
- if (providedInternalImeInsets != null
- && providedInternalImeInsets.length > insetsType
- && providedInternalImeInsets[insetsType] != null) {
- inOutFrame.inset(providedInternalImeInsets[insetsType]);
- }
- } : null;
- switch (insetsType) {
+ if (attrs.providedInsets != null) {
+ for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
+ final InsetsFrameProvider provider = attrs.providedInsets[i];
+ switch (provider.type) {
case ITYPE_STATUS_BAR:
mStatusBarAlt = win;
mStatusBarAltPosition = getAltBarPosition(attrs);
@@ -1219,41 +1243,59 @@ public class DisplayPolicy {
mExtraNavBarAltPosition = getAltBarPosition(attrs);
break;
}
- mDisplayContent.setInsetProvider(insetsType, win,
- win.getAttrs().providedInternalInsets != null ? (displayFrames,
- windowContainer, inOutFrame) -> {
- final Insets[] providedInternalInsets = win.getLayoutingAttrs(
- displayFrames.mRotation).providedInternalInsets;
- if (providedInternalInsets != null
- && providedInternalInsets.length > insetsType
- && providedInternalInsets[insetsType] != null) {
- inOutFrame.inset(providedInternalInsets[insetsType]);
- }
- inOutFrame.inset(win.mGivenContentInsets);
- } : null, imeFrameProvider);
- if (mNavigationBar == null && (insetsType == ITYPE_NAVIGATION_BAR
- || insetsType == ITYPE_EXTRA_NAVIGATION_BAR)) {
- mDisplayContent.setInsetProvider(ITYPE_LEFT_GESTURES, win,
- (displayFrames, windowState, inOutFrame) -> {
- final int leftSafeInset =
- Math.max(displayFrames.mDisplayCutoutSafe.left,0);
- inOutFrame.left = 0;
- inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mDisplayHeight;
- inOutFrame.right =
- leftSafeInset + mLeftGestureInset;
- });
- mDisplayContent.setInsetProvider(ITYPE_RIGHT_GESTURES, win,
- (displayFrames, windowState, inOutFrame) -> {
- final int rightSafeInset =
- Math.min(displayFrames.mDisplayCutoutSafe.right,
- displayFrames.mUnrestricted.right);
- inOutFrame.left = rightSafeInset - mRightGestureInset;
- inOutFrame.top = 0;
- inOutFrame.bottom = displayFrames.mDisplayHeight;
- inOutFrame.right = displayFrames.mDisplayWidth;
- });
+ // The index of the provider and corresponding insets types cannot change at
+ // runtime as ensured in WMS. Make use of the index in the provider directly
+ // to access the latest provided size at runtime.
+ final int index = i;
+ final TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider =
+ provider.insetsSize != null
+ ? (displayFrames, windowContainer, inOutFrame) -> {
+ inOutFrame.inset(win.mGivenContentInsets);
+ final LayoutParams lp =
+ win.mAttrs.forRotation(displayFrames.mRotation);
+ final InsetsFrameProvider ifp =
+ win.mAttrs.forRotation(displayFrames.mRotation)
+ .providedInsets[index];
+ InsetsFrameProvider.calculateInsetsFrame(
+ displayFrames.mUnrestricted,
+ windowContainer.getBounds(),
+ displayFrames.mDisplayCutoutSafe,
+ inOutFrame, ifp.source,
+ ifp.insetsSize, lp.privateFlags);
+ } : null;
+ final InsetsFrameProvider.InsetsSizeOverride[] overrides =
+ provider.insetsSizeOverrides;
+ final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
+ overrideProviders;
+ if (overrides != null) {
+ overrideProviders = new SparseArray<>();
+ for (int j = overrides.length - 1; j >= 0; j--) {
+ final int overrideIndex = j;
+ final TriConsumer<DisplayFrames, WindowContainer, Rect>
+ overrideFrameProvider =
+ (displayFrames, windowContainer, inOutFrame) -> {
+ final LayoutParams lp =
+ win.mAttrs.forRotation(
+ displayFrames.mRotation);
+ final InsetsFrameProvider ifp =
+ win.mAttrs.providedInsets[index];
+ InsetsFrameProvider.calculateInsetsFrame(
+ displayFrames.mUnrestricted,
+ windowContainer.getBounds(),
+ displayFrames.mDisplayCutoutSafe,
+ inOutFrame, ifp.source,
+ ifp.insetsSizeOverrides[
+ overrideIndex].insetsSize,
+ lp.privateFlags);
+ };
+ overrideProviders.put(overrides[j].windowType,
+ overrideFrameProvider);
+ }
+ } else {
+ overrideProviders = null;
}
+ mDisplayContent.setInsetProvider(provider.type, win, frameProvider,
+ overrideProviders);
mInsetsSourceWindowsExceptIme.add(win);
}
}
@@ -1299,10 +1341,11 @@ public class DisplayPolicy {
};
}
- private static void enforceSingleInsetsTypeCorrespondingToWindowType(int[] insetsTypes) {
+ private static void enforceSingleInsetsTypeCorrespondingToWindowType(
+ InsetsFrameProvider[] providers) {
int count = 0;
- for (int insetsType : insetsTypes) {
- switch (insetsType) {
+ for (InsetsFrameProvider provider : providers) {
+ switch (provider.type) {
case ITYPE_NAVIGATION_BAR:
case ITYPE_STATUS_BAR:
case ITYPE_CLIMATE_BAR:
@@ -1350,7 +1393,7 @@ public class DisplayPolicy {
private int getStatusBarHeight(DisplayFrames displayFrames) {
int statusBarHeight;
if (mStatusBar != null) {
- statusBarHeight = mStatusBar.getLayoutingAttrs(displayFrames.mRotation).height;
+ statusBarHeight = mStatusBar.mAttrs.forRotation(displayFrames.mRotation).height;
} else {
statusBarHeight = 0;
}
@@ -1504,13 +1547,14 @@ public class DisplayPolicy {
*/
void simulateLayoutDisplay(DisplayFrames displayFrames) {
final InsetsStateController controller = mDisplayContent.getInsetsStateController();
+ sTmpClientFrames.attachedFrame = null;
for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
- mWindowLayout.computeFrames(win.getLayoutingAttrs(displayFrames.mRotation),
+ mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(),
- null /* attachedWindowFrame */, win.mGlobalScale, sTmpClientFrames);
+ UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
+ sTmpClientFrames);
final SparseArray<InsetsSource> sources = win.getProvidedInsetsSources();
final InsetsState state = displayFrames.mInsetsState;
for (int index = sources.size() - 1; index >= 0; index--) {
@@ -1522,13 +1566,14 @@ public class DisplayPolicy {
}
void updateInsetsSourceFramesExceptIme(DisplayFrames displayFrames) {
+ sTmpClientFrames.attachedFrame = null;
for (int i = mInsetsSourceWindowsExceptIme.size() - 1; i >= 0; i--) {
final WindowState win = mInsetsSourceWindowsExceptIme.valueAt(i);
- mWindowLayout.computeFrames(win.getLayoutingAttrs(displayFrames.mRotation),
+ mWindowLayout.computeFrames(win.mAttrs.forRotation(displayFrames.mRotation),
displayFrames.mInsetsState, displayFrames.mDisplayCutoutSafe,
displayFrames.mUnrestricted, win.getWindowingMode(), UNSPECIFIED_LENGTH,
- UNSPECIFIED_LENGTH, win.getRequestedVisibilities(),
- null /* attachedWindowFrame */, win.mGlobalScale, sTmpClientFrames);
+ UNSPECIFIED_LENGTH, win.getRequestedVisibilities(), win.mGlobalScale,
+ sTmpClientFrames);
win.updateSourceFrame(sTmpClientFrames.frame);
}
}
@@ -1557,8 +1602,8 @@ public class DisplayPolicy {
// We invoke this to get the proper DisplayFrames.
displayFrames = win.getDisplayFrames(displayFrames);
- final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);
- final Rect attachedWindowFrame = attached != null ? attached.getFrame() : null;
+ final WindowManager.LayoutParams attrs = win.mAttrs.forRotation(displayFrames.mRotation);
+ sTmpClientFrames.attachedFrame = attached != null ? attached.getFrame() : null;
// If this window has different LayoutParams for rotations, we cannot trust its requested
// size. Because it might have not sent its requested size for the new rotation.
@@ -1568,8 +1613,7 @@ public class DisplayPolicy {
mWindowLayout.computeFrames(attrs, win.getInsetsState(), displayFrames.mDisplayCutoutSafe,
win.getBounds(), win.getWindowingMode(), requestedWidth, requestedHeight,
- win.getRequestedVisibilities(), attachedWindowFrame, win.mGlobalScale,
- sTmpClientFrames);
+ win.getRequestedVisibilities(), win.mGlobalScale, sTmpClientFrames);
win.setFrames(sTmpClientFrames, win.mRequestedWidth, win.mRequestedHeight);
}
@@ -1961,25 +2005,20 @@ public class DisplayPolicy {
&& lp.paramsForRotation[rotation] != null) {
lp = lp.paramsForRotation[rotation];
}
- final Insets providedInternalInsets;
- if (lp.providedInternalInsets != null
- && lp.providedInternalInsets.length > ITYPE_NAVIGATION_BAR
- && lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] != null) {
- providedInternalInsets = lp.providedInternalInsets[ITYPE_NAVIGATION_BAR];
- } else {
- providedInternalInsets = Insets.NONE;
- }
- if (position == NAV_BAR_LEFT) {
- if (lp.width > providedInternalInsets.right) {
- return lp.width - providedInternalInsets.right;
- } else {
- return 0;
+ Insets providedInsetsSize = null;
+ if (lp.providedInsets != null) {
+ for (InsetsFrameProvider provider : lp.providedInsets) {
+ if (provider.type != ITYPE_NAVIGATION_BAR) {
+ continue;
+ }
+ providedInsetsSize = provider.insetsSize;
}
- } else if (position == NAV_BAR_RIGHT) {
- if (lp.width > providedInternalInsets.left) {
- return lp.width - providedInternalInsets.left;
- } else {
- return 0;
+ }
+ if (providedInsetsSize != null) {
+ if (position == NAV_BAR_LEFT) {
+ return providedInsetsSize.left;
+ } else if (position == NAV_BAR_RIGHT) {
+ return providedInsetsSize.right;
}
}
return lp.width;
@@ -2025,19 +2064,21 @@ public class DisplayPolicy {
if (mNavigationBar == null) {
return 0;
}
- LayoutParams lp = mNavigationBar.getLayoutingAttrs(rotation);
- final Insets providedInternalInsets;
- if (lp.providedInternalInsets != null
- && lp.providedInternalInsets.length > ITYPE_NAVIGATION_BAR
- && lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] != null) {
- providedInternalInsets = lp.providedInternalInsets[ITYPE_NAVIGATION_BAR];
- } else {
- providedInternalInsets = Insets.NONE;
- }
- if (lp.height < providedInternalInsets.top) {
- return 0;
+ LayoutParams lp = mNavigationBar.mAttrs.forRotation(rotation);
+ Insets providedInsetsSize = null;
+ if (lp.providedInsets != null) {
+ for (InsetsFrameProvider provider : lp.providedInsets) {
+ if (provider.type != ITYPE_NAVIGATION_BAR) {
+ continue;
+ }
+ providedInsetsSize = provider.insetsSize;
+ if (providedInsetsSize != null) {
+ return providedInsetsSize.bottom;
+ }
+ break;
+ }
}
- return lp.height - providedInternalInsets.top;
+ return lp.height;
}
/**
@@ -2055,7 +2096,7 @@ public class DisplayPolicy {
if (mNavigationBar == null) {
return 0;
}
- return mNavigationBar.getLayoutingAttrs(rotation).height;
+ return mNavigationBar.mAttrs.forRotation(rotation).height;
}
/**
@@ -2184,7 +2225,7 @@ public class DisplayPolicy {
@NavigationBarPosition
int navigationBarPosition(int displayRotation) {
if (mNavigationBar != null) {
- final int gravity = mNavigationBar.getLayoutingAttrs(displayRotation).gravity;
+ final int gravity = mNavigationBar.mAttrs.forRotation(displayRotation).gravity;
switch (gravity) {
case Gravity.LEFT:
return NAV_BAR_LEFT;
@@ -2371,7 +2412,7 @@ public class DisplayPolicy {
mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
- requestedVisibilities, focusedApp));
+ requestedVisibilities, focusedApp, new LetterboxDetails[]{}));
}
private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index 03e1429f1bf1..3d91921e3ab7 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -51,14 +51,12 @@ import android.content.res.Resources;
import android.database.ContentObserver;
import android.hardware.power.Boost;
import android.os.Handler;
-import android.os.RemoteException;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings;
import android.util.Slog;
import android.util.TimeUtils;
import android.util.proto.ProtoOutputStream;
-import android.view.IDisplayWindowRotationCallback;
import android.view.IWindowManager;
import android.view.Surface;
import android.window.TransitionRequestInfo;
@@ -67,7 +65,6 @@ import android.window.WindowContainerTransaction;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
import com.android.server.UiThread;
import com.android.server.policy.WindowManagerPolicy;
@@ -211,31 +208,6 @@ public class DisplayRotation {
private boolean mDemoHdmiRotationLock;
private boolean mDemoRotationLock;
- private static final int REMOTE_ROTATION_TIMEOUT_MS = 800;
-
- private boolean mIsWaitingForRemoteRotation = false;
-
- private final Runnable mDisplayRotationHandlerTimeout =
- new Runnable() {
- @Override
- public void run() {
- continueRotation(mRotation, null /* transaction */);
- }
- };
-
- private final IDisplayWindowRotationCallback mRemoteRotationCallback =
- new IDisplayWindowRotationCallback.Stub() {
- @Override
- public void continueRotateDisplay(int targetRotation,
- WindowContainerTransaction t) {
- synchronized (mService.getWindowManagerLock()) {
- mService.mH.sendMessage(PooledLambda.obtainMessage(
- DisplayRotation::continueRotation, DisplayRotation.this,
- targetRotation, t));
- }
- }
- };
-
DisplayRotation(WindowManagerService service, DisplayContent displayContent) {
this(service, displayContent, displayContent.getDisplayPolicy(),
service.mDisplayWindowSettings, service.mContext, service.getWindowManagerLock());
@@ -511,6 +483,7 @@ public class DisplayRotation {
final TransitionRequestInfo.DisplayChange change = wasCollecting ? null
: new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId(),
oldRotation, mRotation);
+
mDisplayContent.requestChangeTransitionIfNeeded(
ActivityInfo.CONFIG_WINDOW_CONFIGURATION, change);
if (wasCollecting) {
@@ -554,61 +527,39 @@ public class DisplayRotation {
return null;
}
- /**
- * A Remote rotation is when we are waiting for some registered (remote)
- * {@link IDisplayWindowRotationController} to calculate and return some hierarchy operations
- * to perform in sync with the rotation.
- */
- boolean isWaitingForRemoteRotation() {
- return mIsWaitingForRemoteRotation;
- }
-
private void startRemoteRotation(int fromRotation, int toRotation) {
- if (mService.mDisplayRotationController == null) {
- return;
- }
- mIsWaitingForRemoteRotation = true;
- try {
- mService.mDisplayRotationController.onRotateDisplay(mDisplayContent.getDisplayId(),
- fromRotation, toRotation, mRemoteRotationCallback);
- mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
- mService.mH.postDelayed(mDisplayRotationHandlerTimeout, REMOTE_ROTATION_TIMEOUT_MS);
- } catch (RemoteException e) {
- mIsWaitingForRemoteRotation = false;
- return;
- }
+ mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange(
+ fromRotation, toRotation, null /* newDisplayAreaInfo */,
+ (transaction) -> continueRotation(toRotation, transaction)
+ );
}
private void continueRotation(int targetRotation, WindowContainerTransaction t) {
- synchronized (mService.mGlobalLock) {
- if (targetRotation != mRotation || !mIsWaitingForRemoteRotation) {
- // Drop it, this is either coming from an outdated remote rotation; or, we've
- // already moved on.
- return;
- }
- mService.mH.removeCallbacks(mDisplayRotationHandlerTimeout);
- mIsWaitingForRemoteRotation = false;
+ if (targetRotation != mRotation) {
+ // Drop it, this is either coming from an outdated remote rotation; or, we've
+ // already moved on.
+ return;
+ }
- if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
- if (!mDisplayContent.mTransitionController.isCollecting()) {
- throw new IllegalStateException("Trying to rotate outside a transition");
- }
- mDisplayContent.mTransitionController.collect(mDisplayContent);
- // Go through all tasks and collect them before the rotation
- // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
- // handling is synchronized.
- mDisplayContent.mTransitionController.collectForDisplayChange(mDisplayContent,
- null /* use collecting transition */);
+ if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ if (!mDisplayContent.mTransitionController.isCollecting()) {
+ throw new IllegalStateException("Trying to rotate outside a transition");
}
- mService.mAtmService.deferWindowLayout();
- try {
- mDisplayContent.sendNewConfiguration();
- if (t != null) {
- mService.mAtmService.mWindowOrganizerController.applyTransaction(t);
- }
- } finally {
- mService.mAtmService.continueWindowLayout();
+ mDisplayContent.mTransitionController.collect(mDisplayContent);
+ // Go through all tasks and collect them before the rotation
+ // TODO(shell-transitions): move collect() to onConfigurationChange once wallpaper
+ // handling is synchronized.
+ mDisplayContent.mTransitionController.collectForDisplayAreaChange(mDisplayContent,
+ null /* use collecting transition */);
+ }
+ mService.mAtmService.deferWindowLayout();
+ try {
+ mDisplayContent.sendNewConfiguration();
+ if (t != null) {
+ mService.mAtmService.mWindowOrganizerController.applyTransaction(t);
}
+ } finally {
+ mService.mAtmService.continueWindowLayout();
}
}
@@ -669,7 +620,8 @@ public class DisplayRotation {
// We only enable seamless rotation if the top window has requested it and is in the
// fullscreen opaque state. Seamless rotation requires freezing various Surface states and
// won't work well with animations, so we disable it in the animation case for now.
- if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.isAnimatingLw()) {
+ if (w.getAttrs().rotationAnimation != ROTATION_ANIMATION_SEAMLESS || w.inMultiWindowMode()
+ || w.isAnimatingLw()) {
return false;
}
@@ -1571,8 +1523,8 @@ public class DisplayRotation {
}
@Override
- public boolean isKeyguardLocked() {
- return mService.isKeyguardLocked();
+ public boolean isKeyguardShowingAndNotOccluded() {
+ return mService.isKeyguardShowingAndNotOccluded();
}
@Override
@@ -1684,7 +1636,9 @@ public class DisplayRotation {
final WindowContainer<?> source = dc.getLastOrientationSource();
if (source != null) {
mLastOrientationSource = source.toString();
- mSourceOrientation = source.mOrientation;
+ final WindowState w = source.asWindowState();
+ mSourceOrientation =
+ w != null ? w.mAttrs.screenOrientation : source.mOrientation;
} else {
mLastOrientationSource = null;
mSourceOrientation = SCREEN_ORIENTATION_UNSET;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 65b1f0233d93..f2d4d5427291 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -31,6 +31,8 @@ import static com.android.server.wm.DragDropController.MSG_TEAR_DOWN_DRAG_AND_DR
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerService.MY_PID;
+import static com.android.server.wm.WindowManagerService.MY_UID;
import android.animation.Animator;
import android.animation.PropertyValuesHolder;
@@ -45,7 +47,6 @@ import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
import android.os.InputConfig;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -208,8 +209,6 @@ class DragState {
// Send drag end broadcast if drag start has been sent.
if (mDragInProgress) {
- final int myPid = Process.myPid();
-
if (DEBUG_DRAG) {
Slog.d(TAG_WM, "broadcasting DRAG_ENDED");
}
@@ -237,7 +236,7 @@ class DragState {
}
// if the current window is in the same process,
// the dispatch has already recycled the event
- if (myPid != ws.mSession.mPid) {
+ if (MY_PID != ws.mSession.mPid) {
event.recycle();
}
}
@@ -321,7 +320,6 @@ class DragState {
mData.fixUris(mSourceUserId);
}
}
- final int myPid = Process.myPid();
final IBinder clientToken = touchedWin.mClient.asBinder();
final DragEvent event = obtainDragEvent(DragEvent.ACTION_DROP, x, y,
mData, targetInterceptsGlobalDrag(touchedWin),
@@ -337,7 +335,7 @@ class DragState {
endDragLocked();
return false;
} finally {
- if (myPid != touchedWin.mSession.mPid) {
+ if (MY_PID != touchedWin.mSession.mPid) {
event.recycle();
}
}
@@ -365,8 +363,8 @@ class DragState {
mDragWindowHandle.token = mClientChannel.getToken();
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
- mDragWindowHandle.ownerPid = Process.myPid();
- mDragWindowHandle.ownerUid = Process.myUid();
+ mDragWindowHandle.ownerPid = MY_PID;
+ mDragWindowHandle.ownerUid = MY_UID;
mDragWindowHandle.scaleFactor = 1.0f;
// Keep the default behavior of this window to be focusable, which allows the system
@@ -478,7 +476,7 @@ class DragState {
Slog.w(TAG_WM, "Unable to drag-start window " + newWin);
} finally {
// if the callee was local, the dispatch has already recycled the event
- if (Process.myPid() != newWin.mSession.mPid) {
+ if (MY_PID != newWin.mSession.mPid) {
event.recycle();
}
}
diff --git a/services/core/java/com/android/server/wm/EventLogTags.logtags b/services/core/java/com/android/server/wm/EventLogTags.logtags
index 6d63331622b9..1e5a219e5e52 100644
--- a/services/core/java/com/android/server/wm/EventLogTags.logtags
+++ b/services/core/java/com/android/server/wm/EventLogTags.logtags
@@ -66,4 +66,4 @@ option java_package com.android.server.wm
31007 wm_boot_animation_done (time|2|3)
# Request surface flinger to show / hide the wallpaper surface.
-33001 wm_wallpaper_surface (Display Id|1|5),(visible|1)
+33001 wm_wallpaper_surface (Display Id|1|5),(Visible|1),(Target|3)
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 0d4cfa3a8128..3e6e06a27fdc 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -234,7 +234,7 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
//
private static boolean isImeLayeringTarget(@NonNull InsetsControlTarget target,
@NonNull InsetsControlTarget dcTarget) {
- return !dcTarget.getWindow().isClosing() && target == dcTarget;
+ return !isImeTargetWindowClosing(dcTarget.getWindow()) && target == dcTarget;
}
private static boolean isAboveImeLayeringTarget(@NonNull InsetsControlTarget target,
@@ -256,7 +256,14 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider
final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL);
return target == mImeRequester
&& (mImeRequester.getWindow() == null
- || !mImeRequester.getWindow().isClosing());
+ || !isImeTargetWindowClosing(mImeRequester.getWindow()));
+ }
+
+ private static boolean isImeTargetWindowClosing(@NonNull WindowState win) {
+ return win.mAnimatingExit || win.mActivityRecord != null
+ && (win.mActivityRecord.isInTransition()
+ && !win.mActivityRecord.isVisibleRequested()
+ || win.mActivityRecord.willCloseOrEnterPip());
}
private boolean isTargetChangedWithinActivity(InsetsControlTarget target) {
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 59be3e05f2c0..39622c1c5aaf 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -23,7 +23,6 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.InputConfig;
-import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.view.InputApplicationHandle;
@@ -72,8 +71,8 @@ class InputConsumerImpl implements IBinder.DeathRecipient {
mWindowHandle.token = mClientChannel.getToken();
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
- mWindowHandle.ownerPid = Process.myPid();
- mWindowHandle.ownerUid = Process.myUid();
+ mWindowHandle.ownerPid = WindowManagerService.MY_PID;
+ mWindowHandle.ownerUid = WindowManagerService.MY_UID;
mWindowHandle.scaleFactor = 1.0f;
mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.TRUSTED_OVERLAY;
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 7e06b8823260..2ce333d5438b 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -64,7 +64,10 @@ import android.view.SurfaceControl;
import android.view.WindowManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.protolog.common.ProtoLog;
+import com.android.server.LocalServices;
+import com.android.server.inputmethod.InputMethodManagerInternal;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -288,10 +291,7 @@ final class InputMonitor {
SurfaceControl touchableRegionCrop = null;
final Task task = w.getTask();
if (task != null) {
- // TODO(b/165794636): Remove the special case for freeform window once drag resizing is
- // handled by WM shell.
- if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN
- && !task.inFreeformWindowingMode()) {
+ if (task.isOrganized() && task.getWindowingMode() != WINDOWING_MODE_FULLSCREEN) {
// If the window is in a TaskManaged by a TaskOrganizer then most cropping will
// be applied using the SurfaceControl hierarchy from the Organizer. This means
// we need to make sure that these changes in crop are reflected in the input
@@ -408,8 +408,28 @@ final class InputMonitor {
// Shell transitions doesn't use RecentsAnimationController
|| getWeak(mActiveRecentsActivity) != null && focus.inTransition();
if (shouldApplyRecentsInputConsumer) {
- requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
- recentsAnimationInputConsumer.mName);
+ if (mInputFocus != recentsAnimationInputConsumer.mWindowHandle.token) {
+ requestFocus(recentsAnimationInputConsumer.mWindowHandle.token,
+ recentsAnimationInputConsumer.mName);
+ // Hiding IME/IME icon when recents input consumer gain focus.
+ if (!mDisplayContent.isImeAttachedToApp()) {
+ // Hiding IME if IME window is not attached to app since it's not proper to
+ // snapshot Task with IME window to animate together in this case.
+ final InputMethodManagerInternal inputMethodManagerInternal =
+ LocalServices.getService(InputMethodManagerInternal.class);
+ if (inputMethodManagerInternal != null) {
+ inputMethodManagerInternal.hideCurrentInputMethod(
+ SoftInputShowHideReason.HIDE_RECENTS_ANIMATION);
+ }
+ } else {
+ // Disable IME icon explicitly when IME attached to the app in case
+ // IME icon might flickering while swiping to the next app task still
+ // in animating before the next app window focused, or IME icon
+ // persists on the bottom when swiping the task to recents.
+ InputMethodManagerInternal.get().updateImeWindowStatus(
+ true /* disableImeIcon */);
+ }
+ }
return;
}
}
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 9e0d7b57264e..3e2d7c928936 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -46,6 +46,7 @@ import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.StatusBarManager;
import android.app.WindowConfiguration;
+import android.content.ComponentName;
import android.content.res.Resources;
import android.graphics.Rect;
import android.util.ArrayMap;
@@ -55,6 +56,7 @@ import android.view.InsetsAnimationControlCallbacks;
import android.view.InsetsAnimationControlImpl;
import android.view.InsetsAnimationControlRunner;
import android.view.InsetsController;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -264,7 +266,7 @@ class InsetsPolicy {
state = originalState;
}
state = adjustVisibilityForIme(target, state, state == originalState);
- return adjustInsetsForRoundedCorners(target, state, state == originalState);
+ return adjustInsetsForRoundedCorners(target.mToken, state, state == originalState);
}
InsetsState adjustInsetsForWindow(WindowState target, InsetsState originalState) {
@@ -288,8 +290,9 @@ class InsetsPolicy {
// contains all insets types.
final InsetsState originalState = mDisplayContent.getInsetsPolicy()
.enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
- mStateController.getRawInsetsState());
- return adjustVisibilityForTransientTypes(originalState);
+ attrs.type, mStateController.getRawInsetsState());
+ InsetsState state = adjustVisibilityForTransientTypes(originalState);
+ return adjustInsetsForRoundedCorners(token, state, state == originalState);
}
/**
@@ -322,14 +325,14 @@ class InsetsPolicy {
}
// If not one of the types above, check whether an internal inset mapping is specified.
- if (attrs.providesInsetsTypes != null) {
- for (@InternalInsetsType int insetsType : attrs.providesInsetsTypes) {
- switch (insetsType) {
+ if (attrs.providedInsets != null) {
+ for (InsetsFrameProvider provider : attrs.providedInsets) {
+ switch (provider.type) {
case ITYPE_STATUS_BAR:
case ITYPE_NAVIGATION_BAR:
case ITYPE_CLIMATE_BAR:
case ITYPE_EXTRA_NAVIGATION_BAR:
- return insetsType;
+ return provider.type;
}
}
}
@@ -349,12 +352,13 @@ class InsetsPolicy {
* @param type the inset type provided by the target
* @param windowingMode the windowing mode of the target
* @param isAlwaysOnTop is the target always on top
+ * @param windowType the type of the target
* @param state the input inset state containing all the sources
* @return The state stripped of the necessary information.
*/
InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
@WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
- InsetsState state) {
+ int windowType, InsetsState state) {
boolean stateCopied = false;
if (type != ITYPE_INVALID) {
@@ -375,21 +379,20 @@ class InsetsPolicy {
if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
state.removeSource(ITYPE_CAPTION_BAR);
}
-
- // IME needs different frames for certain cases (e.g. navigation bar in gesture nav).
- if (type == ITYPE_IME) {
- ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
- .getSourceProviders();
- for (int i = providers.size() - 1; i >= 0; i--) {
- WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
- if (otherProvider.overridesImeFrame()) {
- InsetsSource override =
- new InsetsSource(
- state.getSource(otherProvider.getSource().getType()));
- override.setFrame(otherProvider.getImeOverrideFrame());
- state.addSource(override);
- }
+ }
+ ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
+ .getSourceProviders();
+ for (int i = providers.size() - 1; i >= 0; i--) {
+ WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
+ if (otherProvider.overridesFrame(windowType)) {
+ if (!stateCopied) {
+ state = new InsetsState(state);
+ stateCopied = true;
}
+ InsetsSource override =
+ new InsetsSource(state.getSource(otherProvider.getSource().getType()));
+ override.setFrame(otherProvider.getOverriddenFrame(windowType));
+ state.addSource(override);
}
}
@@ -464,15 +467,19 @@ class InsetsPolicy {
return originalState;
}
- private InsetsState adjustInsetsForRoundedCorners(WindowState w, InsetsState originalState,
+ private InsetsState adjustInsetsForRoundedCorners(WindowToken token, InsetsState originalState,
boolean copyState) {
- final Task task = w.getTask();
- if (task != null && !task.getWindowConfiguration().tasksAreFloating()) {
- // Use task bounds to calculating rounded corners if the task is not floating.
- final Rect roundedCornerFrame = new Rect(task.getBounds());
- final InsetsState state = copyState ? new InsetsState(originalState) : originalState;
- state.setRoundedCornerFrame(roundedCornerFrame);
- return state;
+ if (token != null) {
+ final ActivityRecord activityRecord = token.asActivityRecord();
+ final Task task = activityRecord != null ? activityRecord.getTask() : null;
+ if (task != null && !task.getWindowConfiguration().tasksAreFloating()) {
+ // Use task bounds to calculating rounded corners if the task is not floating.
+ final Rect roundedCornerFrame = new Rect(task.getBounds());
+ final InsetsState state = copyState ? new InsetsState(originalState)
+ : originalState;
+ state.setRoundedCornerFrame(roundedCornerFrame);
+ return state;
+ }
}
return originalState;
}
@@ -542,8 +549,10 @@ class InsetsPolicy {
return focusedWin;
}
if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+ ComponentName component = focusedWin.mActivityRecord != null
+ ? focusedWin.mActivityRecord.mActivityComponent : null;
mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
- focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities());
+ component, focusedWin.getRequestedVisibilities());
return mDisplayContent.mRemoteInsetsControlTarget;
}
if (mPolicy.areSystemBarsForcedShownLw()) {
@@ -600,8 +609,10 @@ class InsetsPolicy {
return null;
}
if (remoteInsetsControllerControlsSystemBars(focusedWin)) {
+ ComponentName component = focusedWin.mActivityRecord != null
+ ? focusedWin.mActivityRecord.mActivityComponent : null;
mDisplayContent.mRemoteInsetsControlTarget.topFocusedWindowChanged(
- focusedWin.mAttrs.packageName, focusedWin.getRequestedVisibilities());
+ component, focusedWin.getRequestedVisibilities());
return mDisplayContent.mRemoteInsetsControlTarget;
}
if (mPolicy.areSystemBarsForcedShownLw()) {
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index 358e93d89f64..86a73c935e52 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -27,7 +27,6 @@ import static com.android.server.wm.InsetsSourceProviderProto.CONTROL_TARGET;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL;
import static com.android.server.wm.InsetsSourceProviderProto.FAKE_CONTROL_TARGET;
import static com.android.server.wm.InsetsSourceProviderProto.FRAME;
-import static com.android.server.wm.InsetsSourceProviderProto.IME_OVERRIDDEN_FRAME;
import static com.android.server.wm.InsetsSourceProviderProto.IS_LEASH_READY_FOR_DISPATCHING;
import static com.android.server.wm.InsetsSourceProviderProto.PENDING_CONTROL_TARGET;
import static com.android.server.wm.InsetsSourceProviderProto.SEAMLESS_ROTATING;
@@ -41,7 +40,9 @@ import android.annotation.Nullable;
import android.graphics.Insets;
import android.graphics.Point;
import android.graphics.Rect;
+import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
+import android.view.InsetsFrameProvider;
import android.view.InsetsSource;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
@@ -77,8 +78,8 @@ abstract class InsetsSourceProvider {
private @Nullable ControlAdapter mAdapter;
private TriConsumer<DisplayFrames, WindowContainer, Rect> mFrameProvider;
- private TriConsumer<DisplayFrames, WindowContainer, Rect> mImeFrameProvider;
- private final Rect mImeOverrideFrame = new Rect();
+ private SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> mOverrideFrameProviders;
+ private final SparseArray<Rect> mOverrideFrames = new SparseArray<Rect>();
private boolean mIsLeashReadyForDispatching;
private final Rect mSourceFrame = new Rect();
private final Rect mLastSourceFrame = new Rect();
@@ -145,12 +146,15 @@ abstract class InsetsSourceProvider {
* @param windowContainer The window container that links to this source.
* @param frameProvider Based on display frame state and the window, calculates the resulting
* frame that should be reported to clients.
- * @param imeFrameProvider Based on display frame state and the window, calculates the resulting
- * frame that should be reported to IME.
+ * This will only be used when the window container providing the insets is
+ * not a WindowState.
+ * @param overrideFrameProviders Based on display frame state and the window, calculates the
+ * resulting frame that should be reported to given window type.
*/
void setWindowContainer(@Nullable WindowContainer windowContainer,
@Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> frameProvider,
- @Nullable TriConsumer<DisplayFrames, WindowContainer, Rect> imeFrameProvider) {
+ @Nullable SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>>
+ overrideFrameProviders) {
if (mWindowContainer != null) {
if (mControllable) {
mWindowContainer.setControllableInsetProvider(null);
@@ -166,8 +170,9 @@ abstract class InsetsSourceProvider {
ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "InsetsSource setWin %s for type %s",
windowContainer, InsetsState.typeToString(mSource.getType()));
mWindowContainer = windowContainer;
+ // TODO: remove the frame provider for non-WindowState container.
mFrameProvider = frameProvider;
- mImeFrameProvider = imeFrameProvider;
+ mOverrideFrameProviders = overrideFrameProviders;
if (windowContainer == null) {
setServerVisible(false);
mSource.setVisibleFrame(null);
@@ -227,10 +232,25 @@ abstract class InsetsSourceProvider {
}
updateSourceFrameForServerVisibility();
- if (mImeFrameProvider != null) {
- mImeOverrideFrame.set(frame);
- mImeFrameProvider.accept(mWindowContainer.getDisplayContent().mDisplayFrames,
- mWindowContainer, mImeOverrideFrame);
+ if (mOverrideFrameProviders != null) {
+ for (int i = mOverrideFrameProviders.size() - 1; i >= 0; i--) {
+ final int windowType = mOverrideFrameProviders.keyAt(i);
+ final Rect overrideFrame;
+ if (mOverrideFrames.contains(windowType)) {
+ overrideFrame = mOverrideFrames.get(windowType);
+ overrideFrame.set(frame);
+ } else {
+ overrideFrame = new Rect(frame);
+ }
+ final TriConsumer<DisplayFrames, WindowContainer, Rect> provider =
+ mOverrideFrameProviders.get(windowType);
+ if (provider != null) {
+ mOverrideFrameProviders.get(windowType).accept(
+ mWindowContainer.getDisplayContent().mDisplayFrames, mWindowContainer,
+ overrideFrame);
+ }
+ mOverrideFrames.put(windowType, overrideFrame);
+ }
}
if (win.mGivenVisibleInsets.left != 0 || win.mGivenVisibleInsets.top != 0
@@ -290,7 +310,22 @@ abstract class InsetsSourceProvider {
&& windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) {
windowState.applyWithNextDraw(mSetLeashPositionConsumer);
} else {
- mSetLeashPositionConsumer.accept(mWindowContainer.getSyncTransaction());
+ Transaction t = mWindowContainer.getSyncTransaction();
+ if (windowState != null) {
+ // Make the buffer, token transformation, and leash position to be updated
+ // together when the window is drawn for new rotation. Otherwise the window
+ // may be outside the screen by the inconsistent orientations.
+ final AsyncRotationController rotationController =
+ mDisplayContent.getAsyncRotationController();
+ if (rotationController != null) {
+ final Transaction drawT =
+ rotationController.getDrawTransaction(windowState.mToken);
+ if (drawT != null) {
+ t = drawT;
+ }
+ }
+ }
+ mSetLeashPositionConsumer.accept(t);
}
}
if (mServerVisible && !mLastSourceFrame.equals(mSource.getFrame())) {
@@ -310,17 +345,15 @@ abstract class InsetsSourceProvider {
}
private Point getWindowFrameSurfacePosition() {
- WindowState win = mWindowContainer.asWindowState();
- if (mControl != null) {
- final AsyncRotationController controller =
- win.mDisplayContent.getAsyncRotationController();
+ final WindowState win = mWindowContainer.asWindowState();
+ if (win != null && mControl != null) {
+ final AsyncRotationController controller = mDisplayContent.getAsyncRotationController();
if (controller != null && controller.shouldFreezeInsetsPosition(win)) {
- // Use previous position because the fade-out animation runs in old rotation.
+ // Use previous position because the window still shows with old rotation.
return mControl.getSurfacePosition();
}
}
- final Rect frame = mWindowContainer.asWindowState() != null
- ? mWindowContainer.asWindowState().getFrame() : mWindowContainer.getBounds();
+ final Rect frame = win != null ? win.getFrame() : mWindowContainer.getBounds();
final Point position = new Point();
mWindowContainer.transformFrameToSurfacePosition(frame.left, frame.top, position);
return position;
@@ -500,12 +533,13 @@ abstract class InsetsSourceProvider {
if (mWindowContainer.asWindowState() == null) {
return false;
}
- final int[] provides = ((WindowState) mWindowContainer).mAttrs.providesInsetsTypes;
- if (provides == null) {
+ final InsetsFrameProvider[] providers =
+ ((WindowState) mWindowContainer).mAttrs.providedInsets;
+ if (providers == null) {
return false;
}
- for (int i = 0; i < provides.length; i++) {
- if (provides[i] == ITYPE_IME) {
+ for (int i = 0; i < providers.length; i++) {
+ if (providers[i].type == ITYPE_IME) {
return true;
}
}
@@ -537,32 +571,30 @@ abstract class InsetsSourceProvider {
return mClientVisible;
}
- /**
- * @return Whether this provider uses a different frame to dispatch to the IME.
- */
- boolean overridesImeFrame() {
- return mImeFrameProvider != null;
+ boolean overridesFrame(int windowType) {
+ return mOverrideFrames.contains(windowType);
}
- /**
- * @return Rect to dispatch to the IME as frame. Only valid if {@link #overridesImeFrame()}
- * returns {@code true}.
- */
- Rect getImeOverrideFrame() {
- return mImeOverrideFrame;
+ Rect getOverriddenFrame(int windowType) {
+ return mOverrideFrames.get(windowType);
}
public void dump(PrintWriter pw, String prefix) {
pw.println(prefix + getClass().getSimpleName());
prefix = prefix + " ";
pw.print(prefix + "mSource="); mSource.dump("", pw);
+ pw.print(prefix + "mSourceFrame=");
+ pw.println(mSourceFrame);
+ if (mOverrideFrames.size() > 0) {
+ pw.print(prefix + "mOverrideFrames=");
+ pw.println(mOverrideFrames);
+ }
if (mControl != null) {
pw.print(prefix + "mControl=");
mControl.dump("", pw);
}
pw.print(prefix);
pw.print("mIsLeashReadyForDispatching="); pw.print(mIsLeashReadyForDispatching);
- pw.print(" mImeOverrideFrame="); pw.print(mImeOverrideFrame.toShortString());
pw.println();
if (mWindowContainer != null) {
pw.print(prefix + "mWindowContainer=");
@@ -606,7 +638,6 @@ abstract class InsetsSourceProvider {
if (mAdapter != null && mAdapter.mCapturedLeash != null) {
mAdapter.mCapturedLeash.dumpDebug(proto, CAPTURED_LEASH);
}
- mImeOverrideFrame.dumpDebug(proto, IME_OVERRIDDEN_FRAME);
proto.write(IS_LEASH_READY_FOR_DISPATCHING, mIsLeashReadyForDispatching);
proto.write(CLIENT_VISIBLE, mClientVisible);
proto.write(SERVER_VISIBLE, mServerVisible);
diff --git a/services/core/java/com/android/server/wm/KeyguardController.java b/services/core/java/com/android/server/wm/KeyguardController.java
index 01c94142f46b..d76f6be93aeb 100644
--- a/services/core/java/com/android/server/wm/KeyguardController.java
+++ b/services/core/java/com/android/server/wm/KeyguardController.java
@@ -41,6 +41,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
import static com.android.server.wm.KeyguardControllerProto.AOD_SHOWING;
+import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_GOING_AWAY;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_PER_DISPLAY;
import static com.android.server.wm.KeyguardControllerProto.KEYGUARD_SHOWING;
@@ -72,6 +73,8 @@ class KeyguardController {
static final String KEYGUARD_SLEEP_TOKEN_TAG = "keyguard";
+ private static final int DEFER_WAKE_TRANSITION_TIMEOUT_MS = 5000;
+
private final ActivityTaskSupervisor mTaskSupervisor;
private WindowManagerService mWindowManager;
@@ -79,7 +82,7 @@ class KeyguardController {
private final ActivityTaskManagerService mService;
private RootWindowContainer mRootWindowContainer;
private final ActivityTaskManagerInternal.SleepTokenAcquirer mSleepTokenAcquirer;
-
+ private boolean mWaitingForWakeTransition;
KeyguardController(ActivityTaskManagerService service,
ActivityTaskSupervisor taskSupervisor) {
@@ -167,10 +170,14 @@ class KeyguardController {
final KeyguardDisplayState state = getDisplayState(displayId);
final boolean aodChanged = aodShowing != state.mAodShowing;
+ final boolean aodRemoved = state.mAodShowing && !aodShowing;
// If keyguard is going away, but SystemUI aborted the transition, need to reset state.
- // Do not reset keyguardChanged status if this is aodChanged.
+ // Do not reset keyguardChanged status when only AOD is removed.
final boolean keyguardChanged = (keyguardShowing != state.mKeyguardShowing)
- || (state.mKeyguardGoingAway && keyguardShowing && !aodChanged);
+ || (state.mKeyguardGoingAway && keyguardShowing && !aodRemoved);
+ if (aodRemoved) {
+ updateDeferWakeTransition(false /* waiting */);
+ }
if (!keyguardChanged && !aodChanged) {
setWakeTransitionReady();
return;
@@ -199,10 +206,6 @@ class KeyguardController {
state.mKeyguardShowing = keyguardShowing;
state.mAodShowing = aodShowing;
- if (aodChanged) {
- // Ensure the new state takes effect.
- mWindowManager.mWindowPlacerLocked.performSurfacePlacement();
- }
if (keyguardChanged) {
// Irrelevant to AOD.
@@ -220,6 +223,10 @@ class KeyguardController {
mRootWindowContainer.ensureActivitiesVisible(null, 0, !PRESERVE_WINDOWS);
InputMethodManagerInternal.get().updateImeWindowStatus(false /* disableImeIcon */);
setWakeTransitionReady();
+ if (aodChanged) {
+ // Ensure the new state takes effect.
+ mWindowManager.mWindowPlacerLocked.performSurfacePlacement();
+ }
}
private void setWakeTransitionReady() {
@@ -526,6 +533,33 @@ class KeyguardController {
}
}
+ private final Runnable mResetWaitTransition = () -> {
+ synchronized (mWindowManager.mGlobalLock) {
+ updateDeferWakeTransition(false /* waiting */);
+ }
+ };
+
+ void updateDeferWakeTransition(boolean waiting) {
+ if (waiting == mWaitingForWakeTransition) {
+ return;
+ }
+ if (!mWindowManager.mAtmService.getTransitionController().isShellTransitionsEnabled()) {
+ return;
+ }
+ // if aod is showing, defer the wake transition until aod state changed.
+ if (waiting && isAodShowing(DEFAULT_DISPLAY)) {
+ mWaitingForWakeTransition = true;
+ mWindowManager.mAtmService.getTransitionController().deferTransitionReady();
+ mWindowManager.mH.postDelayed(mResetWaitTransition, DEFER_WAKE_TRANSITION_TIMEOUT_MS);
+ } else if (!waiting) {
+ // dismiss the deferring if the aod state change or cancel awake.
+ mWaitingForWakeTransition = false;
+ mWindowManager.mAtmService.getTransitionController().continueTransitionReady();
+ mWindowManager.mH.removeCallbacks(mResetWaitTransition);
+ }
+ }
+
+
/** Represents Keyguard state per individual display. */
private static class KeyguardDisplayState {
private final int mDisplayId;
@@ -594,6 +628,7 @@ class KeyguardController {
} else if (top.canShowWhenLocked()) {
mTopOccludesActivity = top;
}
+ top.mDismissKeyguard = false;
// Only the top activity may control occluded, as we can't occlude the Keyguard
// if the top app doesn't want to occlude it.
@@ -671,6 +706,7 @@ class KeyguardController {
proto.write(KeyguardPerDisplayProto.KEYGUARD_SHOWING, mKeyguardShowing);
proto.write(KeyguardPerDisplayProto.AOD_SHOWING, mAodShowing);
proto.write(KeyguardPerDisplayProto.KEYGUARD_OCCLUDED, mOccluded);
+ proto.write(KeyguardPerDisplayProto.KEYGUARD_GOING_AWAY, mKeyguardGoingAway);
proto.end(token);
}
}
@@ -691,6 +727,7 @@ class KeyguardController {
final long token = proto.start(fieldId);
proto.write(AOD_SHOWING, default_state.mAodShowing);
proto.write(KEYGUARD_SHOWING, default_state.mKeyguardShowing);
+ proto.write(KEYGUARD_GOING_AWAY, default_state.mKeyguardGoingAway);
writeDisplayStatesToProto(proto, KEYGUARD_PER_DISPLAY);
proto.end(token);
}
diff --git a/services/core/java/com/android/server/wm/Letterbox.java b/services/core/java/com/android/server/wm/Letterbox.java
index 40df02c176e5..b5eff41d4f62 100644
--- a/services/core/java/com/android/server/wm/Letterbox.java
+++ b/services/core/java/com/android/server/wm/Letterbox.java
@@ -25,7 +25,6 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
import android.os.InputConfig;
-import android.os.Process;
import android.view.GestureDetector;
import android.view.InputChannel;
import android.view.InputEvent;
@@ -72,7 +71,8 @@ public class Letterbox {
private final LetterboxSurface mFullWindowSurface = new LetterboxSurface("fullWindow");
private final LetterboxSurface[] mSurfaces = { mLeft, mTop, mRight, mBottom };
// Reachability gestures.
- private final IntConsumer mDoubleTapCallback;
+ private final IntConsumer mDoubleTapCallbackX;
+ private final IntConsumer mDoubleTapCallbackY;
/**
* Constructs a Letterbox.
@@ -86,7 +86,8 @@ public class Letterbox {
Supplier<Boolean> hasWallpaperBackgroundSupplier,
Supplier<Integer> blurRadiusSupplier,
Supplier<Float> darkScrimAlphaSupplier,
- IntConsumer doubleTapCallback) {
+ IntConsumer doubleTapCallbackX,
+ IntConsumer doubleTapCallbackY) {
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
mAreCornersRounded = areCornersRounded;
@@ -94,7 +95,8 @@ public class Letterbox {
mHasWallpaperBackgroundSupplier = hasWallpaperBackgroundSupplier;
mBlurRadiusSupplier = blurRadiusSupplier;
mDarkScrimAlphaSupplier = darkScrimAlphaSupplier;
- mDoubleTapCallback = doubleTapCallback;
+ mDoubleTapCallbackX = doubleTapCallbackX;
+ mDoubleTapCallbackY = doubleTapCallbackY;
}
/**
@@ -264,7 +266,8 @@ public class Letterbox {
@Override
public boolean onDoubleTapEvent(MotionEvent e) {
if (e.getAction() == MotionEvent.ACTION_UP) {
- mDoubleTapCallback.accept((int) e.getX());
+ mDoubleTapCallbackX.accept((int) e.getX());
+ mDoubleTapCallbackY.accept((int) e.getY());
return true;
}
return false;
@@ -293,8 +296,8 @@ public class Letterbox {
mWindowHandle.token = mToken;
mWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
mWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
- mWindowHandle.ownerPid = Process.myPid();
- mWindowHandle.ownerUid = Process.myUid();
+ mWindowHandle.ownerPid = WindowManagerService.MY_PID;
+ mWindowHandle.ownerUid = WindowManagerService.MY_UID;
mWindowHandle.scaleFactor = 1.0f;
mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.SLIPPERY;
}
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index d02ad992c7e8..91b2fb63a543 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -22,7 +22,6 @@ import android.content.Context;
import android.graphics.Color;
import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -56,25 +55,48 @@ final class LetterboxConfiguration {
static final int LETTERBOX_BACKGROUND_WALLPAPER = 3;
/**
- * Enum for Letterbox reachability position types.
+ * Enum for Letterbox horizontal reachability position types.
*
* <p>Order from left to right is important since it's used in {@link
* #movePositionForReachabilityToNextRightStop} and {@link
* #movePositionForReachabilityToNextLeftStop}.
*/
@Retention(RetentionPolicy.SOURCE)
- @IntDef({LETTERBOX_REACHABILITY_POSITION_LEFT, LETTERBOX_REACHABILITY_POSITION_CENTER,
- LETTERBOX_REACHABILITY_POSITION_RIGHT})
- @interface LetterboxReachabilityPosition {};
+ @IntDef({LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT,
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER,
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT})
+ @interface LetterboxHorizontalReachabilityPosition {};
/** Letterboxed app window is aligned to the left side. */
- static final int LETTERBOX_REACHABILITY_POSITION_LEFT = 0;
+ static final int LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT = 0;
/** Letterboxed app window is positioned in the horizontal center. */
- static final int LETTERBOX_REACHABILITY_POSITION_CENTER = 1;
+ static final int LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER = 1;
/** Letterboxed app window is aligned to the right side. */
- static final int LETTERBOX_REACHABILITY_POSITION_RIGHT = 2;
+ static final int LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT = 2;
+
+ /**
+ * Enum for Letterbox vertical reachability position types.
+ *
+ * <p>Order from top to bottom is important since it's used in {@link
+ * #movePositionForReachabilityToNextBottomStop} and {@link
+ * #movePositionForReachabilityToNextTopStop}.
+ */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP,
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER,
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM})
+ @interface LetterboxVerticalReachabilityPosition {};
+
+ /** Letterboxed app window is aligned to the left side. */
+ static final int LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP = 0;
+
+ /** Letterboxed app window is positioned in the vertical center. */
+ static final int LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER = 1;
+
+ /** Letterboxed app window is aligned to the right side. */
+ static final int LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM = 2;
final Context mContext;
@@ -82,6 +104,9 @@ final class LetterboxConfiguration {
// MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
private float mFixedOrientationLetterboxAspectRatio;
+ // Default min aspect ratio for unresizable apps that are eligible for the size compat mode.
+ private float mDefaultMinAspectRatioForUnresizableApps;
+
// Corners radius for activities presented in the letterbox mode, values < 0 will be ignored.
private int mLetterboxActivityCornersRadius;
@@ -107,29 +132,57 @@ final class LetterboxConfiguration {
// side of the screen and 1.0 to the right side.
private float mLetterboxHorizontalPositionMultiplier;
- // Default horizontal position the letterboxed app window when reachability is enabled and
- // an app is fullscreen in landscape device orientatio.
- // It is used as a starting point for mLetterboxPositionForReachability.
- @LetterboxReachabilityPosition
- private int mDefaultPositionForReachability;
+ // Vertical position of a center of the letterboxed app window. 0 corresponds to the top
+ // side of the screen and 1.0 to the bottom side.
+ private float mLetterboxVerticalPositionMultiplier;
+
+ // Default horizontal position the letterboxed app window when horizontal reachability is
+ // enabled and an app is fullscreen in landscape device orientation.
+ // It is used as a starting point for mLetterboxPositionForHorizontalReachability.
+ @LetterboxHorizontalReachabilityPosition
+ private int mDefaultPositionForHorizontalReachability;
+
+ // Default vertical position the letterboxed app window when vertical reachability is enabled
+ // and an app is fullscreen in portrait device orientation.
+ // It is used as a starting point for mLetterboxPositionForVerticalReachability.
+ @LetterboxVerticalReachabilityPosition
+ private int mDefaultPositionForVerticalReachability;
+
+ // Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in
+ // landscape device orientation.
+ private boolean mIsHorizontalReachabilityEnabled;
+
+ // Whether vertical reachability repositioning is allowed for letterboxed fullscreen apps in
+ // portrait device orientation.
+ private boolean mIsVerticalReachabilityEnabled;
- // Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
- // device orientation.
- private boolean mIsReachabilityEnabled;
// Horizontal position of a center of the letterboxed app window which is global to prevent
// "jumps" when switching between letterboxed apps. It's updated to reposition the app window
// in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
// LetterboxUiController#getHorizontalPositionMultiplier which is called from
- // ActivityRecord#updateResolvedBoundsHorizontalPosition.
+ // ActivityRecord#updateResolvedBoundsPosition.
+ // TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
+ // Overview after changing position in another app.
+ @LetterboxHorizontalReachabilityPosition
+ private volatile int mLetterboxPositionForHorizontalReachability;
+
+ // Vertical position of a center of the letterboxed app window which is global to prevent
+ // "jumps" when switching between letterboxed apps. It's updated to reposition the app window
+ // in response to a double tap gesture (see LetterboxUiController#handleDoubleTap). Used in
+ // LetterboxUiController#getVerticalPositionMultiplier which is called from
+ // ActivityRecord#updateResolvedBoundsPosition.
// TODO(b/199426138): Global reachability setting causes a jump when resuming an app from
// Overview after changing position in another app.
- @LetterboxReachabilityPosition
- private volatile int mLetterboxPositionForReachability;
+ @LetterboxVerticalReachabilityPosition
+ private volatile int mLetterboxPositionForVerticalReachability;
// Whether education is allowed for letterboxed fullscreen apps.
private boolean mIsEducationEnabled;
+ // Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
+ private boolean mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+
LetterboxConfiguration(Context systemUiContext) {
mContext = systemUiContext;
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
@@ -143,12 +196,24 @@ final class LetterboxConfiguration {
R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
R.dimen.config_letterboxHorizontalPositionMultiplier);
- mIsReachabilityEnabled = mContext.getResources().getBoolean(
- R.bool.config_letterboxIsReachabilityEnabled);
- mDefaultPositionForReachability = readLetterboxReachabilityPositionFromConfig(mContext);
- mLetterboxPositionForReachability = mDefaultPositionForReachability;
+ mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
+ R.dimen.config_letterboxVerticalPositionMultiplier);
+ mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsHorizontalReachabilityEnabled);
+ mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsVerticalReachabilityEnabled);
+ mDefaultPositionForHorizontalReachability =
+ readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
+ mDefaultPositionForVerticalReachability =
+ readLetterboxVerticalReachabilityPositionFromConfig(mContext);
+ mLetterboxPositionForHorizontalReachability = mDefaultPositionForHorizontalReachability;
+ mLetterboxPositionForVerticalReachability = mDefaultPositionForVerticalReachability;
mIsEducationEnabled = mContext.getResources().getBoolean(
R.bool.config_letterboxIsEducationEnabled);
+ setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
+ R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
+ mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
}
/**
@@ -157,7 +222,6 @@ final class LetterboxConfiguration {
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio} will be ignored and
* the framework implementation will be used to determine the aspect ratio.
*/
- @VisibleForTesting
void setFixedOrientationLetterboxAspectRatio(float aspectRatio) {
mFixedOrientationLetterboxAspectRatio = aspectRatio;
}
@@ -166,7 +230,6 @@ final class LetterboxConfiguration {
* Resets the aspect ratio of letterbox for fixed orientation to {@link
* com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}.
*/
- @VisibleForTesting
void resetFixedOrientationLetterboxAspectRatio() {
mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio);
@@ -180,6 +243,47 @@ final class LetterboxConfiguration {
}
/**
+ * Resets the min aspect ratio for unresizable apps that are eligible for size compat mode.
+ */
+ void resetDefaultMinAspectRatioForUnresizableApps() {
+ setDefaultMinAspectRatioForUnresizableApps(mContext.getResources().getFloat(
+ R.dimen.config_letterboxDefaultMinAspectRatioForUnresizableApps));
+ }
+
+ /**
+ * Gets the min aspect ratio for unresizable apps that are eligible for size compat mode.
+ */
+ float getDefaultMinAspectRatioForUnresizableApps() {
+ return mDefaultMinAspectRatioForUnresizableApps;
+ }
+
+ /**
+ * Overrides the min aspect ratio for unresizable apps that are eligible for size compat mode.
+ */
+ void setDefaultMinAspectRatioForUnresizableApps(float aspectRatio) {
+ mDefaultMinAspectRatioForUnresizableApps = aspectRatio;
+ }
+
+ /**
+ * Overrides corners raidus for activities presented in the letterbox mode. If given value < 0,
+ * both it and a value of {@link
+ * com.android.internal.R.integer.config_letterboxActivityCornersRadius} will be ignored and
+ * corners of the activity won't be rounded.
+ */
+ void setLetterboxActivityCornersRadius(int cornersRadius) {
+ mLetterboxActivityCornersRadius = cornersRadius;
+ }
+
+ /**
+ * Resets corners raidus for activities presented in the letterbox mode to {@link
+ * com.android.internal.R.integer.config_letterboxActivityCornersRadius}.
+ */
+ void resetLetterboxActivityCornersRadius() {
+ mLetterboxActivityCornersRadius = mContext.getResources().getInteger(
+ com.android.internal.R.integer.config_letterboxActivityCornersRadius);
+ }
+
+ /**
* Whether corners of letterboxed activities are rounded.
*/
boolean isLetterboxActivityCornersRounded() {
@@ -210,6 +314,34 @@ final class LetterboxConfiguration {
return Color.valueOf(mContext.getResources().getColor(colorId));
}
+
+ /**
+ * Sets color of letterbox background which is used when {@link
+ * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+ * fallback for other backfround types.
+ */
+ void setLetterboxBackgroundColor(Color color) {
+ mLetterboxBackgroundColorOverride = color;
+ }
+
+ /**
+ * Sets color ID of letterbox background which is used when {@link
+ * #getLetterboxBackgroundType()} is {@link #LETTERBOX_BACKGROUND_SOLID_COLOR} or as
+ * fallback for other backfround types.
+ */
+ void setLetterboxBackgroundColorResourceId(int colorId) {
+ mLetterboxBackgroundColorResourceIdOverride = colorId;
+ }
+
+ /**
+ * Resets color of letterbox background to {@link
+ * com.android.internal.R.color.config_letterboxBackgroundColor}.
+ */
+ void resetLetterboxBackgroundColor() {
+ mLetterboxBackgroundColorOverride = null;
+ mLetterboxBackgroundColorResourceIdOverride = null;
+ }
+
/**
* Gets {@link LetterboxBackgroundType} specified in {@link
* com.android.internal.R.integer.config_letterboxBackgroundType} or over via ADB command.
@@ -219,6 +351,19 @@ final class LetterboxConfiguration {
return mLetterboxBackgroundType;
}
+ /** Sets letterbox background type. */
+ void setLetterboxBackgroundType(@LetterboxBackgroundType int backgroundType) {
+ mLetterboxBackgroundType = backgroundType;
+ }
+
+ /**
+ * Resets cletterbox background type to {@link
+ * com.android.internal.R.integer.config_letterboxBackgroundType}.
+ */
+ void resetLetterboxBackgroundType() {
+ mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
+ }
+
/** Returns a string representing the given {@link LetterboxBackgroundType}. */
static String letterboxBackgroundTypeToString(
@LetterboxBackgroundType int backgroundType) {
@@ -248,6 +393,27 @@ final class LetterboxConfiguration {
}
/**
+ * Overrides alpha of a black scrim shown over wallpaper for {@link
+ * #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link mLetterboxBackgroundType}.
+ *
+ * <p>If given value is < 0 or >= 1, both it and a value of {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha} are ignored
+ * and 0.0 (transparent) is instead.
+ */
+ void setLetterboxBackgroundWallpaperDarkScrimAlpha(float alpha) {
+ mLetterboxBackgroundWallpaperDarkScrimAlpha = alpha;
+ }
+
+ /**
+ * Resets alpha of a black scrim shown over wallpaper letterbox background to {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha}.
+ */
+ void resetLetterboxBackgroundWallpaperDarkScrimAlpha() {
+ mLetterboxBackgroundWallpaperDarkScrimAlpha = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha);
+ }
+
+ /**
* Gets alpha of a black scrim shown over wallpaper letterbox background.
*/
float getLetterboxBackgroundWallpaperDarkScrimAlpha() {
@@ -255,6 +421,28 @@ final class LetterboxConfiguration {
}
/**
+ * Overrides blur radius for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in
+ * {@link mLetterboxBackgroundType}.
+ *
+ * <p> If given value <= 0, both it and a value of {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius} are ignored
+ * and 0 is used instead.
+ */
+ void setLetterboxBackgroundWallpaperBlurRadius(int radius) {
+ mLetterboxBackgroundWallpaperBlurRadius = radius;
+ }
+
+ /**
+ * Resets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
+ * mLetterboxBackgroundType} to {@link
+ * com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius}.
+ */
+ void resetLetterboxBackgroundWallpaperBlurRadius() {
+ mLetterboxBackgroundWallpaperBlurRadius = mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.config_letterboxBackgroundWallpaperBlurRadius);
+ }
+
+ /**
* Gets blur raidus for {@link #LETTERBOX_BACKGROUND_WALLPAPER} option in {@link
* mLetterboxBackgroundType}.
*/
@@ -275,72 +463,187 @@ final class LetterboxConfiguration {
? 0.5f : mLetterboxHorizontalPositionMultiplier;
}
+ /*
+ * Gets vertical position of a center of the letterboxed app window specified
+ * in {@link com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier}
+ * or via an ADB command. 0 corresponds to the top side of the screen and 1 to the
+ * bottom side.
+ */
+ float getLetterboxVerticalPositionMultiplier() {
+ return (mLetterboxVerticalPositionMultiplier < 0.0f
+ || mLetterboxVerticalPositionMultiplier > 1.0f)
+ // Default to central position if invalid value is provided.
+ ? 0.5f : mLetterboxVerticalPositionMultiplier;
+ }
+
/**
* Overrides horizontal position of a center of the letterboxed app window. If given value < 0
* or > 1, then it and a value of {@link
* com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier} are ignored and
* central position (0.5) is used.
*/
- @VisibleForTesting
void setLetterboxHorizontalPositionMultiplier(float multiplier) {
mLetterboxHorizontalPositionMultiplier = multiplier;
}
/**
+ * Overrides vertical position of a center of the letterboxed app window. If given value < 0
+ * or > 1, then it and a value of {@link
+ * com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier} are ignored and
+ * central position (0.5) is used.
+ */
+ void setLetterboxVerticalPositionMultiplier(float multiplier) {
+ mLetterboxVerticalPositionMultiplier = multiplier;
+ }
+
+ /**
* Resets horizontal position of a center of the letterboxed app window to {@link
* com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier}.
*/
- @VisibleForTesting
void resetLetterboxHorizontalPositionMultiplier() {
mLetterboxHorizontalPositionMultiplier = mContext.getResources().getFloat(
com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier);
}
+ /**
+ * Resets vertical position of a center of the letterboxed app window to {@link
+ * com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier}.
+ */
+ void resetLetterboxVerticalPositionMultiplier() {
+ mLetterboxVerticalPositionMultiplier = mContext.getResources().getFloat(
+ com.android.internal.R.dimen.config_letterboxVerticalPositionMultiplier);
+ }
+
+ /*
+ * Whether horizontal reachability repositioning is allowed for letterboxed fullscreen apps in
+ * landscape device orientation.
+ */
+ boolean getIsHorizontalReachabilityEnabled() {
+ return mIsHorizontalReachabilityEnabled;
+ }
+
/*
- * Whether reachability repositioning is allowed for letterboxed fullscreen apps in landscape
- * device orientation.
+ * Whether vertical reachability repositioning is allowed for letterboxed fullscreen apps in
+ * portrait device orientation.
*/
- boolean getIsReachabilityEnabled() {
- return mIsReachabilityEnabled;
+ boolean getIsVerticalReachabilityEnabled() {
+ return mIsVerticalReachabilityEnabled;
}
/**
- * Overrides whether reachability repositioning is allowed for letterboxed fullscreen apps in
- * landscape device orientation.
+ * Overrides whether horizontal reachability repositioning is allowed for letterboxed fullscreen
+ * apps in landscape device orientation.
+ */
+ void setIsHorizontalReachabilityEnabled(boolean enabled) {
+ mIsHorizontalReachabilityEnabled = enabled;
+ }
+
+ /**
+ * Overrides whether vertical reachability repositioning is allowed for letterboxed fullscreen
+ * apps in portrait device orientation.
+ */
+ void setIsVerticalReachabilityEnabled(boolean enabled) {
+ mIsVerticalReachabilityEnabled = enabled;
+ }
+
+ /**
+ * Resets whether horizontal reachability repositioning is allowed for letterboxed fullscreen
+ * apps in landscape device orientation to
+ * {@link R.bool.config_letterboxIsHorizontalReachabilityEnabled}.
*/
- @VisibleForTesting
- void setIsReachabilityEnabled(boolean enabled) {
- mIsReachabilityEnabled = enabled;
+ void resetIsHorizontalReachabilityEnabled() {
+ mIsHorizontalReachabilityEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsHorizontalReachabilityEnabled);
}
/**
- * Resets whether reachability repositioning is allowed for letterboxed fullscreen apps in
- * landscape device orientation to {@link R.bool.config_letterboxIsReachabilityEnabled}.
+ * Resets whether vertical reachability repositioning is allowed for letterboxed fullscreen apps
+ * in portrait device orientation to
+ * {@link R.bool.config_letterboxIsVerticalReachabilityEnabled}.
*/
- @VisibleForTesting
- void resetIsReachabilityEnabled() {
- mIsReachabilityEnabled = mContext.getResources().getBoolean(
- R.bool.config_letterboxIsReachabilityEnabled);
+ void resetIsVerticalReachabilityEnabled() {
+ mIsVerticalReachabilityEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsVerticalReachabilityEnabled);
}
/*
- * Gets default horizontal position of the letterboxed app window when reachability is enabled.
- * Specified in {@link R.integer.config_letterboxDefaultPositionForReachability} or via an ADB
- * command.
+ * Gets default horizontal position of the letterboxed app window when horizontal reachability
+ * is enabled.
+ *
+ * <p> Specified in {@link R.integer.config_letterboxDefaultPositionForHorizontalReachability}
+ * or via an ADB command.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getDefaultPositionForHorizontalReachability() {
+ return mDefaultPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets default vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ *
+ * <p> Specified in {@link R.integer.config_letterboxDefaultPositionForVerticalReachability} or
+ * via an ADB command.
*/
- @LetterboxReachabilityPosition
- int getDefaultPositionForReachability() {
- return mDefaultPositionForReachability;
+ @LetterboxVerticalReachabilityPosition
+ int getDefaultPositionForVerticalReachability() {
+ return mDefaultPositionForVerticalReachability;
}
- @LetterboxReachabilityPosition
- private static int readLetterboxReachabilityPositionFromConfig(Context context) {
+ /**
+ * Overrides default horizontal position of the letterboxed app window when horizontal
+ * reachability is enabled.
+ */
+ void setDefaultPositionForHorizontalReachability(
+ @LetterboxHorizontalReachabilityPosition int position) {
+ mDefaultPositionForHorizontalReachability = position;
+ }
+
+ /**
+ * Overrides default vertical position of the letterboxed app window when vertical
+ * reachability is enabled.
+ */
+ void setDefaultPositionForVerticalReachability(
+ @LetterboxVerticalReachabilityPosition int position) {
+ mDefaultPositionForVerticalReachability = position;
+ }
+
+ /**
+ * Resets default horizontal position of the letterboxed app window when horizontal reachability
+ * is enabled to {@link R.integer.config_letterboxDefaultPositionForHorizontalReachability}.
+ */
+ void resetDefaultPositionForHorizontalReachability() {
+ mDefaultPositionForHorizontalReachability =
+ readLetterboxHorizontalReachabilityPositionFromConfig(mContext);
+ }
+
+ /**
+ * Resets default vertical position of the letterboxed app window when vertical reachability
+ * is enabled to {@link R.integer.config_letterboxDefaultPositionForVerticalReachability}.
+ */
+ void resetDefaultPositionForVerticalReachability() {
+ mDefaultPositionForVerticalReachability =
+ readLetterboxVerticalReachabilityPositionFromConfig(mContext);
+ }
+
+ @LetterboxHorizontalReachabilityPosition
+ private static int readLetterboxHorizontalReachabilityPositionFromConfig(Context context) {
+ int position = context.getResources().getInteger(
+ R.integer.config_letterboxDefaultPositionForHorizontalReachability);
+ return position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT
+ || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ || position == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT
+ ? position : LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+ }
+
+ @LetterboxVerticalReachabilityPosition
+ private static int readLetterboxVerticalReachabilityPositionFromConfig(Context context) {
int position = context.getResources().getInteger(
- R.integer.config_letterboxDefaultPositionForReachability);
- return position == LETTERBOX_REACHABILITY_POSITION_LEFT
- || position == LETTERBOX_REACHABILITY_POSITION_CENTER
- || position == LETTERBOX_REACHABILITY_POSITION_RIGHT
- ? position : LETTERBOX_REACHABILITY_POSITION_CENTER;
+ R.integer.config_letterboxDefaultPositionForVerticalReachability);
+ return position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP
+ || position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ || position == LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM
+ ? position : LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
}
/*
@@ -350,48 +653,126 @@ final class LetterboxConfiguration {
* <p>The position multiplier is changed after each double tap in the letterbox area.
*/
float getHorizontalMultiplierForReachability() {
- switch (mLetterboxPositionForReachability) {
- case LETTERBOX_REACHABILITY_POSITION_LEFT:
+ switch (mLetterboxPositionForHorizontalReachability) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return 0.0f;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return 0.5f;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return 1.0f;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: "
+ + mLetterboxPositionForHorizontalReachability);
+ }
+ }
+ /*
+ * Gets vertical position of a center of the letterboxed app window when reachability
+ * is enabled specified. 0 corresponds to the top side of the screen and 1 to the bottom side.
+ *
+ * <p>The position multiplier is changed after each double tap in the letterbox area.
+ */
+ float getVerticalMultiplierForReachability() {
+ switch (mLetterboxPositionForVerticalReachability) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
return 0.0f;
- case LETTERBOX_REACHABILITY_POSITION_CENTER:
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
return 0.5f;
- case LETTERBOX_REACHABILITY_POSITION_RIGHT:
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
return 1.0f;
default:
throw new AssertionError(
- "Unexpected letterbox position type: " + mLetterboxPositionForReachability);
+ "Unexpected letterbox position type: "
+ + mLetterboxPositionForVerticalReachability);
}
}
- /** Returns a string representing the given {@link LetterboxReachabilityPosition}. */
- static String letterboxReachabilityPositionToString(
- @LetterboxReachabilityPosition int position) {
+ /*
+ * Gets the horizontal position of the letterboxed app window when horizontal reachability is
+ * enabled.
+ */
+ @LetterboxHorizontalReachabilityPosition
+ int getLetterboxPositionForHorizontalReachability() {
+ return mLetterboxPositionForHorizontalReachability;
+ }
+
+ /*
+ * Gets the vertical position of the letterboxed app window when vertical reachability is
+ * enabled.
+ */
+ @LetterboxVerticalReachabilityPosition
+ int getLetterboxPositionForVerticalReachability() {
+ return mLetterboxPositionForVerticalReachability;
+ }
+
+ /** Returns a string representing the given {@link LetterboxHorizontalReachabilityPosition}. */
+ static String letterboxHorizontalReachabilityPositionToString(
+ @LetterboxHorizontalReachabilityPosition int position) {
switch (position) {
- case LETTERBOX_REACHABILITY_POSITION_LEFT:
- return "LETTERBOX_REACHABILITY_POSITION_LEFT";
- case LETTERBOX_REACHABILITY_POSITION_CENTER:
- return "LETTERBOX_REACHABILITY_POSITION_CENTER";
- case LETTERBOX_REACHABILITY_POSITION_RIGHT:
- return "LETTERBOX_REACHABILITY_POSITION_RIGHT";
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return "LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT";
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return "LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER";
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return "LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT";
default:
throw new AssertionError(
"Unexpected letterbox position type: " + position);
}
}
+ /** Returns a string representing the given {@link LetterboxVerticalReachabilityPosition}. */
+ static String letterboxVerticalReachabilityPositionToString(
+ @LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return "LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP";
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return "LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER";
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return "LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM";
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox position type: " + position);
+ }
+ }
+
+ /**
+ * Changes letterbox position for horizontal reachability to the next available one on the
+ * right side.
+ */
+ void movePositionForHorizontalReachabilityToNextRightStop() {
+ mLetterboxPositionForHorizontalReachability = Math.min(
+ mLetterboxPositionForHorizontalReachability + 1,
+ LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT);
+ }
+
+ /**
+ * Changes letterbox position for horizontal reachability to the next available one on the left
+ * side.
+ */
+ void movePositionForHorizontalReachabilityToNextLeftStop() {
+ mLetterboxPositionForHorizontalReachability =
+ Math.max(mLetterboxPositionForHorizontalReachability - 1, 0);
+ }
+
/**
- * Changes letterbox position for reachability to the next available one on the right side.
+ * Changes letterbox position for vertical reachability to the next available one on the bottom
+ * side.
*/
- void movePositionForReachabilityToNextRightStop() {
- mLetterboxPositionForReachability = Math.min(
- mLetterboxPositionForReachability + 1, LETTERBOX_REACHABILITY_POSITION_RIGHT);
+ void movePositionForVerticalReachabilityToNextBottomStop() {
+ mLetterboxPositionForVerticalReachability = Math.min(
+ mLetterboxPositionForVerticalReachability + 1,
+ LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM);
}
/**
- * Changes letterbox position for reachability to the next available one on the left side.
+ * Changes letterbox position for vertical reachability to the next available one on the top
+ * side.
*/
- void movePositionForReachabilityToNextLeftStop() {
- mLetterboxPositionForReachability = Math.max(mLetterboxPositionForReachability - 1, 0);
+ void movePositionForVerticalReachabilityToNextTopStop() {
+ mLetterboxPositionForVerticalReachability =
+ Math.max(mLetterboxPositionForVerticalReachability - 1, 0);
}
/**
@@ -404,8 +785,41 @@ final class LetterboxConfiguration {
/**
* Overrides whether education is allowed for letterboxed fullscreen apps.
*/
- @VisibleForTesting
void setIsEducationEnabled(boolean enabled) {
mIsEducationEnabled = enabled;
}
+
+ /**
+ * Resets whether education is allowed for letterboxed fullscreen apps to
+ * {@link R.bool.config_letterboxIsEducationEnabled}.
+ */
+ void resetIsEducationEnabled() {
+ mIsEducationEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsEducationEnabled);
+ }
+
+ /**
+ * Whether using split screen aspect ratio as a default aspect ratio for unresizable apps.
+ */
+ boolean getIsSplitScreenAspectRatioForUnresizableAppsEnabled() {
+ return mIsSplitScreenAspectRatioForUnresizableAppsEnabled;
+ }
+
+ /**
+ * Overrides whether using split screen aspect ratio as a default aspect ratio for unresizable
+ * apps.
+ */
+ void setIsSplitScreenAspectRatioForUnresizableAppsEnabled(boolean enabled) {
+ mIsSplitScreenAspectRatioForUnresizableAppsEnabled = enabled;
+ }
+
+ /**
+ * Resets whether using split screen aspect ratio as a default aspect ratio for unresizable
+ * apps {@link R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled}.
+ */
+ void resetIsSplitScreenAspectRatioForUnresizableAppsEnabled() {
+ mIsSplitScreenAspectRatioForUnresizableAppsEnabled = mContext.getResources().getBoolean(
+ R.bool.config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled);
+ }
+
}
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index bb15d76c3bac..d65276793700 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -21,6 +21,20 @@ import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+import static com.android.internal.util.FrameworkStatsLog.APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+import static com.android.internal.util.FrameworkStatsLog.LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
import static com.android.server.wm.ActivityRecord.computeAspectRatio;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -28,6 +42,13 @@ import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+import static com.android.server.wm.LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
import static com.android.server.wm.LetterboxConfiguration.letterboxBackgroundTypeToString;
import android.annotation.Nullable;
@@ -163,7 +184,8 @@ final class LetterboxUiController {
this::hasWallpaperBackgroudForLetterbox,
this::getLetterboxWallpaperBlurRadius,
this::getLetterboxWallpaperDarkScrimAlpha,
- this::handleDoubleTap);
+ this::handleHorizontalDoubleTap,
+ this::handleVerticalDoubleTap);
mLetterbox.attachInput(w);
}
mActivityRecord.getPosition(mTmpPoint);
@@ -193,18 +215,36 @@ final class LetterboxUiController {
float getHorizontalPositionMultiplier(Configuration parentConfiguration) {
// Don't check resolved configuration because it may not be updated yet during
// configuration change.
- return isReachabilityEnabled(parentConfiguration)
+ return isHorizontalReachabilityEnabled(parentConfiguration)
// Using the last global dynamic position to avoid "jumps" when moving
// between apps or activities.
? mLetterboxConfiguration.getHorizontalMultiplierForReachability()
: mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier();
}
- float getFixedOrientationLetterboxAspectRatio(Configuration parentConfiguration) {
- // Don't check resolved windowing mode because it may not be updated yet during
+ float getVerticalPositionMultiplier(Configuration parentConfiguration) {
+ // Don't check resolved configuration because it may not be updated yet during
// configuration change.
- if (!isReachabilityEnabled(parentConfiguration)) {
- return mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ return isVerticalReachabilityEnabled(parentConfiguration)
+ // Using the last global dynamic position to avoid "jumps" when moving
+ // between apps or activities.
+ ? mLetterboxConfiguration.getVerticalMultiplierForReachability()
+ : mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier();
+ }
+
+ float getFixedOrientationLetterboxAspectRatio() {
+ return mActivityRecord.shouldCreateCompatDisplayInsets()
+ ? getDefaultMinAspectRatioForUnresizableApps()
+ : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
+ }
+
+ private float getDefaultMinAspectRatioForUnresizableApps() {
+ if (!mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()
+ || mActivityRecord.getDisplayContent() == null) {
+ return mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+ > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+ ? mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+ : mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio();
}
int dividerWindowWidth =
@@ -214,10 +254,14 @@ final class LetterboxUiController {
int dividerSize = dividerWindowWidth - dividerInsets * 2;
// Getting the same aspect ratio that apps get in split screen.
- Rect bounds = new Rect(parentConfiguration.windowConfiguration.getAppBounds());
- bounds.inset(dividerSize, /* dy */ 0);
- bounds.right = bounds.centerX();
-
+ Rect bounds = new Rect(mActivityRecord.getDisplayContent().getBounds());
+ if (bounds.width() >= bounds.height()) {
+ bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
+ bounds.right = bounds.centerX();
+ } else {
+ bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
+ bounds.bottom = bounds.centerY();
+ }
return computeAspectRatio(bounds);
}
@@ -225,8 +269,8 @@ final class LetterboxUiController {
return mActivityRecord.mWmService.mContext.getResources();
}
- private void handleDoubleTap(int x) {
- if (!isReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ private void handleHorizontalDoubleTap(int x) {
+ if (!isHorizontalReachabilityEnabled() || mActivityRecord.isInTransition()) {
return;
}
@@ -235,12 +279,61 @@ final class LetterboxUiController {
return;
}
+ int letterboxPositionForHorizontalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForHorizontalReachability();
if (mLetterbox.getInnerFrame().left > x) {
// Moving to the next stop on the left side of the app window: right > center > left.
- mLetterboxConfiguration.movePositionForReachabilityToNextLeftStop();
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextLeftStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_LEFT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__RIGHT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
} else if (mLetterbox.getInnerFrame().right < x) {
// Moving to the next stop on the right side of the app window: left > center > right.
- mLetterboxConfiguration.movePositionForReachabilityToNextRightStop();
+ mLetterboxConfiguration.movePositionForHorizontalReachabilityToNextRightStop();
+ int changeToLog =
+ letterboxPositionForHorizontalReachability
+ == LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_RIGHT
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__LEFT_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
+ }
+
+ // TODO(197549949): Add animation for transition.
+ mActivityRecord.recomputeConfiguration();
+ }
+
+ private void handleVerticalDoubleTap(int y) {
+ if (!isVerticalReachabilityEnabled() || mActivityRecord.isInTransition()) {
+ return;
+ }
+
+ if (mLetterbox.getInnerFrame().top <= y && mLetterbox.getInnerFrame().bottom >= y) {
+ // Only react to clicks at the top and bottom of the letterboxed app window.
+ return;
+ }
+ int letterboxPositionForVerticalReachability = mLetterboxConfiguration
+ .getLetterboxPositionForVerticalReachability();
+ if (mLetterbox.getInnerFrame().top > y) {
+ // Moving to the next stop on the top side of the app window: bottom > center > top.
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextTopStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_TOP
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__BOTTOM_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
+ } else if (mLetterbox.getInnerFrame().bottom < y) {
+ // Moving to the next stop on the bottom side of the app window: top > center > bottom.
+ mLetterboxConfiguration.movePositionForVerticalReachabilityToNextBottomStop();
+ int changeToLog =
+ letterboxPositionForVerticalReachability
+ == LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER
+ ? LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__CENTER_TO_BOTTOM
+ : LETTERBOX_POSITION_CHANGED__POSITION_CHANGE__TOP_TO_CENTER;
+ logLetterboxPositionChange(changeToLog);
}
// TODO(197549949): Add animation for transition.
@@ -248,25 +341,47 @@ final class LetterboxUiController {
}
/**
- * Whether reachability is enabled for an activity in the curren configuration.
+ * Whether horizontal reachability is enabled for an activity in the current configuration.
*
* <p>Conditions that needs to be met:
* <ul>
* <li>Activity is portrait-only.
* <li>Fullscreen window in landscape device orientation.
- * <li>Reachability is enabled.
+ * <li>Horizontal Reachability is enabled.
+ * </ul>
+ */
+ private boolean isHorizontalReachabilityEnabled(Configuration parentConfiguration) {
+ return mLetterboxConfiguration.getIsHorizontalReachabilityEnabled()
+ && parentConfiguration.windowConfiguration.getWindowingMode()
+ == WINDOWING_MODE_FULLSCREEN
+ && (parentConfiguration.orientation == ORIENTATION_LANDSCAPE
+ && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT);
+ }
+
+ private boolean isHorizontalReachabilityEnabled() {
+ return isHorizontalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ }
+
+ /**
+ * Whether vertical reachability is enabled for an activity in the current configuration.
+ *
+ * <p>Conditions that needs to be met:
+ * <ul>
+ * <li>Activity is landscape-only.
+ * <li>Fullscreen window in portrait device orientation.
+ * <li>Vertical Reachability is enabled.
* </ul>
*/
- private boolean isReachabilityEnabled(Configuration parentConfiguration) {
- return mLetterboxConfiguration.getIsReachabilityEnabled()
+ private boolean isVerticalReachabilityEnabled(Configuration parentConfiguration) {
+ return mLetterboxConfiguration.getIsVerticalReachabilityEnabled()
&& parentConfiguration.windowConfiguration.getWindowingMode()
== WINDOWING_MODE_FULLSCREEN
- && parentConfiguration.orientation == ORIENTATION_LANDSCAPE
- && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_PORTRAIT;
+ && (parentConfiguration.orientation == ORIENTATION_PORTRAIT
+ && mActivityRecord.getRequestedConfigurationOrientation() == ORIENTATION_LANDSCAPE);
}
- private boolean isReachabilityEnabled() {
- return isReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
+ private boolean isVerticalReachabilityEnabled() {
+ return isVerticalReachabilityEnabled(mActivityRecord.getParent().getConfiguration());
}
@VisibleForTesting
@@ -474,12 +589,19 @@ final class LetterboxUiController {
+ getLetterboxWallpaperBlurRadius());
}
- pw.println(prefix + " isReachabilityEnabled=" + isReachabilityEnabled());
+ pw.println(prefix + " isHorizontalReachabilityEnabled="
+ + isHorizontalReachabilityEnabled());
+ pw.println(prefix + " isVerticalReachabilityEnabled=" + isVerticalReachabilityEnabled());
pw.println(prefix + " letterboxHorizontalPositionMultiplier="
+ getHorizontalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
+ pw.println(prefix + " letterboxVerticalPositionMultiplier="
+ + getVerticalPositionMultiplier(mActivityRecord.getParent().getConfiguration()));
pw.println(prefix + " fixedOrientationLetterboxAspectRatio="
- + getFixedOrientationLetterboxAspectRatio(
- mActivityRecord.getParent().getConfiguration()));
+ + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
+ pw.println(prefix + " defaultMinAspectRatioForUnresizableApps="
+ + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps());
+ pw.println(prefix + " isSplitScreenAspectRatioForUnresizableAppsEnabled="
+ + mLetterboxConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
}
/**
@@ -496,7 +618,69 @@ final class LetterboxUiController {
if (mainWin.isLetterboxedForDisplayCutout()) {
return "DISPLAY_CUTOUT";
}
+ if (mActivityRecord.isAspectRatioApplied()) {
+ return "ASPECT_RATIO";
+ }
return "UNKNOWN_REASON";
}
+ private int letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxHorizontalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__LEFT;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__RIGHT;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox horizontal reachability position type: "
+ + position);
+ }
+ }
+
+ private int letterboxVerticalReachabilityPositionToLetterboxPosition(
+ @LetterboxConfiguration.LetterboxVerticalReachabilityPosition int position) {
+ switch (position) {
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__TOP;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__CENTER;
+ case LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM:
+ return APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__BOTTOM;
+ default:
+ throw new AssertionError(
+ "Unexpected letterbox vertical reachability position type: "
+ + position);
+ }
+ }
+
+ int getLetterboxPositionForLogging() {
+ int positionToLog = APP_COMPAT_STATE_CHANGED__LETTERBOX_POSITION__UNKNOWN_POSITION;
+ if (isHorizontalReachabilityEnabled()) {
+ int letterboxPositionForHorizontalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForHorizontalReachability();
+ positionToLog = letterboxHorizontalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForHorizontalReachability);
+ } else if (isVerticalReachabilityEnabled()) {
+ int letterboxPositionForVerticalReachability = getLetterboxConfiguration()
+ .getLetterboxPositionForVerticalReachability();
+ positionToLog = letterboxVerticalReachabilityPositionToLetterboxPosition(
+ letterboxPositionForVerticalReachability);
+ }
+ return positionToLog;
+ }
+
+ private LetterboxConfiguration getLetterboxConfiguration() {
+ return mLetterboxConfiguration;
+ }
+
+ /**
+ * Logs letterbox position changes via {@link ActivityMetricsLogger#logLetterboxPositionChange}.
+ */
+ private void logLetterboxPositionChange(int letterboxPositionChange) {
+ mActivityRecord.mTaskSupervisor.getActivityMetricsLogger()
+ .logLetterboxPositionChange(mActivityRecord, letterboxPositionChange);
+ }
}
diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
index 9c1d5601dad5..64749cf94ddf 100644
--- a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
+++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java
@@ -22,17 +22,21 @@ import static com.android.internal.R.bool.config_unfoldTransitionEnabled;
import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY;
import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.HandlerExecutor;
+import android.window.DisplayAreaInfo;
import android.window.TransitionRequestInfo;
+import android.window.WindowContainerTransaction;
public class PhysicalDisplaySwitchTransitionLauncher {
private final DisplayContent mDisplayContent;
+ private final WindowManagerService mService;
private final DeviceStateManager mDeviceStateManager;
- private final Context mContext;
private final TransitionController mTransitionController;
private DeviceStateListener mDeviceStateListener;
@@ -46,13 +50,13 @@ public class PhysicalDisplaySwitchTransitionLauncher {
public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent,
TransitionController transitionController) {
mDisplayContent = displayContent;
- mContext = mDisplayContent.mWmService.mContext;
+ mService = displayContent.mWmService;
mTransitionController = transitionController;
- mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class);
+ mDeviceStateManager = mService.mContext.getSystemService(DeviceStateManager.class);
if (mDeviceStateManager != null) {
- mDeviceStateListener = new DeviceStateListener(mContext);
+ mDeviceStateListener = new DeviceStateListener(mService.mContext);
mDeviceStateManager
.registerCallback(new HandlerExecutor(mDisplayContent.mWmService.mH),
mDeviceStateListener);
@@ -74,7 +78,7 @@ public class PhysicalDisplaySwitchTransitionLauncher {
if (!mDisplayContent.getLastHasContent()) return;
boolean shouldRequestUnfoldTransition = !mIsFolded
- && mContext.getResources().getBoolean(config_unfoldTransitionEnabled)
+ && mService.mContext.getResources().getBoolean(config_unfoldTransitionEnabled)
&& ValueAnimator.areAnimatorsEnabled();
if (!shouldRequestUnfoldTransition) {
@@ -97,16 +101,47 @@ public class PhysicalDisplaySwitchTransitionLauncher {
if (t != null) {
mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY);
- mTransitionController.collectForDisplayChange(mDisplayContent, t);
+ mTransitionController.collectForDisplayAreaChange(mDisplayContent, t);
mTransition = t;
}
}
- public void onDisplayUpdated() {
- if (mTransition != null) {
- mTransition.setAllReady();
- mTransition = null;
+ /**
+ * Called when physical display is getting updated, this could happen e.g. on foldable
+ * devices when the physical underlying display is replaced.
+ *
+ * @param fromRotation rotation before the display change
+ * @param toRotation rotation after the display change
+ * @param newDisplayAreaInfo display area info after the display change
+ */
+ public void onDisplayUpdated(int fromRotation, int toRotation,
+ @NonNull DisplayAreaInfo newDisplayAreaInfo) {
+ if (mTransition == null) return;
+
+ final boolean started = mDisplayContent.mRemoteDisplayChangeController
+ .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo,
+ this::continueDisplayUpdate);
+
+ if (!started) {
+ markTransitionAsReady();
+ }
+ }
+
+ private void continueDisplayUpdate(@Nullable WindowContainerTransaction transaction) {
+ if (mTransition == null) return;
+
+ if (transaction != null) {
+ mService.mAtmService.mWindowOrganizerController.applyTransaction(transaction);
}
+
+ markTransitionAsReady();
+ }
+
+ private void markTransitionAsReady() {
+ if (mTransition == null) return;
+
+ mTransition.setAllReady();
+ mTransition = null;
}
class DeviceStateListener extends DeviceStateManager.FoldStateListener {
diff --git a/services/core/java/com/android/server/wm/RecentsAnimation.java b/services/core/java/com/android/server/wm/RecentsAnimation.java
index eca201dc2bda..ffe3374e6658 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimation.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimation.java
@@ -203,9 +203,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
final LaunchingState launchingState =
mTaskSupervisor.getActivityMetricsLogger().notifyActivityLaunching(mTargetIntent);
- if (mCaller != null) {
- mCaller.setRunningRecentsAnimation(true);
- }
+ setProcessAnimating(true);
mService.deferWindowLayout();
try {
@@ -409,15 +407,33 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
if (mWindowManager.mRoot.isLayoutNeeded()) {
mWindowManager.mRoot.performSurfacePlacement();
}
- if (mCaller != null) {
- mCaller.setRunningRecentsAnimation(false);
- }
+ setProcessAnimating(false);
Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
}
});
}
}
+ /** Gives the owner of recents animation higher priority. */
+ private void setProcessAnimating(boolean animating) {
+ if (mCaller == null) return;
+ // Apply the top-app scheduling group to who runs the animation.
+ mCaller.setRunningRecentsAnimation(animating);
+ int demoteReasons = mService.mDemoteTopAppReasons;
+ if (animating) {
+ demoteReasons |= ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
+ } else {
+ demoteReasons &= ~ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS;
+ }
+ mService.mDemoteTopAppReasons = demoteReasons;
+ // Make the demotion of the real top app take effect. No need to restore top app state for
+ // finishing recents because addToStopping -> scheduleIdle -> activityIdleInternal ->
+ // trimApplications will have a full update.
+ if (animating && mService.mTopApp != null) {
+ mService.mTopApp.scheduleUpdateOomAdj();
+ }
+ }
+
@Override
public void onAnimationFinished(@RecentsAnimationController.ReorderMode int reorderMode,
boolean sendUserLeaveHint) {
@@ -504,7 +520,7 @@ class RecentsAnimation implements RecentsAnimationCallbacks, OnRootTaskOrderChan
}
private boolean matchesTarget(Task task) {
- return task.mUserId == mUserId
+ return task.getNonFinishingActivityCount() > 0 && task.mUserId == mUserId
&& task.getBaseIntent().getComponent().equals(mTargetIntent.getComponent());
}
}
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index fe0ab2b39909..53f1fe6abec5 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -70,10 +70,7 @@ import android.window.PictureInPictureSurfaceTransaction;
import android.window.TaskSnapshot;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.os.BackgroundThread;
import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.LatencyTracker;
import com.android.internal.util.function.pooled.PooledConsumer;
import com.android.internal.util.function.pooled.PooledLambda;
import com.android.server.LocalServices;
@@ -99,11 +96,6 @@ import java.util.stream.Collectors;
public class RecentsAnimationController implements DeathRecipient {
private static final String TAG = RecentsAnimationController.class.getSimpleName();
private static final long FAILSAFE_DELAY = 1000;
- /**
- * If the recents animation is canceled before the delay since the window drawn, do not log the
- * action because the duration is too small that may be just a mistouch.
- */
- private static final long LATENCY_TRACKER_LOG_DELAY_MS = 300;
// Constant for a yet-to-be-calculated {@link RemoteAnimationTarget#Mode} state
private static final int MODE_UNKNOWN = -1;
@@ -144,7 +136,7 @@ public class RecentsAnimationController implements DeathRecipient {
private boolean mPendingStart = true;
// Set when the animation has been canceled
- private volatile boolean mCanceled;
+ private boolean mCanceled;
// Whether or not the input consumer is enabled. The input consumer must be both registered and
// enabled for it to start intercepting touch events.
@@ -333,26 +325,6 @@ public class RecentsAnimationController implements DeathRecipient {
}
}
InputMethodManagerInternal.get().maybeFinishStylusHandwriting();
- if (!behindSystemBars) {
- // Hiding IME if IME window is not attached to app.
- // Since some windowing mode is not proper to snapshot Task with IME window
- // while the app transitioning to the next task (e.g. split-screen mode)
- if (!mDisplayContent.isImeAttachedToApp()) {
- final InputMethodManagerInternal inputMethodManagerInternal =
- LocalServices.getService(InputMethodManagerInternal.class);
- if (inputMethodManagerInternal != null) {
- inputMethodManagerInternal.hideCurrentInputMethod(
- SoftInputShowHideReason.HIDE_RECENTS_ANIMATION);
- }
- } else {
- // Disable IME icon explicitly when IME attached to the app in case
- // IME icon might flickering while swiping to the next app task still
- // in animating before the next app window focused, or IME icon
- // persists on the bottom when swiping the task to recents.
- InputMethodManagerInternal.get().updateImeWindowStatus(
- true /* disableImeIcon */);
- }
- }
mService.mWindowPlacerLocked.requestTraversal();
}
} finally {
@@ -380,10 +352,6 @@ public class RecentsAnimationController implements DeathRecipient {
}
}
- // TODO(b/166736352): Remove this method without the need to expose to launcher.
- @Override
- public void hideCurrentInputMethod() { }
-
@Override
public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
synchronized (mService.mGlobalLock) {
@@ -785,15 +753,6 @@ public class RecentsAnimationController implements DeathRecipient {
}, false /* traverseTopToBottom */);
}
- void logRecentsAnimationStartTime(int durationMs) {
- BackgroundThread.getHandler().postDelayed(() -> {
- if (!mCanceled) {
- mService.mLatencyTracker.logAction(LatencyTracker.ACTION_START_RECENTS_ANIMATION,
- durationMs);
- }
- }, LATENCY_TRACKER_LOG_DELAY_MS);
- }
-
private boolean removeTaskInternal(int taskId) {
boolean result = false;
for (int i = mPendingAnimations.size() - 1; i >= 0; i--) {
@@ -1335,16 +1294,16 @@ public class RecentsAnimationController implements DeathRecipient {
mDisplayContent.mPinnedTaskController.setEnterPipTransaction(
mFinishTransaction);
}
- mFinishTransaction = null;
- mFinishOverlay = null;
- pendingTransaction.apply();
-
- // In the case where we are transferring the transform to the task in preparation
+ // In the case where we are transferring the transform to the task in preparation
// for entering PIP, we disable the task being able to affect sysui flags otherwise
// it may cause a flash
- if (mTask.getActivityType() != mTargetActivityType) {
+ if (mTask.getActivityType() != mTargetActivityType
+ && mFinishTransaction.getShouldDisableCanAffectSystemUiFlags()) {
mTask.setCanAffectSystemUiFlags(false);
}
+ mFinishTransaction = null;
+ mFinishOverlay = null;
+ pendingTransaction.apply();
} else if (!mTask.isAttached()) {
// Apply the task's pending transaction in case it is detached and its transaction
// is not reachable.
diff --git a/services/core/java/com/android/server/wm/RemoteAnimationController.java b/services/core/java/com/android/server/wm/RemoteAnimationController.java
index 35cc5e30cacf..8db5289f8b45 100644
--- a/services/core/java/com/android/server/wm/RemoteAnimationController.java
+++ b/services/core/java/com/android/server/wm/RemoteAnimationController.java
@@ -22,6 +22,7 @@ import static com.android.server.wm.RemoteAnimationAdapterWrapperProto.TARGET;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import android.annotation.ColorInt;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Point;
@@ -57,7 +58,7 @@ import java.util.function.Consumer;
*/
class RemoteAnimationController implements DeathRecipient {
private static final String TAG = TAG_WITH_CLASS_NAME
- ? "RemoteAnimationController" : TAG_WM;
+ ? "RemoteAnimationController" : TAG_WM;
private static final long TIMEOUT_MS = 10000;
private final WindowManagerService mService;
@@ -73,35 +74,40 @@ class RemoteAnimationController implements DeathRecipient {
private boolean mIsFinishing;
private FinishedCallback mFinishedCallback;
+ private final boolean mIsActivityEmbedding;
private boolean mCanceled;
private boolean mLinkedToDeathOfRunner;
@Nullable
private Runnable mOnRemoteAnimationReady;
RemoteAnimationController(WindowManagerService service, DisplayContent displayContent,
- RemoteAnimationAdapter remoteAnimationAdapter, Handler handler) {
+ RemoteAnimationAdapter remoteAnimationAdapter, Handler handler,
+ boolean isActivityEmbedding) {
mService = service;
mDisplayContent = displayContent;
mRemoteAnimationAdapter = remoteAnimationAdapter;
mHandler = handler;
+ mIsActivityEmbedding = isActivityEmbedding;
}
/**
* Creates an animation record for each individual {@link WindowContainer}.
*
* @param windowContainer The windows to animate.
- * @param position The position app bounds relative to its parent.
- * @param localBounds The bounds of the app relative to its parent.
- * @param endBounds The end bounds after the transition, in screen coordinates.
- * @param startBounds The start bounds before the transition, in screen coordinates.
+ * @param position The position app bounds relative to its parent.
+ * @param localBounds The bounds of the app relative to its parent.
+ * @param endBounds The end bounds after the transition, in screen coordinates.
+ * @param startBounds The start bounds before the transition, in screen coordinates.
+ * @param showBackdrop To show background behind a window during animation.
* @return The record representing animation(s) to run on the app.
*/
RemoteAnimationRecord createRemoteAnimationRecord(WindowContainer windowContainer,
- Point position, Rect localBounds, Rect endBounds, Rect startBounds) {
+ Point position, Rect localBounds, Rect endBounds, Rect startBounds,
+ boolean showBackdrop) {
ProtoLog.d(WM_DEBUG_REMOTE_ANIMATIONS, "createAnimationAdapter(): container=%s",
windowContainer);
final RemoteAnimationRecord adapters = new RemoteAnimationRecord(windowContainer, position,
- localBounds, endBounds, startBounds);
+ localBounds, endBounds, startBounds, showBackdrop);
mPendingAnimations.add(adapters);
return adapters;
}
@@ -112,6 +118,15 @@ class RemoteAnimationController implements DeathRecipient {
}
/**
+ * We use isFromActivityEmbedding() in the server process to tell if we're running an
+ * Activity Embedding type remote animation, where animations are driven by the client.
+ * This is currently supporting features like showBackdrop where we need to load App XML.
+ */
+ public boolean isFromActivityEmbedding() {
+ return mIsActivityEmbedding;
+ }
+
+ /**
* Called when the transition is ready to be started, and all leashes have been set up.
*/
void goodToGo(@WindowManager.TransitionOldType int transit) {
@@ -421,30 +436,37 @@ class RemoteAnimationController implements DeathRecipient {
RemoteAnimationTarget mTarget;
final WindowContainer mWindowContainer;
final Rect mStartBounds;
+ final boolean mShowBackdrop;
+ @ColorInt int mBackdropColor = 0;
private @RemoteAnimationTarget.Mode int mMode = RemoteAnimationTarget.MODE_CHANGING;
RemoteAnimationRecord(WindowContainer windowContainer, Point endPos, Rect localBounds,
- Rect endBounds, Rect startBounds) {
+ Rect endBounds, Rect startBounds, boolean showBackdrop) {
mWindowContainer = windowContainer;
+ mShowBackdrop = showBackdrop;
if (startBounds != null) {
mStartBounds = new Rect(startBounds);
mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
- mStartBounds);
+ mStartBounds, mShowBackdrop);
if (mRemoteAnimationAdapter.getChangeNeedsSnapshot()) {
final Rect thumbnailLocalBounds = new Rect(startBounds);
thumbnailLocalBounds.offsetTo(0, 0);
// Snapshot is located at (0,0) of the animation leash. It doesn't have size
// change, so the startBounds is its end bounds, and no start bounds for it.
mThumbnailAdapter = new RemoteAnimationAdapterWrapper(this, new Point(0, 0),
- thumbnailLocalBounds, startBounds, new Rect());
+ thumbnailLocalBounds, startBounds, new Rect(), mShowBackdrop);
}
} else {
mAdapter = new RemoteAnimationAdapterWrapper(this, endPos, localBounds, endBounds,
- new Rect());
+ new Rect(), mShowBackdrop);
mStartBounds = null;
}
}
+ void setBackDropColor(@ColorInt int backdropColor) {
+ mBackdropColor = backdropColor;
+ }
+
RemoteAnimationTarget createRemoteAnimationTarget() {
if (mAdapter == null
|| mAdapter.mCapturedFinishCallback == null
@@ -486,14 +508,27 @@ class RemoteAnimationController implements DeathRecipient {
final Rect mLocalBounds;
final Rect mEndBounds = new Rect();
final Rect mStartBounds = new Rect();
+ final boolean mShowBackdrop;
RemoteAnimationAdapterWrapper(RemoteAnimationRecord record, Point position,
- Rect localBounds, Rect endBounds, Rect startBounds) {
+ Rect localBounds, Rect endBounds, Rect startBounds, boolean showBackdrop) {
mRecord = record;
mPosition.set(position.x, position.y);
mLocalBounds = localBounds;
mEndBounds.set(endBounds);
mStartBounds.set(startBounds);
+ mShowBackdrop = showBackdrop;
+ }
+
+ @Override
+ @ColorInt
+ public int getBackgroundColor() {
+ return mRecord.mBackdropColor;
+ }
+
+ @Override
+ public boolean getShowBackground() {
+ return mShowBackdrop;
}
@Override
diff --git a/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
new file mode 100644
index 000000000000..43baebc7255a
--- /dev/null
+++ b/services/core/java/com/android/server/wm/RemoteDisplayChangeController.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.view.IDisplayChangeWindowCallback;
+import android.window.DisplayAreaInfo;
+import android.window.WindowContainerTransaction;
+
+import com.android.internal.protolog.common.ProtoLog;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A helper class, a wrapper around {@link android.view.IDisplayChangeWindowController} to perform
+ * a synchronous display change in other parts (e.g. in the Shell) and continue the process
+ * in the system server. It handles timeouts and multiple requests.
+ * We have an instance of this controller for each display.
+ */
+public class RemoteDisplayChangeController {
+
+ private static final String TAG = "RemoteDisplayChangeController";
+
+ private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800;
+
+ private final WindowManagerService mService;
+ private final int mDisplayId;
+
+ private final Runnable mTimeoutRunnable = this::onContinueTimedOut;
+
+ // all remote changes that haven't finished yet.
+ private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>();
+
+ public RemoteDisplayChangeController(WindowManagerService service, int displayId) {
+ mService = service;
+ mDisplayId = displayId;
+ }
+
+ /**
+ * A Remote change is when we are waiting for some registered (remote)
+ * {@link IDisplayChangeWindowController} to calculate and return some hierarchy operations
+ * to perform in sync with the display change.
+ */
+ public boolean isWaitingForRemoteDisplayChange() {
+ return !mCallbacks.isEmpty();
+ }
+
+ /**
+ * Starts remote display change
+ * @param fromRotation rotation before the change
+ * @param toRotation rotation after the change
+ * @param newDisplayAreaInfo display area info after change
+ * @param callback that will be called after completing remote display change
+ * @return true if the change successfully started, false otherwise
+ */
+ public boolean performRemoteDisplayChange(
+ int fromRotation, int toRotation,
+ @Nullable DisplayAreaInfo newDisplayAreaInfo,
+ ContinueRemoteDisplayChangeCallback callback) {
+ if (mService.mDisplayChangeController == null) {
+ return false;
+ }
+ mCallbacks.add(callback);
+
+ if (newDisplayAreaInfo != null) {
+ ProtoLog.v(WM_DEBUG_CONFIGURATION,
+ "Starting remote display change: "
+ + "from [rot = %d], "
+ + "to [%dx%d, rot = %d]",
+ fromRotation,
+ newDisplayAreaInfo.configuration.windowConfiguration
+ .getMaxBounds().width(),
+ newDisplayAreaInfo.configuration.windowConfiguration
+ .getMaxBounds().height(),
+ toRotation);
+ }
+
+ final IDisplayChangeWindowCallback remoteCallback = createCallback(callback);
+ try {
+ mService.mH.removeCallbacks(mTimeoutRunnable);
+ mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS);
+ mService.mDisplayChangeController.onDisplayChange(mDisplayId, fromRotation, toRotation,
+ newDisplayAreaInfo, remoteCallback);
+ return true;
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Exception while dispatching remote display-change", e);
+ mCallbacks.remove(callback);
+ return false;
+ }
+ }
+
+ private void onContinueTimedOut() {
+ Slog.e(TAG, "RemoteDisplayChange timed-out, UI might get messed-up after this.");
+ // timed-out, so run all continue callbacks and clear the list
+ synchronized (mService.mGlobalLock) {
+ for (int i = 0; i < mCallbacks.size(); ++i) {
+ mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
+ }
+ mCallbacks.clear();
+ }
+ }
+
+ private void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback,
+ @Nullable WindowContainerTransaction transaction) {
+ synchronized (mService.mGlobalLock) {
+ int idx = mCallbacks.indexOf(callback);
+ if (idx < 0) {
+ // already called this callback or a more-recent one (eg. via timeout)
+ return;
+ }
+ for (int i = 0; i < idx; ++i) {
+ // Expect remote callbacks in order. If they don't come in order, then force
+ // ordering by continuing everything up until this one with empty transactions.
+ mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */);
+ }
+ mCallbacks.subList(0, idx + 1).clear();
+ if (mCallbacks.isEmpty()) {
+ mService.mH.removeCallbacks(mTimeoutRunnable);
+ }
+ callback.onContinueRemoteDisplayChange(transaction);
+ }
+ }
+
+ private IDisplayChangeWindowCallback createCallback(
+ @NonNull ContinueRemoteDisplayChangeCallback callback) {
+ return new IDisplayChangeWindowCallback.Stub() {
+ @Override
+ public void continueDisplayChange(WindowContainerTransaction t) {
+ synchronized (mService.mGlobalLock) {
+ if (!mCallbacks.contains(callback)) {
+ // already ran this callback or a more-recent one.
+ return;
+ }
+ mService.mH.post(() -> RemoteDisplayChangeController.this
+ .continueDisplayChange(callback, t));
+ }
+ }
+ };
+ }
+
+ /**
+ * Callback interface to handle continuation of the remote display change
+ */
+ public interface ContinueRemoteDisplayChangeCallback {
+ /**
+ * This method is called when the remote display change has been applied
+ * @param transaction window changes collected by the remote display change
+ */
+ void onContinueRemoteDisplayChange(@Nullable WindowContainerTransaction transaction);
+ }
+}
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index ef18b50e910b..dfce40b874c0 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -37,6 +37,7 @@ import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_PIP;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_WAKE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_KEEP_SCREEN_ON;
@@ -1238,11 +1239,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
@Override
- void scheduleAnimation() {
- mWmService.scheduleAnimationLocked();
- }
-
- @Override
protected void removeChild(DisplayContent dc) {
super.removeChild(dc);
if (mTopFocusedDisplayId == dc.getDisplayId()) {
@@ -1977,23 +1973,28 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
@Nullable ActivityRecord launchIntoPipHostActivity, String reason) {
- mService.deferWindowLayout();
+ moveActivityToPinnedRootTask(r, launchIntoPipHostActivity, reason, null /* transition */);
+ }
+ void moveActivityToPinnedRootTask(@NonNull ActivityRecord r,
+ @Nullable ActivityRecord launchIntoPipHostActivity, String reason,
+ @Nullable Transition transition) {
final TaskDisplayArea taskDisplayArea = r.getDisplayArea();
+ final Task task = r.getTask();
+ final Task rootTask;
- try {
- final Task task = r.getTask();
-
- // Create a transition now to collect the current pinned Task dismiss. Only do the
- // create here as the Task (trigger) to enter PIP is not ready yet.
- final TransitionController transitionController = task.mTransitionController;
- Transition newTransition = null;
- if (transitionController.isCollecting()) {
- transitionController.setReady(task, false /* ready */);
- } else if (transitionController.getTransitionPlayer() != null) {
- newTransition = transitionController.createTransition(TRANSIT_PIP);
- }
+ Transition newTransition = transition;
+ // Create a transition now (if not provided) to collect the current pinned Task dismiss.
+ // Only do the create here as the Task (trigger) to enter PIP is not ready yet.
+ final TransitionController transitionController = task.mTransitionController;
+ if (newTransition == null && !transitionController.isCollecting()
+ && transitionController.getTransitionPlayer() != null) {
+ newTransition = transitionController.createTransition(TRANSIT_PIP);
+ }
+ transitionController.deferTransitionReady();
+ mService.deferWindowLayout();
+ try {
// This will change the root pinned task's windowing mode to its original mode, ensuring
// we only have one root task that is in pinned mode.
final Task rootPinnedTask = taskDisplayArea.getRootPinnedTask();
@@ -2011,7 +2012,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
final TaskFragment organizedTf = r.getOrganizedTaskFragment();
final boolean singleActivity = task.getNonFinishingActivityCount() == 1;
- final Task rootTask;
if (singleActivity) {
rootTask = task;
@@ -2044,6 +2044,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
task.mLastRecentsAnimationTransaction,
task.mLastRecentsAnimationOverlay);
task.clearLastRecentsAnimationTransaction(false /* forceRemoveOverlay */);
+ } else {
+ // Reset the original task surface
+ task.resetSurfaceControlTransforms();
}
// The organized TaskFragment is becoming empty because this activity is reparented
@@ -2077,6 +2080,9 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
oldTopActivity.mRequestForceTransition = true;
}
}
+
+ transitionController.collect(rootTask);
+
// The intermediate windowing mode to be set on the ActivityRecord later.
// This needs to happen before the re-parenting, otherwise we will always set the
// ActivityRecord to be fullscreen.
@@ -2087,13 +2093,6 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
rootTask.reparent(taskDisplayArea, true /* onTop */);
}
- // The new PIP Task is ready, start the transition before updating the windowing mode.
- if (newTransition != null) {
- transitionController.requestStartTransition(newTransition, rootTask,
- null /* remoteTransition */, null /* displayChange */);
- }
- transitionController.collect(rootTask);
-
// Defer the windowing mode change until after the transition to prevent the activity
// from doing work and changing the activity visuals while animating
// TODO(task-org): Figure-out more structured way to do this long term.
@@ -2119,7 +2118,7 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
// entering content-pip animation.
mWindowManager.mTaskSnapshotController.recordTaskSnapshot(
task, false /* allowSnapshotHome */);
- rootTask.setBounds(r.getOptions().getLaunchBounds());
+ rootTask.setBounds(r.pictureInPictureArgs.getSourceRectHint());
}
rootTask.setDeferTaskAppear(false);
@@ -2136,9 +2135,23 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
}
} finally {
mService.continueWindowLayout();
+ try {
+ ensureActivitiesVisible(null, 0, false /* preserveWindows */);
+ } finally {
+ transitionController.continueTransitionReady();
+ }
+ }
+
+ if (newTransition != null) {
+ // Request at end since we want task-organizer events from ensureActivitiesVisible
+ // to be recognized.
+ transitionController.requestStartTransition(newTransition, rootTask,
+ null /* remoteTransition */, null /* displayChange */);
+ // A new transition was created just for this operations. Since the operation is
+ // complete, mark it as ready.
+ newTransition.setReady(rootTask, true /* ready */);
}
- ensureActivitiesVisible(null, 0, false /* preserveWindows */);
resumeFocusedTasksTopActivities();
notifyActivityPipModeChanged(r.getTask(), r);
@@ -2321,6 +2334,14 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
continue;
}
+ // Prepare transition before resume top activity, so it can be collected.
+ if (!displayShouldSleep && display.isDefaultDisplay
+ && !display.getDisplayPolicy().isAwake()
+ && display.mTransitionController.isShellTransitionsEnabled()
+ && !display.mTransitionController.isCollecting()) {
+ display.mTransitionController.requestTransitionIfNeeded(TRANSIT_WAKE,
+ 0 /* flags */, null /* trigger */, display);
+ }
// Set the sleeping state of the root tasks on the display.
display.forAllRootTasks(rootTask -> {
if (displayShouldSleep) {
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index 9856ebf7dc2a..d92a1f495917 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -18,6 +18,7 @@ package com.android.server.wm;
import static android.Manifest.permission.CONTROL_KEYGUARD;
import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.STATUS_BAR_SERVICE;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
@@ -175,7 +176,7 @@ public class SafeActivityOptions {
if (adapter == null) {
return;
}
- if (callingPid == Process.myPid()) {
+ if (callingPid == WindowManagerService.MY_PID) {
Slog.wtf(TAG, "Safe activity options constructed after clearing calling id");
return;
}
@@ -249,6 +250,14 @@ public class SafeActivityOptions {
throw new SecurityException(msg);
}
}
+ if (options.getTransientLaunch() && !supervisor.mRecentTasks.isCallerRecents(callingUid)
+ && ActivityTaskManagerService.checkPermission(
+ MANAGE_ACTIVITY_TASKS, callingPid, callingUid) == PERMISSION_DENIED) {
+ final String msg = "Permission Denial: starting transient launch from " + callerApp
+ + ", pid=" + callingPid + ", uid=" + callingUid;
+ Slog.w(TAG, msg);
+ throw new SecurityException(msg);
+ }
// Check if the caller is allowed to launch on the specified display area.
final WindowContainerToken daToken = options.getLaunchTaskDisplayArea();
TaskDisplayArea taskDisplayArea = daToken != null
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index f31a3cc9bcb3..fd8b614de9b7 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -642,7 +642,7 @@ class ScreenRotationAnimation {
}
private SurfaceAnimator startDisplayRotation() {
- return startAnimation(initializeBuilder()
+ SurfaceAnimator animator = startAnimation(initializeBuilder()
.setAnimationLeashParent(mDisplayContent.getSurfaceControl())
.setSurfaceControl(mDisplayContent.getWindowingLayer())
.setParentSurfaceControl(mDisplayContent.getSurfaceControl())
@@ -651,6 +651,13 @@ class ScreenRotationAnimation {
.build(),
createWindowAnimationSpec(mRotateEnterAnimation),
this::onAnimationEnd);
+
+ // Crop the animation leash to avoid extended wallpaper from showing over
+ // mBackColorSurface
+ Rect displayBounds = mDisplayContent.getBounds();
+ mDisplayContent.getPendingTransaction()
+ .setWindowCrop(animator.mLeash, displayBounds.width(), displayBounds.height());
+ return animator;
}
private SurfaceAnimator startScreenshotAlphaAnimation() {
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 30b50839cd35..3577545088e0 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -19,6 +19,7 @@ package com.android.server.wm;
import static android.Manifest.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
import static android.Manifest.permission.HIDE_OVERLAY_WINDOWS;
import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
+import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
@@ -109,14 +110,13 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
final boolean mCanCreateSystemApplicationOverlay;
final boolean mCanHideNonSystemOverlayWindows;
+ final boolean mCanSetUnrestrictedGestureExclusion;
private AlertWindowNotification mAlertWindowNotification;
private boolean mShowingAlertWindowNotificationAllowed;
private boolean mClientDead = false;
private float mLastReportedAnimatorScale;
private String mPackageName;
private String mRelayoutTag;
- private String mUpdateViewVisibilityTag;
- private String mUpdateWindowLayoutTag;
private final InsetsVisibilities mDummyRequestedVisibilities = new InsetsVisibilities();
private final InsetsSourceControl[] mDummyControls = new InsetsSourceControl[0];
final boolean mSetsUnrestrictedKeepClearAreas;
@@ -141,6 +141,9 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
mSetsUnrestrictedKeepClearAreas =
service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_KEEP_CLEAR_AREAS)
== PERMISSION_GRANTED;
+ mCanSetUnrestrictedGestureExclusion =
+ service.mContext.checkCallingOrSelfPermission(SET_UNRESTRICTED_GESTURE_EXCLUSION)
+ == PERMISSION_GRANTED;
mShowingAlertWindowNotificationAllowed = mService.mShowAlertWindowNotifications;
mDragDropController = mService.mDragDropController;
StringBuilder sb = new StringBuilder();
@@ -195,27 +198,28 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), requestedVisibilities, outInputChannel, outInsetsState,
- outActiveControls);
+ outActiveControls, outAttachedFrame);
}
@Override
public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
- requestedVisibilities, outInputChannel, outInsetsState, outActiveControls);
+ requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
+ outAttachedFrame);
}
@Override
public int addToDisplayWithoutInputChannel(IWindow window, WindowManager.LayoutParams attrs,
- int viewVisibility, int displayId, InsetsState outInsetsState) {
+ int viewVisibility, int displayId, InsetsState outInsetsState, Rect outAttachedFrame) {
return mService.addWindow(this, window, attrs, viewVisibility, displayId,
UserHandle.getUserId(mUid), mDummyRequestedVisibilities, null /* outInputChannel */,
- outInsetsState, mDummyControls);
+ outInsetsState, mDummyControls, outAttachedFrame);
}
@Override
@@ -224,29 +228,13 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
}
@Override
- public int updateVisibility(IWindow client, WindowManager.LayoutParams attrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mUpdateViewVisibilityTag);
- int res = mService.updateViewVisibility(this, client, attrs, viewVisibility,
- outMergedConfiguration, outSurfaceControl, outInsetsState, outActiveControls);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
- return res;
- }
-
- @Override
- public void updateLayout(IWindow window, WindowManager.LayoutParams attrs, int flags,
- ClientWindowFrames clientFrames, int requestedWidth, int requestedHeight) {
- Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mUpdateWindowLayoutTag);
- mService.updateWindowLayout(this, window, attrs, flags, clientFrames, requestedWidth,
- requestedHeight);
- Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
+ public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
+ mService.setWillReplaceWindows(appToken, childrenOnly);
}
@Override
- public void prepareToReplaceWindows(IBinder appToken, boolean childrenOnly) {
- mService.setWillReplaceWindows(appToken, childrenOnly);
+ public boolean cancelDraw(IWindow window) {
+ return mService.cancelDraw(this, window);
}
@Override
@@ -711,8 +699,6 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
if (wpc != null) {
mPackageName = wpc.mInfo.packageName;
mRelayoutTag = "relayoutWindow: " + mPackageName;
- mUpdateViewVisibilityTag = "updateVisibility: " + mPackageName;
- mUpdateWindowLayoutTag = "updateLayout: " + mPackageName;
} else {
Slog.e(TAG_WM, "Unknown process pid=" + mPid);
}
diff --git a/services/core/java/com/android/server/wm/StartingSurfaceController.java b/services/core/java/com/android/server/wm/StartingSurfaceController.java
index f83173bd46c0..0bb773ae5e41 100644
--- a/services/core/java/com/android/server/wm/StartingSurfaceController.java
+++ b/services/core/java/com/android/server/wm/StartingSurfaceController.java
@@ -220,6 +220,11 @@ public class StartingSurfaceController {
// Attempt to add starting window from the top-most activity.
for (int i = mDeferringAddStartActivities.size() - 1; i >= 0; --i) {
final DeferringStartingWindowRecord next = mDeferringAddStartActivities.get(i);
+ if (next.mDeferring.getTask() == null) {
+ Slog.e(TAG, "No task exists: " + next.mDeferring.shortComponentName
+ + " parent: " + next.mDeferring.getParent());
+ continue;
+ }
next.mDeferring.showStartingWindow(next.mPrev, mInitNewTask, mInitTaskSwitch,
mInitProcessRunning, true /* startActivity */, next.mSource, topOptions);
// If one succeeds, it is done.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index bf5246f2339a..3b97e637a20a 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -67,16 +67,14 @@ import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN_BEHIND;
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.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
-import static com.android.internal.policy.DecorView.DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
-import static com.android.internal.policy.DecorView.DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_LOCKTASK;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RECENTS_ANIMATIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STATES;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TASKS;
-import static com.android.server.wm.ActivityRecord.State.INITIALIZING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -131,7 +129,6 @@ import static com.android.server.wm.WindowContainerChildProto.TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ROOT_TASK;
import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.dipToPixel;
import static java.lang.Integer.MAX_VALUE;
@@ -197,6 +194,7 @@ import android.window.WindowContainerToken;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.XmlUtils;
import com.android.internal.util.function.pooled.PooledConsumer;
@@ -426,9 +424,6 @@ class Task extends TaskFragment {
/** Helper object used for updating override configuration. */
private Configuration mTmpConfig = new Configuration();
- /** Used by fillTaskInfo */
- final TaskActivitiesReport mReuseActivitiesReport = new TaskActivitiesReport();
-
/* Unique identifier for this task. */
final int mTaskId;
/* User for which this task was created. */
@@ -743,8 +738,9 @@ class Task extends TaskFragment {
"removeTask:" + reason + " deferring removing taskId=" + mTaskId);
return;
}
+ final boolean isLeafTask = isLeafTask();
removeImmediately(reason);
- if (isLeafTask()) {
+ if (isLeafTask) {
mAtmService.getTaskChangeNotificationController().notifyTaskRemoved(mTaskId);
final TaskDisplayArea taskDisplayArea = getDisplayArea();
@@ -1404,15 +1400,6 @@ class Task extends TaskFragment {
}
/**
- * Return the number of running activities, and the number of non-finishing/initializing
- * activities in the provided {@param reportOut} respectively.
- */
- private void getNumRunningActivities(TaskActivitiesReport reportOut) {
- reportOut.reset();
- forAllActivities(reportOut);
- }
-
- /**
* Reorder the history task so that the passed activity is brought to the front.
*/
final void moveActivityToFrontLocked(ActivityRecord newTop) {
@@ -1707,23 +1694,6 @@ class Task extends TaskFragment {
lockTaskAuthToString());
}
- @Override
- public boolean supportsSplitScreenWindowingMode() {
- return supportsSplitScreenWindowingModeInDisplayArea(getDisplayArea());
- }
-
- boolean supportsSplitScreenWindowingModeInDisplayArea(@Nullable TaskDisplayArea tda) {
- final Task topTask = getTopMostTask();
- return super.supportsSplitScreenWindowingMode()
- && (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda));
- }
-
- private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) {
- return super.supportsSplitScreenWindowingMode()
- && mAtmService.mSupportsSplitScreenMultiWindow
- && supportsMultiWindowInDisplayArea(tda);
- }
-
boolean supportsFreeform() {
return supportsFreeformInDisplayArea(getDisplayArea());
}
@@ -1889,6 +1859,11 @@ class Task extends TaskFragment {
if (getRequestedOverrideWindowingMode() == WINDOWING_MODE_UNDEFINED) {
nextPersistTaskBounds = newParentConfig.windowConfiguration.persistTaskBounds();
}
+ // Only restore to the last non-fullscreen bounds when the requested override bounds
+ // have not been explicitly set already.
+ nextPersistTaskBounds &=
+ (getRequestedOverrideConfiguration().windowConfiguration.getBounds() == null
+ || getRequestedOverrideConfiguration().windowConfiguration.getBounds().isEmpty());
if (!prevPersistTaskBounds && nextPersistTaskBounds
&& mLastNonFullscreenBounds != null && !mLastNonFullscreenBounds.isEmpty()) {
// Bypass onRequestedOverrideConfigurationChanged here to avoid infinite loop.
@@ -2031,10 +2006,6 @@ class Task extends TaskFragment {
Rect outOverrideBounds = getResolvedOverrideConfiguration().windowConfiguration.getBounds();
if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
- if (!isOrganized()) {
- // Use empty bounds to indicate "fill parent".
- outOverrideBounds.setEmpty();
- }
// The bounds for fullscreen mode shouldn't be adjusted by minimal size. Otherwise if
// the parent or display is smaller than the size, the content may be cropped.
return;
@@ -3309,7 +3280,6 @@ class Task extends TaskFragment {
}
final SurfaceControl.Transaction t = getSyncTransaction();
- updateShadowsRadius(isFocused(), t);
if (mDimmer.updateDims(t, mTmpDimBoundsRect)) {
scheduleAnimation();
@@ -3384,14 +3354,14 @@ class Task extends TaskFragment {
* the give {@link TaskDisplayArea}.
*/
void fillTaskInfo(TaskInfo info, boolean stripExtras, @Nullable TaskDisplayArea tda) {
- getNumRunningActivities(mReuseActivitiesReport);
+ info.launchCookies.clear();
+ info.addLaunchCookie(mLaunchCookie);
+ final ActivityRecord top = mTaskSupervisor.mTaskInfoHelper.fillAndReturnTop(this, info);
+
info.userId = isLeafTask() ? mUserId : mCurrentUser;
info.taskId = mTaskId;
info.displayId = getDisplayId();
- if (tda != null) {
- info.displayAreaFeatureId = tda.mFeatureId;
- }
- info.isRunning = getTopNonFinishingActivity() != null;
+ info.displayAreaFeatureId = tda != null ? tda.mFeatureId : FEATURE_UNDEFINED;
final Intent baseIntent = getBaseIntent();
// Make a copy of base intent because this is like a snapshot info.
// Besides, {@link RecentTasks#getRecentTasksImpl} may modify it.
@@ -3400,18 +3370,13 @@ class Task extends TaskFragment {
? new Intent()
: stripExtras ? baseIntent.cloneFilter() : new Intent(baseIntent);
info.baseIntent.setFlags(baseIntentFlags);
- info.baseActivity = mReuseActivitiesReport.base != null
- ? mReuseActivitiesReport.base.intent.getComponent()
- : null;
- info.topActivity = mReuseActivitiesReport.top != null
- ? mReuseActivitiesReport.top.mActivityComponent
- : null;
+
+ info.isRunning = top != null;
+ info.topActivity = top != null ? top.mActivityComponent : null;
info.origActivity = origActivity;
info.realActivity = realActivity;
- info.numActivities = mReuseActivitiesReport.numActivities;
info.lastActiveTime = lastActiveTime;
info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription());
- info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingModeInDisplayArea(tda);
info.supportsMultiWindow = supportsMultiWindowInDisplayArea(tda);
info.configuration.setTo(getConfiguration());
// Update to the task's current activity type and windowing mode which may differ from the
@@ -3422,49 +3387,38 @@ class Task extends TaskFragment {
//TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
// order changes.
- final Task top = getTopMostTask();
- info.resizeMode = top != null ? top.mResizeMode : mResizeMode;
- info.topActivityType = top.getActivityType();
+ final Task topTask = top != null ? top.getTask() : this;
+ info.resizeMode = topTask.mResizeMode;
+ info.topActivityType = topTask.getActivityType();
+ info.displayCutoutInsets = topTask.getDisplayCutoutInsets();
info.isResizeable = isResizeable();
info.minWidth = mMinWidth;
info.minHeight = mMinHeight;
info.defaultMinSize = mDisplayContent == null
? DEFAULT_MIN_TASK_SIZE_DP : mDisplayContent.mMinSizeOfResizeableTaskDp;
-
info.positionInParent = getRelativePosition();
+ info.topActivityInfo = top != null ? top.info : null;
info.pictureInPictureParams = getPictureInPictureParams(top);
- info.shouldDockBigOverlays = shouldDockBigOverlays();
- if (info.pictureInPictureParams != null
+ info.launchIntoPipHostTaskId = (info.pictureInPictureParams != null
&& info.pictureInPictureParams.isLaunchIntoPip()
- && top.getTopMostActivity().getLastParentBeforePip() != null) {
- info.launchIntoPipHostTaskId =
- top.getTopMostActivity().getLastParentBeforePip().mTaskId;
- }
- info.displayCutoutInsets = top != null ? top.getDisplayCutoutInsets() : null;
- info.topActivityInfo = mReuseActivitiesReport.top != null
- ? mReuseActivitiesReport.top.info
- : null;
-
- boolean isTopActivityResumed = mReuseActivitiesReport.top != null
- && mReuseActivitiesReport.top.getOrganizedTask() == this
- && mReuseActivitiesReport.top.isState(RESUMED);
+ && top.getLastParentBeforePip() != null)
+ ? top.getLastParentBeforePip().mTaskId : INVALID_TASK_ID;
+ info.shouldDockBigOverlays = top != null && top.shouldDockBigOverlays;
+ info.mTopActivityLocusId = top != null ? top.getLocusId() : null;
+
+ final boolean isTopActivityResumed = top != null
+ && top.getOrganizedTask() == this && top.isState(RESUMED);
// Whether the direct top activity is in size compat mode on foreground.
- info.topActivityInSizeCompat = isTopActivityResumed
- && mReuseActivitiesReport.top.inSizeCompatMode();
+ info.topActivityInSizeCompat = isTopActivityResumed && top.inSizeCompatMode();
// Whether the direct top activity is eligible for letterbox education.
info.topActivityEligibleForLetterboxEducation = isTopActivityResumed
- && mReuseActivitiesReport.top.isEligibleForLetterboxEducation();
+ && top.isEligibleForLetterboxEducation();
// Whether the direct top activity requested showing camera compat control.
info.cameraCompatControlState = isTopActivityResumed
- ? mReuseActivitiesReport.top.getCameraCompatControlState()
+ ? top.getCameraCompatControlState()
: TaskInfo.CAMERA_COMPAT_CONTROL_HIDDEN;
- info.launchCookies.clear();
- info.addLaunchCookie(mLaunchCookie);
- forAllActivities(r -> {
- info.addLaunchCookie(r.mLaunchCookie);
- });
final Task parentTask = getParent() != null ? getParent().asTask() : null;
info.parentTaskId = parentTask != null && parentTask.mCreatedByOrganizer
? parentTask.mTaskId
@@ -3472,19 +3426,17 @@ class Task extends TaskFragment {
info.isFocused = isFocused();
info.isVisible = hasVisibleChildren();
info.isSleeping = shouldSleepActivities();
- ActivityRecord topRecord = getTopNonFinishingActivity();
- info.mTopActivityLocusId = topRecord != null ? topRecord.getLocusId() : null;
}
@Nullable PictureInPictureParams getPictureInPictureParams() {
- return getPictureInPictureParams(getTopMostTask());
+ final Task topTask = getTopMostTask();
+ if (topTask == null) return null;
+ return getPictureInPictureParams(topTask.getTopMostActivity());
}
- private @Nullable PictureInPictureParams getPictureInPictureParams(Task top) {
- if (top == null) return null;
- final ActivityRecord topMostActivity = top.getTopMostActivity();
- return (topMostActivity == null || topMostActivity.pictureInPictureArgs.empty())
- ? null : new PictureInPictureParams(topMostActivity.pictureInPictureArgs);
+ private static @Nullable PictureInPictureParams getPictureInPictureParams(ActivityRecord top) {
+ return (top == null || top.pictureInPictureArgs.empty())
+ ? null : new PictureInPictureParams(top.pictureInPictureArgs);
}
private boolean shouldDockBigOverlays() {
@@ -3673,30 +3625,35 @@ class Task extends TaskFragment {
}
@Override
+ String toFullString() {
+ final StringBuilder sb = new StringBuilder(192);
+ sb.append(this);
+ sb.setLength(sb.length() - 1); // Remove tail '}'.
+ sb.append(" U=");
+ sb.append(mUserId);
+ final Task rootTask = getRootTask();
+ if (rootTask != this) {
+ sb.append(" rootTaskId=");
+ sb.append(rootTask.mTaskId);
+ }
+ sb.append(" visible=");
+ sb.append(shouldBeVisible(null /* starting */));
+ sb.append(" visibleRequested=");
+ sb.append(isVisibleRequested());
+ sb.append(" mode=");
+ sb.append(windowingModeToString(getWindowingMode()));
+ sb.append(" translucent=");
+ sb.append(isTranslucent(null /* starting */));
+ sb.append(" sz=");
+ sb.append(getChildCount());
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
public String toString() {
+ if (stringName != null) return stringName;
StringBuilder sb = new StringBuilder(128);
- if (stringName != null) {
- sb.append(stringName);
- sb.append(" U=");
- sb.append(mUserId);
- final Task rootTask = getRootTask();
- if (rootTask != this) {
- sb.append(" rootTaskId=");
- sb.append(rootTask.mTaskId);
- }
- sb.append(" visible=");
- sb.append(shouldBeVisible(null /* starting */));
- sb.append(" visibleRequested=");
- sb.append(isVisibleRequested());
- sb.append(" mode=");
- sb.append(windowingModeToString(getWindowingMode()));
- sb.append(" translucent=");
- sb.append(isTranslucent(null /* starting */));
- sb.append(" sz=");
- sb.append(getChildCount());
- sb.append('}');
- return sb.toString();
- }
sb.append("Task{");
sb.append(Integer.toHexString(System.identityHashCode(this)));
sb.append(" #");
@@ -3711,47 +3668,9 @@ class Task extends TaskFragment {
} else if (affinityIntent != null && affinityIntent.getComponent() != null) {
sb.append(" aI=");
sb.append(affinityIntent.getComponent().flattenToShortString());
- } else {
- sb.append(" ??");
- }
- stringName = sb.toString();
- return toString();
- }
-
- /** @see #getNumRunningActivities(TaskActivitiesReport) */
- static class TaskActivitiesReport implements Consumer<ActivityRecord> {
- int numRunning;
- int numActivities;
- ActivityRecord top;
- ActivityRecord base;
-
- void reset() {
- numRunning = numActivities = 0;
- top = base = null;
- }
-
- @Override
- public void accept(ActivityRecord r) {
- if (r.finishing) {
- return;
- }
-
- base = r;
-
- // Increment the total number of non-finishing activities
- numActivities++;
-
- if (top == null || (top.isState(INITIALIZING))) {
- top = r;
- // Reset the number of running activities until we hit the first non-initializing
- // activity
- numRunning = 0;
- }
- if (r.attachedToProcess()) {
- // Increment the number of actually running activities
- numRunning++;
- }
}
+ sb.append('}');
+ return stringName = sb.toString();
}
/**
@@ -4335,48 +4254,10 @@ class Task extends TaskFragment {
}
/**
- * @return the desired shadow radius in pixels for the current task.
- */
- private float getShadowRadius(boolean taskIsFocused) {
- int elevation = 0;
-
- // Get elevation for a specific windowing mode.
- if (inFreeformWindowingMode()) {
- elevation = taskIsFocused
- ? DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
- } else {
- // For all other windowing modes, do not draw a shadow.
- return 0;
- }
-
- // If the task has no visible children, do not draw a shadow.
- if (!hasVisibleChildren()) {
- return 0;
- }
-
- return dipToPixel(elevation, getDisplayContent().getDisplayMetrics());
- }
-
- /**
- * Update the length of the shadow if needed based on windowing mode and task focus state.
- */
- private void updateShadowsRadius(boolean taskIsFocused,
- SurfaceControl.Transaction pendingTransaction) {
- if (!isRootTask()) return;
-
- final float newShadowRadius = getShadowRadius(taskIsFocused);
- if (mShadowRadius != newShadowRadius) {
- mShadowRadius = newShadowRadius;
- pendingTransaction.setShadowRadius(getSurfaceControl(), mShadowRadius);
- }
- }
-
- /**
* Called on the task when it gained or lost focus.
* @param hasFocus
*/
void onAppFocusChanged(boolean hasFocus) {
- updateShadowsRadius(hasFocus, getSyncTransaction());
dispatchTaskInfoChangedIfNeeded(false /* force */);
}
@@ -4555,13 +4436,13 @@ class Task extends TaskFragment {
: WINDOWING_MODE_FULLSCREEN;
}
if (currentMode == WINDOWING_MODE_PINNED) {
+ // In the case that we've disabled affecting the SysUI flags as a part of seamlessly
+ // transferring the transform on the leash to the task, reset this state once we're
+ // moving out of pip
+ setCanAffectSystemUiFlags(true);
mRootWindowContainer.notifyActivityPipModeChanged(this, null);
}
if (likelyResolvedMode == WINDOWING_MODE_PINNED) {
- // In the case that we've disabled affecting the SysUI flags as a part of seamlessly
- // transferring the transform on the leash to the task, reset this state once we've
- // actually entered pip
- setCanAffectSystemUiFlags(true);
if (taskDisplayArea.getRootPinnedTask() != null) {
// Can only have 1 pip at a time, so replace an existing pip
taskDisplayArea.getRootPinnedTask().dismissPip();
@@ -4652,22 +4533,12 @@ class Task extends TaskFragment {
moveToFront(reason, null);
}
- void moveToFront(String reason, Task task) {
- if (mMoveAdjacentTogether && getAdjacentTaskFragment() != null) {
- final Task adjacentTask = getAdjacentTaskFragment().asTask();
- if (adjacentTask != null) {
- adjacentTask.moveToFrontInner(reason + " adjacentTaskToTop", null /* task */);
- }
- }
- moveToFrontInner(reason, task);
- }
-
/**
* @param reason The reason for moving the root task to the front.
* @param task If non-null, the task will be moved to the top of the root task.
*/
@VisibleForTesting
- void moveToFrontInner(String reason, Task task) {
+ void moveToFront(String reason, Task task) {
if (!isAttached()) {
return;
}
@@ -5581,23 +5452,10 @@ class Task extends TaskFragment {
}
}
- /**
- * Worker method for rearranging history task. Implements the function of moving all
- * activities for a specific task (gathering them if disjoint) into a single group at the
- * bottom of the root task.
- *
- * If a watcher is installed, the action is preflighted and the watcher has an opportunity
- * to premeptively cancel the move.
- *
- * @param tr The task to collect and move to the bottom.
- * @return Returns true if the move completed, false if not.
- */
- boolean moveTaskToBack(Task tr) {
- Slog.i(TAG, "moveTaskToBack: " + tr);
-
+ private boolean canMoveTaskToBack(Task task) {
// In LockTask mode, moving a locked task to the back of the root task may expose unlocked
// ones. Therefore we need to check if this operation is allowed.
- if (!mAtmService.getLockTaskController().canMoveTaskToBack(tr)) {
+ if (!mAtmService.getLockTaskController().canMoveTaskToBack(task)) {
return false;
}
@@ -5605,7 +5463,7 @@ class Task extends TaskFragment {
// for *other* available tasks, but if none are available, then try again allowing the
// current task to be selected.
if (isTopRootTaskInDisplayArea() && mAtmService.mController != null) {
- ActivityRecord next = topRunningActivity(null, tr.mTaskId);
+ ActivityRecord next = topRunningActivity(null, task.mTaskId);
if (next == null) {
next = topRunningActivity(null, INVALID_TASK_ID);
}
@@ -5623,15 +5481,70 @@ class Task extends TaskFragment {
}
}
}
+ return true;
+ }
+
+ /**
+ * Worker method for rearranging history task. Implements the function of moving all
+ * activities for a specific task (gathering them if disjoint) into a single group at the
+ * bottom of the root task.
+ *
+ * If a watcher is installed, the action is preflighted and the watcher has an opportunity
+ * to premeptively cancel the move.
+ *
+ * If this is a pinned task, it will be removed instead of rearranged.
+ *
+ * @param tr The task to collect and move to the bottom.
+ * @return Returns true if the move completed, false if not.
+ */
+ boolean moveTaskToBack(Task tr) {
+ Slog.i(TAG, "moveTaskToBack: " + tr);
+
+ if (!canMoveTaskToBack(tr)) return false;
if (DEBUG_TRANSITION) Slog.v(TAG_TRANSITION, "Prepare to back transition: task="
+ tr.mTaskId);
- // Skip the transition for pinned task.
- if (!inPinnedWindowingMode()) {
- mDisplayContent.requestTransitionAndLegacyPrepare(TRANSIT_TO_BACK, tr);
+ if (mTransitionController.isShellTransitionsEnabled()) {
+ final Transition transition = new Transition(TRANSIT_TO_BACK, 0 /* flags */,
+ mTransitionController, mWmService.mSyncEngine);
+ // Guarantee that this gets its own transition by queueing on SyncEngine
+ if (mWmService.mSyncEngine.hasActiveSync()) {
+ ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
+ "Creating Pending Move-to-back: %s", transition);
+ mWmService.mSyncEngine.queueSyncSet(
+ () -> mTransitionController.moveToCollecting(transition),
+ () -> {
+ mTransitionController.requestStartTransition(transition, tr,
+ null /* remoteTransition */, null /* displayChange */);
+ // Need to check again since this happens later and the system might
+ // be in a different state.
+ if (!canMoveTaskToBack(tr)) {
+ Slog.e(TAG, "Failed to move task to back after saying we could: "
+ + tr.mTaskId);
+ transition.abort();
+ return;
+ }
+ moveTaskToBackInner(tr);
+ });
+ } else {
+ mTransitionController.moveToCollecting(transition);
+ mTransitionController.requestStartTransition(transition, tr,
+ null /* remoteTransition */, null /* displayChange */);
+ moveTaskToBackInner(tr);
+ }
+ } else {
+ // Skip the transition for pinned task.
+ if (!inPinnedWindowingMode()) {
+ mDisplayContent.prepareAppTransition(TRANSIT_TO_BACK);
+ }
+ moveTaskToBackInner(tr);
}
- moveToBack("moveTaskToBackLocked", tr);
+ return true;
+ }
+
+ private boolean moveTaskToBackInner(@NonNull Task task) {
+ moveToBack("moveTaskToBackInner", task);
if (inPinnedWindowingMode()) {
mTaskSupervisor.removeRootTask(this);
@@ -6028,8 +5941,13 @@ class Task extends TaskFragment {
mLastRecentsAnimationTransaction = null;
mLastRecentsAnimationOverlay = null;
// reset also the crop and transform introduced by mLastRecentsAnimationTransaction
- getPendingTransaction().setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9])
+ resetSurfaceControlTransforms();
+ }
+
+ void resetSurfaceControlTransforms() {
+ getSyncTransaction().setMatrix(mSurfaceControl, Matrix.IDENTITY_MATRIX, new float[9])
.setWindowCrop(mSurfaceControl, null)
+ .setShadowRadius(mSurfaceControl, 0)
.setCornerRadius(mSurfaceControl, 0);
}
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index ca91a742786a..8220cae74dc8 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -29,7 +29,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
-import static android.view.WindowManagerPolicyConstants.SPLIT_DIVIDER_LAYER;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -91,20 +90,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
*/
private int mColorLayerCounter = 0;
- /**
- * Given that the split-screen divider does not have an AppWindowToken, it
- * will have to live inside of a "NonAppWindowContainer". However, in visual Z order
- * it will need to be interleaved with some of our children, appearing on top of
- * both docked root tasks but underneath any assistant root tasks.
- *
- * To solve this problem we have this anchor control, which will always exist so
- * we can always assign it the correct value in our {@link #assignChildLayers}.
- * Likewise since it always exists, we can always
- * assign the divider a layer relative to it. This way we prevent linking lifecycle
- * events between tasks and the divider window.
- */
- private SurfaceControl mSplitScreenDividerAnchor;
-
// Cached reference to some special tasks we tend to get a lot so we don't need to loop
// through the list to find them.
private Task mRootHomeTask;
@@ -422,8 +407,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// wasContained} restricts the preferred root task is set only when moving an existing
// root task to top instead of adding a new root task that may be too early (e.g. in the
// middle of launching or reparenting).
- if (moveToTop && child.isFocusableAndVisible()) {
- mPreferredTopFocusableRootTask = child;
+ final boolean isTopFocusableTask = moveToTop && child.isTopActivityFocusable();
+ if (isTopFocusableTask) {
+ mPreferredTopFocusableRootTask =
+ child.shouldBeVisible(null /* starting */) ? child : null;
} else if (mPreferredTopFocusableRootTask == child) {
mPreferredTopFocusableRootTask = null;
}
@@ -736,12 +723,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// Place root home tasks to the bottom.
layer = adjustRootTaskLayer(t, mTmpHomeChildren, layer);
layer = adjustRootTaskLayer(t, mTmpNormalChildren, layer);
- // TODO(b/207185041): Remove this divider workaround after we full remove leagacy split and
- // make app pair split only have single root then we can just attach the
- // divider to the single root task in shell.
- layer = Math.max(layer, SPLIT_DIVIDER_LAYER + 1);
adjustRootTaskLayer(t, mTmpAlwaysOnTopChildren, layer);
- t.setLayer(mSplitScreenDividerAnchor, SPLIT_DIVIDER_LAYER);
}
/**
@@ -769,19 +751,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
continue;
}
- final Task childTask = child.asTask();
- final boolean inAdjacentTask = childTask != null
- && child.inMultiWindowMode()
- && childTask.getRootTask().getAdjacentTaskFragment() != null;
-
- if (inAdjacentTask) {
- hasAdjacentTask = true;
- } else if (hasAdjacentTask && startLayer < SPLIT_DIVIDER_LAYER) {
- // Task on top of adjacent tasks should be higher than split divider layer so
- // set it as start.
- startLayer = SPLIT_DIVIDER_LAYER + 1;
- }
-
child.assignLayer(t, startLayer++);
}
@@ -808,31 +777,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
return activity != null ? activity.createRemoteAnimationTarget(record) : null;
}
- SurfaceControl getSplitScreenDividerAnchor() {
- return mSplitScreenDividerAnchor;
- }
-
- @Override
- void onParentChanged(ConfigurationContainer newParent, ConfigurationContainer oldParent) {
- if (getParent() != null) {
- super.onParentChanged(newParent, oldParent, () -> {
- mSplitScreenDividerAnchor = makeChildSurface(null)
- .setName("splitScreenDividerAnchor")
- .setCallsite("TaskDisplayArea.onParentChanged")
- .build();
-
- getSyncTransaction()
- .show(mSplitScreenDividerAnchor);
- });
- } else {
- super.onParentChanged(newParent, oldParent);
- mWmService.mTransactionFactory.get()
- .remove(mSplitScreenDividerAnchor)
- .apply();
- mSplitScreenDividerAnchor = null;
- }
- }
-
void setBackgroundColor(@ColorInt int colorInt) {
setBackgroundColor(colorInt, false /* restore */);
}
@@ -878,12 +822,6 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
setBackgroundColor(mBackgroundColor, true /* restore */);
}
- if (mSplitScreenDividerAnchor == null) {
- return;
- }
-
- // As TaskDisplayArea is getting a new surface, reparent and reorder the child surfaces.
- t.reparent(mSplitScreenDividerAnchor, mSurfaceControl);
reassignLayer(t);
scheduleAnimation();
}
@@ -1176,7 +1114,10 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
// If a task is launching from a created-by-organizer task, it should be launched into the
// same created-by-organizer task as well. Unless, the candidate task is already positioned
// in the another adjacent task.
- if (sourceTask != null) {
+ if (sourceTask != null && (candidateTask == null
+ // A pinned task relaunching should be handled by its task organizer. Skip fallback
+ // launch target of a pinned task from source task.
+ || candidateTask.getWindowingMode() != WINDOWING_MODE_PINNED)) {
Task launchTarget = sourceTask.getCreatedByOrganizerTask();
if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) {
if (candidateTask != null) {
@@ -1906,15 +1847,12 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
task.remove(false /* withTransition */, "removeTaskDisplayArea");
} else {
// Reparent task to corresponding launch root or display area.
- final WindowContainer launchRoot =
- task.supportsSplitScreenWindowingModeInDisplayArea(toDisplayArea)
- ? toDisplayArea.getLaunchRootTask(
+ final WindowContainer launchRoot = toDisplayArea.getLaunchRootTask(
task.getWindowingMode(),
task.getActivityType(),
null /* options */,
null /* sourceTask */,
- 0 /* launchFlags */)
- : null;
+ 0 /* launchFlags */);
task.reparent(launchRoot == null ? toDisplayArea : launchRoot, POSITION_TOP);
// Set the windowing mode to undefined by default to let the root task inherited the
@@ -2002,7 +1940,7 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> {
continue;
}
final Task rootTask = child.asTask();
- pw.println(doublePrefix + "* " + rootTask);
+ pw.println(doublePrefix + "* " + rootTask.toFullString());
rootTask.dump(pw, triplePrefix, dumpAll);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 1b0c01816f73..68884247df97 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -214,14 +214,6 @@ class TaskFragment extends WindowContainer<WindowContainer> {
private TaskFragment mAdjacentTaskFragment;
/**
- * Whether to move adjacent task fragment together when re-positioning.
- *
- * @see #mAdjacentTaskFragment
- */
- // TODO(b/207185041): Remove this once having a single-top root for split screen.
- boolean mMoveAdjacentTogether;
-
- /**
* Prevents duplicate calls to onTaskAppeared.
*/
boolean mTaskFragmentAppearedSent;
@@ -373,15 +365,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return service.mWindowOrganizerController.getTaskFragment(token);
}
- void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment, boolean moveTogether) {
+ void setAdjacentTaskFragment(@Nullable TaskFragment taskFragment) {
if (mAdjacentTaskFragment == taskFragment) {
return;
}
resetAdjacentTaskFragment();
if (taskFragment != null) {
mAdjacentTaskFragment = taskFragment;
- mMoveAdjacentTogether = moveTogether;
- taskFragment.setAdjacentTaskFragment(this, moveTogether);
+ taskFragment.setAdjacentTaskFragment(this);
}
}
@@ -390,11 +381,9 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (mAdjacentTaskFragment != null && mAdjacentTaskFragment.mAdjacentTaskFragment == this) {
mAdjacentTaskFragment.mAdjacentTaskFragment = null;
mAdjacentTaskFragment.mDelayLastActivityRemoval = false;
- mAdjacentTaskFragment.mMoveAdjacentTogether = false;
}
mAdjacentTaskFragment = null;
mDelayLastActivityRemoval = false;
- mMoveAdjacentTogether = false;
}
void setTaskFragmentOrganizer(@NonNull TaskFragmentOrganizerToken organizer, int uid,
@@ -1601,7 +1590,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (prev.attachedToProcess()) {
if (shouldAutoPip) {
boolean didAutoPip = mAtmService.enterPictureInPictureMode(
- prev, prev.pictureInPictureArgs);
+ prev, prev.pictureInPictureArgs, false /* fromClient */);
ProtoLog.d(WM_DEBUG_STATES, "Auto-PIP allowed, entering PIP mode "
+ "directly: %s, didAutoPip: %b", prev, didAutoPip);
} else {
@@ -1639,8 +1628,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
} else {
prev.schedulePauseTimeout();
- // Unset readiness since we now need to wait until this pause is complete.
- mTransitionController.setReady(this, false /* ready */);
+ // All activities will be stopped when sleeping, don't need to wait for pause.
+ if (!uiSleeping) {
+ // Unset readiness since we now need to wait until this pause is complete.
+ mTransitionController.setReady(this, false /* ready */);
+ }
return true;
}
@@ -1934,19 +1926,19 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!mAtmService.mSupportsMultiWindow) {
return false;
}
- final Task task = getTask();
- if (task == null) {
+ if (tda == null) {
return false;
}
- if (tda == null) {
+ final Task task = getTask();
+ if (task == null) {
return false;
}
- if (!getTask().isResizeable() && !tda.supportsNonResizableMultiWindow()) {
+ if (!task.isResizeable() && !tda.supportsNonResizableMultiWindow()) {
// Not support non-resizable in multi window.
return false;
}
- final ActivityRecord rootActivity = getTask().getRootActivity();
+ final ActivityRecord rootActivity = task.getRootActivity();
return tda.supportsActivityMinWidthHeightMultiWindow(mMinWidth, mMinHeight,
rootActivity != null ? rootActivity.info : null);
}
@@ -2108,13 +2100,14 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
if (inOutConfig.screenWidthDp == Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
- final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density);
+ final int overrideScreenWidthDp = (int) (mTmpStableBounds.width() / density + 0.5f);
inOutConfig.screenWidthDp = (insideParentBounds && !customContainerPolicy)
? Math.min(overrideScreenWidthDp, parentConfig.screenWidthDp)
: overrideScreenWidthDp;
}
if (inOutConfig.screenHeightDp == Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
- final int overrideScreenHeightDp = (int) (mTmpStableBounds.height() / density);
+ final int overrideScreenHeightDp =
+ (int) (mTmpStableBounds.height() / density + 0.5f);
inOutConfig.screenHeightDp = (insideParentBounds && !customContainerPolicy)
? Math.min(overrideScreenHeightDp, parentConfig.screenHeightDp)
: overrideScreenHeightDp;
@@ -2133,8 +2126,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (WindowConfiguration.isFloating(windowingMode) && !inPipTransition) {
// For floating tasks, calculate the smallest width from the bounds of the
// task, because they should not be affected by insets.
- inOutConfig.smallestScreenWidthDp = (int) (
- Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
+ inOutConfig.smallestScreenWidthDp = (int) (0.5f
+ + Math.min(mTmpFullBounds.width(), mTmpFullBounds.height()) / density);
} else if (isEmbedded()) {
// For embedded TFs, the smallest width should be updated. Otherwise, inherit
// from the parent task would result in applications loaded wrong resource.
@@ -2153,8 +2146,8 @@ class TaskFragment extends WindowContainer<WindowContainer> {
// For calculating screen layout, we need to use the non-decor inset screen area for the
// calculation for compatibility reasons, i.e. screen area without system bars that
// could never go away in Honeycomb.
- int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density);
- int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density);
+ int compatScreenWidthDp = (int) (mTmpNonDecorBounds.width() / density + 0.5f);
+ int compatScreenHeightDp = (int) (mTmpNonDecorBounds.height() / density + 0.5f);
// Use overrides if provided. If both overrides are provided, mTmpNonDecorBounds is
// undefined so it can't be used.
if (inOutConfig.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
@@ -2323,6 +2316,26 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return !startBounds.equals(getBounds());
}
+ boolean canHaveEmbeddingActivityTransition(@NonNull ActivityRecord child) {
+ if (!isOrganizedTaskFragment() || !mTransitionController.isShellTransitionsEnabled()) {
+ return false;
+ }
+ // The activity should request open transition when it is becoming visible.
+ return child.isVisibleRequested();
+ }
+
+ void collectEmbeddedTaskFragmentIfNeeded() {
+ if (!isOrganizedTaskFragment() || mTransitionController.isCollecting(this)) {
+ return;
+ }
+ if (getChildCount() == 0) {
+ // The TaskFragment is new created, and just becoming non-empty.
+ mTransitionController.collectExistenceChange(this);
+ } else {
+ mTransitionController.collect(this);
+ }
+ }
+
@Override
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
@@ -2550,7 +2563,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
if (!hasChild()) {
return false;
}
- return isExitAnimationRunningSelfOrChild() || inTransition();
+ return isExitAnimationRunningSelfOrChild();
}
@Override
@@ -2635,6 +2648,32 @@ class TaskFragment extends WindowContainer<WindowContainer> {
return getWindowingMode() == WINDOWING_MODE_FULLSCREEN || matchParentBounds();
}
+ String toFullString() {
+ final StringBuilder sb = new StringBuilder(128);
+ sb.append(this);
+ sb.setLength(sb.length() - 1); // Remove tail '}'.
+ if (mTaskFragmentOrganizerUid != INVALID_UID) {
+ sb.append(" organizerUid=");
+ sb.append(mTaskFragmentOrganizerUid);
+ }
+ if (mTaskFragmentOrganizerProcessName != null) {
+ sb.append(" organizerProc=");
+ sb.append(mTaskFragmentOrganizerProcessName);
+ }
+ if (mAdjacentTaskFragment != null) {
+ sb.append(" adjacent=");
+ sb.append(mAdjacentTaskFragment);
+ }
+ sb.append('}');
+ return sb.toString();
+ }
+
+ @Override
+ public String toString() {
+ return "TaskFragment{" + Integer.toHexString(System.identityHashCode(this))
+ + " mode=" + WindowConfiguration.windowingModeToString(getWindowingMode()) + "}";
+ }
+
boolean dump(String prefix, FileDescriptor fd, PrintWriter pw, boolean dumpAll,
boolean dumpClient, String dumpPackage, final boolean needSep, Runnable header) {
boolean printed = false;
@@ -2673,7 +2712,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
void dumpInner(String prefix, PrintWriter pw, boolean dumpAll, String dumpPackage) {
- pw.print(prefix); pw.print("* "); pw.println(this);
+ pw.print(prefix); pw.print("* "); pw.println(toFullString());
final Rect bounds = getRequestedOverrideBounds();
if (!bounds.isEmpty()) {
pw.println(prefix + " mBounds=" + bounds);
@@ -2694,10 +2733,11 @@ class TaskFragment extends WindowContainer<WindowContainer> {
final String doublePrefix = prefix + " ";
for (int i = mChildren.size() - 1; i >= 0; i--) {
final WindowContainer<?> child = mChildren.get(i);
- pw.println(prefix + "* " + child);
+ final TaskFragment tf = child.asTaskFragment();
+ pw.println(prefix + "* " + (tf != null ? tf.toFullString() : child));
// Only dump non-activity because full activity info is already printed by
// RootWindowContainer#dumpActivities.
- if (child.asActivityRecord() == null) {
+ if (tf != null) {
child.dump(pw, doublePrefix, dumpAll);
}
}
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 2546177ec367..d4551becbaaf 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -16,7 +16,7 @@
package com.android.server.wm;
-import static android.window.TaskFragmentOrganizer.putExceptionInBundle;
+import static android.window.TaskFragmentOrganizer.putErrorInfoInBundle;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
@@ -214,12 +214,15 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
}
- void onTaskFragmentError(IBinder errorCallbackToken, Throwable exception) {
+ void onTaskFragmentError(IBinder errorCallbackToken, @Nullable TaskFragment taskFragment,
+ int opType, Throwable exception) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER,
"Sending TaskFragment error exception=%s", exception.toString());
- final Bundle exceptionBundle = putExceptionInBundle(exception);
+ final TaskFragmentInfo info =
+ taskFragment != null ? taskFragment.getTaskFragmentInfo() : null;
+ final Bundle errorBundle = putErrorInfoInBundle(exception, info, opType);
try {
- mOrganizer.onTaskFragmentError(errorCallbackToken, exceptionBundle);
+ mOrganizer.onTaskFragmentError(errorCallbackToken, errorBundle);
} catch (RemoteException e) {
Slog.d(TAG, "Exception sending onTaskFragmentError callback", e);
}
@@ -462,15 +465,19 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
void onTaskFragmentError(ITaskFragmentOrganizer organizer, IBinder errorCallbackToken,
- Throwable exception) {
+ TaskFragment taskFragment, int opType, Throwable exception) {
validateAndGetState(organizer);
Slog.w(TAG, "onTaskFragmentError ", exception);
final PendingTaskFragmentEvent pendingEvent = new PendingTaskFragmentEvent.Builder(
PendingTaskFragmentEvent.EVENT_ERROR, organizer)
.setErrorCallbackToken(errorCallbackToken)
+ .setTaskFragment(taskFragment)
.setException(exception)
+ .setOpType(opType)
.build();
mPendingTaskFragmentEvents.add(pendingEvent);
+ // Make sure the error event will be dispatched if there are no other changes.
+ mAtmService.mWindowManager.mWindowPlacerLocked.requestTraversal();
}
void onActivityReparentToTask(ActivityRecord activity) {
@@ -565,19 +572,22 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
// Set when the event is deferred due to the host task is invisible. The defer time will
// be the last active time of the host task.
private long mDeferTime;
+ private int mOpType;
private PendingTaskFragmentEvent(@EventType int eventType,
ITaskFragmentOrganizer taskFragmentOrg,
@Nullable TaskFragment taskFragment,
@Nullable IBinder errorCallbackToken,
@Nullable Throwable exception,
- @Nullable ActivityRecord activity) {
+ @Nullable ActivityRecord activity,
+ int opType) {
mEventType = eventType;
mTaskFragmentOrg = taskFragmentOrg;
mTaskFragment = taskFragment;
mErrorCallbackToken = errorCallbackToken;
mException = exception;
mActivity = activity;
+ mOpType = opType;
}
/**
@@ -608,6 +618,7 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
private Throwable mException;
@Nullable
private ActivityRecord mActivity;
+ private int mOpType;
Builder(@EventType int eventType, ITaskFragmentOrganizer taskFragmentOrg) {
mEventType = eventType;
@@ -634,9 +645,14 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
return this;
}
+ Builder setOpType(int opType) {
+ mOpType = opType;
+ return this;
+ }
+
PendingTaskFragmentEvent build() {
return new PendingTaskFragmentEvent(mEventType, mTaskFragmentOrg, mTaskFragment,
- mErrorCallbackToken, mException, mActivity);
+ mErrorCallbackToken, mException, mActivity, mOpType);
}
}
}
@@ -665,6 +681,10 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
}
private boolean shouldSendEventWhenTaskInvisible(@NonNull PendingTaskFragmentEvent event) {
+ if (event.mEventType == PendingTaskFragmentEvent.EVENT_ERROR) {
+ return true;
+ }
+
final TaskFragmentOrganizerState state =
mTaskFragmentOrganizerState.get(event.mTaskFragmentOrg.asBinder());
final TaskFragmentInfo lastInfo = state.mLastSentTaskFragmentInfos.get(event.mTaskFragment);
@@ -755,7 +775,8 @@ public class TaskFragmentOrganizerController extends ITaskFragmentOrganizerContr
state.onTaskFragmentParentInfoChanged(taskFragment);
break;
case PendingTaskFragmentEvent.EVENT_ERROR:
- state.onTaskFragmentError(event.mErrorCallbackToken, event.mException);
+ state.onTaskFragmentError(event.mErrorCallbackToken, taskFragment, event.mOpType,
+ event.mException);
break;
case PendingTaskFragmentEvent.EVENT_ACTIVITY_REPARENT_TO_TASK:
state.onActivityReparentToTask(event.mActivity);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 6e84681f0ab5..801665862d29 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -36,6 +36,7 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import android.util.ArrayMap;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
@@ -55,7 +56,6 @@ import com.android.internal.util.ArrayUtils;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@@ -94,7 +94,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
* lifecycle order since we may be updating the visibility of task surface controls in a pending
* transaction before they are presented to the task org.
*/
- private class TaskOrganizerCallbacks {
+ private static class TaskOrganizerCallbacks {
final ITaskOrganizer mTaskOrganizer;
final Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
@@ -123,7 +123,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
}
-
void onTaskVanished(Task task) {
ProtoLog.v(WM_DEBUG_WINDOW_ORGANIZER, "Task vanished taskId=%d", task.mTaskId);
final RunningTaskInfo taskInfo = task.getTaskInfo();
@@ -173,11 +172,160 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
}
+ /**
+ * Maintains a list of all the pending events for a given {@link android.window.TaskOrganizer}
+ */
+ static final class TaskOrganizerPendingEventsQueue {
+ private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
+ private final TaskOrganizerState mOrganizerState;
+ private RunningTaskInfo mTmpTaskInfo;
+ // Pending task events due to layout deferred.
+ private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>();
+
+ TaskOrganizerPendingEventsQueue(TaskOrganizerState taskOrganizerState) {
+ mOrganizerState = taskOrganizerState;
+ }
+
+ @VisibleForTesting
+ public ArrayList<PendingTaskEvent> getPendingEventList() {
+ return mPendingTaskEvents;
+ }
+
+ int numPendingTaskEvents() {
+ return mPendingTaskEvents.size();
+ }
+
+ void clearPendingTaskEvents() {
+ mPendingTaskEvents.clear();
+ }
+
+ void addPendingTaskEvent(PendingTaskEvent event) {
+ mPendingTaskEvents.add(event);
+ }
+
+ void removePendingTaskEvent(PendingTaskEvent event) {
+ mPendingTaskEvents.remove(event);
+ }
+
+ /**
+ * Removes all the pending task events for the given {@code task}.
+ *
+ * @param task
+ * @return true if a {@link PendingTaskEvent#EVENT_APPEARED} is still pending for the given
+ * {code task}.
+ */
+ boolean removePendingTaskEvents(Task task) {
+ boolean foundPendingAppearedEvents = false;
+ for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+ PendingTaskEvent entry = mPendingTaskEvents.get(i);
+ if (task.mTaskId == entry.mTask.mTaskId) {
+ // This task is vanished so remove all pending event of it.
+ mPendingTaskEvents.remove(i);
+
+ if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
+ foundPendingAppearedEvents = true;
+ }
+ }
+ }
+ return foundPendingAppearedEvents;
+ }
+
+ @Nullable
+ private PendingTaskEvent getPendingTaskEvent(Task task, int type) {
+ for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+ PendingTaskEvent entry = mPendingTaskEvents.get(i);
+ if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ @VisibleForTesting
+ @Nullable
+ PendingTaskEvent getPendingLifecycleTaskEvent(Task task) {
+ for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
+ PendingTaskEvent entry = mPendingTaskEvents.get(i);
+ if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) {
+ return entry;
+ }
+ }
+ return null;
+ }
+
+ void dispatchPendingEvents() {
+ if (mPendingTaskEvents.isEmpty()) {
+ return;
+ }
+ for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) {
+ dispatchPendingEvent(mPendingTaskEvents.get(i));
+ }
+ mPendingTaskEvents.clear();
+ }
+
+ private void dispatchPendingEvent(PendingTaskEvent event) {
+ final Task task = event.mTask;
+ switch (event.mEventType) {
+ case PendingTaskEvent.EVENT_APPEARED:
+ if (task.taskAppearedReady()) {
+ mOrganizerState.mOrganizer.onTaskAppeared(task);
+ }
+ break;
+ case PendingTaskEvent.EVENT_VANISHED:
+ mOrganizerState.mOrganizer.onTaskVanished(task);
+ mLastSentTaskInfos.remove(task);
+ break;
+ case PendingTaskEvent.EVENT_INFO_CHANGED:
+ dispatchTaskInfoChanged(event.mTask, event.mForce);
+ break;
+ case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED:
+ mOrganizerState.mOrganizer.onBackPressedOnTaskRoot(task);
+ break;
+ }
+ }
+
+ private void dispatchTaskInfoChanged(Task task, boolean force) {
+ RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
+ if (mTmpTaskInfo == null) {
+ mTmpTaskInfo = new RunningTaskInfo();
+ }
+ mTmpTaskInfo.configuration.unset();
+ task.fillTaskInfo(mTmpTaskInfo);
+
+ boolean changed = !mTmpTaskInfo
+ .equalsForTaskOrganizer(lastInfo)
+ || !configurationsAreEqualForOrganizer(
+ mTmpTaskInfo.configuration,
+ lastInfo.configuration);
+ if (!(changed || force)) {
+ // mTmpTaskInfo will be reused next time.
+ return;
+ }
+ final RunningTaskInfo newInfo = mTmpTaskInfo;
+ mLastSentTaskInfos.put(task,
+ mTmpTaskInfo);
+ // Since we've stored this, clean up the reference so a new one will be created next
+ // time.
+ // Transferring it this way means we only have to construct new RunningTaskInfos when
+ // they change.
+ mTmpTaskInfo = null;
+
+ if (task.isOrganized()) {
+ // Because we defer sending taskAppeared() until the app has drawn, we may receive a
+ // configuration change before the state actually has the task registered. As such
+ // we should ignore these change events to the organizer until taskAppeared(). If
+ // the task was created by the organizer, then we always send the info change.
+ mOrganizerState.mOrganizer.onTaskInfoChanged(task, newInfo);
+ }
+ }
+ }
+
@VisibleForTesting
class TaskOrganizerState {
private final TaskOrganizerCallbacks mOrganizer;
private final DeathRecipient mDeathRecipient;
private final ArrayList<Task> mOrganizedTasks = new ArrayList<>();
+ private final TaskOrganizerPendingEventsQueue mPendingEventsQueue;
private final int mUid;
TaskOrganizerState(ITaskOrganizer organizer, int uid) {
@@ -187,6 +335,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
: mService.mWindowManager.mAnimator::addAfterPrepareSurfacesRunnable;
mOrganizer = new TaskOrganizerCallbacks(organizer, deferTaskOrgCallbacksConsumer);
mDeathRecipient = new DeathRecipient(organizer);
+ mPendingEventsQueue = new TaskOrganizerPendingEventsQueue(this);
try {
organizer.asBinder().linkToDeath(mDeathRecipient, 0);
} catch (RemoteException e) {
@@ -200,6 +349,11 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
return mDeathRecipient;
}
+ @VisibleForTesting
+ TaskOrganizerPendingEventsQueue getPendingEventsQueue() {
+ return mPendingEventsQueue;
+ }
+
/**
* Register this task with this state, but doesn't trigger the task appeared callback to
* the organizer.
@@ -263,8 +417,7 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
// updateTaskOrganizerState should remove the task from the list, but still
// check it again to avoid while-loop isn't terminate.
if (removeTask(t, t.mRemoveWithTaskOrganizer)) {
- TaskOrganizerController.this.onTaskVanishedInternal(
- mOrganizer.mTaskOrganizer, t);
+ TaskOrganizerController.this.onTaskVanishedInternal(this, t);
}
}
if (mService.getTransitionController().isShellTransitionsEnabled()) {
@@ -278,8 +431,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
}
- // Remove organizer state after removing tasks so we get a chance to send
- // onTaskVanished.
+ // Pending events queue for this organizer need to be cleared because this organizer
+ // has either died or unregistered itself.
+ mPendingEventsQueue.clearPendingTaskEvents();
mTaskOrganizerStates.remove(mOrganizer.getBinder());
}
@@ -320,14 +474,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
// List of task organizers by priority
private final LinkedList<ITaskOrganizer> mTaskOrganizers = new LinkedList<>();
- private final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap<>();
- private final WeakHashMap<Task, RunningTaskInfo> mLastSentTaskInfos = new WeakHashMap<>();
- // Pending task events due to layout deferred.
- private final ArrayList<PendingTaskEvent> mPendingTaskEvents = new ArrayList<>();
+ private final ArrayMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new ArrayMap<>();
// Set of organized tasks (by taskId) that dispatch back pressed to their organizers
private final HashSet<Integer> mInterceptBackPressedOnRootTasks = new HashSet();
- private RunningTaskInfo mTmpTaskInfo;
private Consumer<Runnable> mDeferTaskOrgCallbacksConsumer;
TaskOrganizerController(ActivityTaskManagerService atm) {
@@ -354,11 +504,6 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mDeferTaskOrgCallbacksConsumer = consumer;
}
- @VisibleForTesting
- ArrayList<PendingTaskEvent> getPendingEventList() {
- return mPendingTaskEvents;
- }
-
/**
* Register a TaskOrganizer to manage tasks as they enter the a supported windowing mode.
*/
@@ -382,11 +527,12 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
mService.mRootWindowContainer.forAllTasks((task) -> {
boolean returnTask = !task.mCreatedByOrganizer;
task.updateTaskOrganizerState(returnTask /* skipTaskAppeared */);
- if (returnTask) {
- SurfaceControl outSurfaceControl = state.addTaskWithoutCallback(task,
+ // It is possible for the task to not yet have a surface control, so ensure that
+ // the update succeeded in setting the organizer for the task before returning
+ if (task.isOrganized() && returnTask) {
+ SurfaceControl taskLeash = state.addTaskWithoutCallback(task,
"TaskOrganizerController.registerTaskOrganizer");
- taskInfos.add(
- new TaskAppearedInfo(task.getTaskInfo(), outSurfaceControl));
+ taskInfos.add(new TaskAppearedInfo(task.getTaskInfo(), taskLeash));
}
});
};
@@ -592,10 +738,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
void onTaskAppeared(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state != null && state.addTask(task)) {
- PendingTaskEvent pending = getPendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED);
+ final TaskOrganizerPendingEventsQueue pendingEvents =
+ state.mPendingEventsQueue;
+ PendingTaskEvent pending = pendingEvents.getPendingTaskEvent(task,
+ PendingTaskEvent.EVENT_APPEARED);
if (pending == null) {
- pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_APPEARED);
- mPendingTaskEvents.add(pending);
+ pendingEvents.addPendingTaskEvent(new PendingTaskEvent(task,
+ PendingTaskEvent.EVENT_APPEARED));
}
}
}
@@ -603,26 +752,25 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
void onTaskVanished(ITaskOrganizer organizer, Task task) {
final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
if (state != null && state.removeTask(task, task.mRemoveWithTaskOrganizer)) {
- onTaskVanishedInternal(organizer, task);
+ onTaskVanishedInternal(state, task);
}
}
- private void onTaskVanishedInternal(ITaskOrganizer organizer, Task task) {
- for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
- PendingTaskEvent entry = mPendingTaskEvents.get(i);
- if (task.mTaskId == entry.mTask.mTaskId && entry.mTaskOrg == organizer) {
- // This task is vanished so remove all pending event of it.
- mPendingTaskEvents.remove(i);
- if (entry.mEventType == PendingTaskEvent.EVENT_APPEARED) {
- // If task appeared callback still pend, ignore this callback too.
- return;
- }
- }
+ private void onTaskVanishedInternal(TaskOrganizerState organizerState, Task task) {
+ if (organizerState == null) {
+ Slog.i(TAG, "cannot send onTaskVanished because organizer state is not "
+ + "present for this organizer");
+ return;
}
-
- PendingTaskEvent pending =
- new PendingTaskEvent(task, organizer, PendingTaskEvent.EVENT_VANISHED);
- mPendingTaskEvents.add(pending);
+ TaskOrganizerPendingEventsQueue pendingEventsQueue =
+ organizerState.mPendingEventsQueue;
+ boolean hadPendingAppearedEvents =
+ pendingEventsQueue.removePendingTaskEvents(task);
+ if (hadPendingAppearedEvents) {
+ return;
+ }
+ pendingEventsQueue.addPendingTaskEvent(new PendingTaskEvent(task,
+ organizerState.mOrganizer.mTaskOrganizer, PendingTaskEvent.EVENT_VANISHED));
}
@Override
@@ -690,48 +838,13 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
}
void dispatchPendingEvents() {
- if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()
- || mPendingTaskEvents.isEmpty()) {
+ if (mService.mWindowManager.mWindowPlacerLocked.isLayoutDeferred()) {
return;
}
-
- for (int i = 0, n = mPendingTaskEvents.size(); i < n; i++) {
- PendingTaskEvent event = mPendingTaskEvents.get(i);
- final Task task = event.mTask;
- final TaskOrganizerState state;
- switch (event.mEventType) {
- case PendingTaskEvent.EVENT_APPEARED:
- state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
- if (state != null && task.taskAppearedReady()) {
- state.mOrganizer.onTaskAppeared(task);
- }
- break;
- case PendingTaskEvent.EVENT_VANISHED:
- // TaskOrganizerState cannot be used here because it might have already been
- // removed.
- // The state is removed when an organizer dies or is unregistered. In order to
- // send the pending vanished task events, the mTaskOrg from event is used.
- // These events should not ideally be sent and will be removed as part of
- // b/224812558.
- try {
- event.mTaskOrg.onTaskVanished(task.getTaskInfo());
- } catch (RemoteException ex) {
- Slog.e(TAG, "Exception sending onTaskVanished callback", ex);
- }
- mLastSentTaskInfos.remove(task);
- break;
- case PendingTaskEvent.EVENT_INFO_CHANGED:
- dispatchTaskInfoChanged(event.mTask, event.mForce);
- break;
- case PendingTaskEvent.EVENT_ROOT_BACK_PRESSED:
- state = mTaskOrganizerStates.get(event.mTaskOrg.asBinder());
- if (state != null) {
- state.mOrganizer.onBackPressedOnTaskRoot(task);
- }
- break;
- }
+ for (int taskOrgIdx = 0; taskOrgIdx < mTaskOrganizerStates.size(); taskOrgIdx++) {
+ TaskOrganizerState taskOrganizerState = mTaskOrganizerStates.valueAt(taskOrgIdx);
+ taskOrganizerState.mPendingEventsQueue.dispatchPendingEvents();
}
- mPendingTaskEvents.clear();
}
void reportImeDrawnOnTask(Task task) {
@@ -750,20 +863,30 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
// Skip if task still not appeared.
return;
}
- if (force && mPendingTaskEvents.isEmpty()) {
+ final TaskOrganizerState taskOrganizerState =
+ mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder());
+ final TaskOrganizerPendingEventsQueue pendingEventsQueue =
+ taskOrganizerState.mPendingEventsQueue;
+ if (pendingEventsQueue == null) {
+ Slog.i(TAG, "cannot send onTaskInfoChanged because pending events queue is not "
+ + "present for this organizer");
+ return;
+ }
+ if (force && pendingEventsQueue.numPendingTaskEvents() == 0) {
// There are task-info changed events do not result in
// - RootWindowContainer#performSurfacePlacementNoTrace OR
// - WindowAnimator#animate
// For instance, when an app requesting aspect ratio change when in PiP mode.
// To solve this, we directly dispatch the pending event if there are no events queued (
// otherwise, all pending events should be dispatched on next drawn).
- dispatchTaskInfoChanged(task, true /* force */);
+ pendingEventsQueue.dispatchTaskInfoChanged(task, true /* force */);
return;
}
// Defer task info reporting while layout is deferred. This is because layout defer
// blocks tend to do lots of re-ordering which can mess up animations in receivers.
- PendingTaskEvent pending = getPendingLifecycleTaskEvent(task);
+ PendingTaskEvent pending = pendingEventsQueue
+ .getPendingLifecycleTaskEvent(task);
if (pending == null) {
pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_INFO_CHANGED);
} else {
@@ -774,45 +897,10 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
return;
}
// Remove and add for re-ordering.
- mPendingTaskEvents.remove(pending);
+ pendingEventsQueue.removePendingTaskEvent(pending);
}
pending.mForce |= force;
- mPendingTaskEvents.add(pending);
- }
-
- private void dispatchTaskInfoChanged(Task task, boolean force) {
- RunningTaskInfo lastInfo = mLastSentTaskInfos.get(task);
- if (mTmpTaskInfo == null) {
- mTmpTaskInfo = new RunningTaskInfo();
- }
- mTmpTaskInfo.configuration.unset();
- task.fillTaskInfo(mTmpTaskInfo);
-
- boolean changed = !mTmpTaskInfo.equalsForTaskOrganizer(lastInfo)
- || !configurationsAreEqualForOrganizer(
- mTmpTaskInfo.configuration, lastInfo.configuration);
- if (!(changed || force)) {
- // mTmpTaskInfo will be reused next time.
- return;
- }
- final RunningTaskInfo newInfo = mTmpTaskInfo;
- mLastSentTaskInfos.put(task, mTmpTaskInfo);
- // Since we've stored this, clean up the reference so a new one will be created next time.
- // Transferring it this way means we only have to construct new RunningTaskInfos when they
- // change.
- mTmpTaskInfo = null;
-
- if (task.isOrganized()) {
- // Because we defer sending taskAppeared() until the app has drawn, we may receive a
- // configuration change before the state actually has the task registered. As such we
- // should ignore these change events to the organizer until taskAppeared(). If the task
- // was created by the organizer, then we always send the info change.
- final TaskOrganizerState state = mTaskOrganizerStates.get(
- task.mTaskOrganizer.asBinder());
- if (state != null) {
- state.mOrganizer.onTaskInfoChanged(task, newInfo);
- }
- }
+ pendingEventsQueue.addPendingTaskEvent(pending);
}
@Override
@@ -1018,50 +1106,36 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
|| !mInterceptBackPressedOnRootTasks.contains(task.mTaskId)) {
return false;
}
+ final TaskOrganizerPendingEventsQueue pendingEventsQueue =
+ mTaskOrganizerStates.get(task.mTaskOrganizer.asBinder())
+ .mPendingEventsQueue;
+ if (pendingEventsQueue == null) {
+ Slog.w(TAG, "cannot get handle BackPressedOnTaskRoot because organizerState is "
+ + "not present");
+ return false;
+ }
PendingTaskEvent pendingVanished =
- getPendingTaskEvent(task, PendingTaskEvent.EVENT_VANISHED);
+ pendingEventsQueue.getPendingTaskEvent(task,
+ PendingTaskEvent.EVENT_VANISHED);
if (pendingVanished != null) {
// This task will vanish before this callback so just ignore.
return false;
}
- PendingTaskEvent pending = getPendingTaskEvent(
+ PendingTaskEvent pending = pendingEventsQueue.getPendingTaskEvent(
task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED);
if (pending == null) {
pending = new PendingTaskEvent(task, PendingTaskEvent.EVENT_ROOT_BACK_PRESSED);
} else {
// Pending already exist, remove and add for re-ordering.
- mPendingTaskEvents.remove(pending);
+ pendingEventsQueue.removePendingTaskEvent(pending);
}
- mPendingTaskEvents.add(pending);
+ pendingEventsQueue.addPendingTaskEvent(pending);
mService.mWindowManager.mWindowPlacerLocked.requestTraversal();
return true;
}
- @Nullable
- private PendingTaskEvent getPendingTaskEvent(Task task, int type) {
- for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
- PendingTaskEvent entry = mPendingTaskEvents.get(i);
- if (task.mTaskId == entry.mTask.mTaskId && type == entry.mEventType) {
- return entry;
- }
- }
- return null;
- }
-
- @VisibleForTesting
- @Nullable
- PendingTaskEvent getPendingLifecycleTaskEvent(Task task) {
- for (int i = mPendingTaskEvents.size() - 1; i >= 0; i--) {
- PendingTaskEvent entry = mPendingTaskEvents.get(i);
- if (task.mTaskId == entry.mTask.mTaskId && entry.isLifecycleEvent()) {
- return entry;
- }
- }
- return null;
- }
-
public void dump(PrintWriter pw, String prefix) {
final String innerPrefix = prefix + " ";
pw.print(prefix); pw.println("TaskOrganizerController:");
@@ -1084,4 +1158,9 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub {
TaskOrganizerState getTaskOrganizerState(IBinder taskOrganizer) {
return mTaskOrganizerStates.get(taskOrganizer);
}
+
+ @VisibleForTesting
+ TaskOrganizerPendingEventsQueue getTaskOrganizerPendingEvents(IBinder taskOrganizer) {
+ return mTaskOrganizerStates.get(taskOrganizer).mPendingEventsQueue;
+ }
}
diff --git a/services/core/java/com/android/server/wm/TaskPositioner.java b/services/core/java/com/android/server/wm/TaskPositioner.java
index 47c397d12720..03ca4fd49a96 100644
--- a/services/core/java/com/android/server/wm/TaskPositioner.java
+++ b/services/core/java/com/android/server/wm/TaskPositioner.java
@@ -41,7 +41,6 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.InputConfig;
-import android.os.Process;
import android.os.RemoteException;
import android.os.Trace;
import android.util.DisplayMetrics;
@@ -222,8 +221,8 @@ class TaskPositioner implements IBinder.DeathRecipient {
mDragWindowHandle.token = mClientChannel.getToken();
mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG;
mDragWindowHandle.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
- mDragWindowHandle.ownerPid = Process.myPid();
- mDragWindowHandle.ownerUid = Process.myUid();
+ mDragWindowHandle.ownerPid = WindowManagerService.MY_PID;
+ mDragWindowHandle.ownerUid = WindowManagerService.MY_UID;
mDragWindowHandle.scaleFactor = 1.0f;
// When dragging the window around, we do not want to steal focus for the window.
mDragWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
diff --git a/services/core/java/com/android/server/wm/TaskSnapshotController.java b/services/core/java/com/android/server/wm/TaskSnapshotController.java
index 814656db9fa0..534616fb7207 100644
--- a/services/core/java/com/android/server/wm/TaskSnapshotController.java
+++ b/services/core/java/com/android/server/wm/TaskSnapshotController.java
@@ -47,6 +47,7 @@ import android.graphics.RenderNode;
import android.hardware.HardwareBuffer;
import android.os.Environment;
import android.os.Handler;
+import android.os.Trace;
import android.util.ArraySet;
import android.util.Pair;
import android.util.Slog;
@@ -391,8 +392,10 @@ class TaskSnapshotController {
SurfaceControl.ScreenshotHardwareBuffer createTaskSnapshot(@NonNull Task task,
TaskSnapshot.Builder builder) {
Point taskSize = new Point();
+ Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "createTaskSnapshot");
final SurfaceControl.ScreenshotHardwareBuffer taskSnapshot = createTaskSnapshot(task,
mHighResTaskSnapshotScale, builder.getPixelFormat(), taskSize, builder);
+ Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
builder.setTaskSize(taskSize);
return taskSnapshot;
}
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f6f9020555d8..31d8eb8eea17 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -27,6 +27,7 @@ import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS;
import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_FLAG_IS_RECENTS;
@@ -45,6 +46,8 @@ import static android.view.WindowManager.TransitionType;
import static android.view.WindowManager.transitTypeToString;
import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
+import static android.window.TransitionInfo.FLAG_IS_INPUT_METHOD;
import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_OCCLUDES_KEYGUARD;
@@ -52,6 +55,8 @@ import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
+import static com.android.server.wm.ActivityRecord.State.RESUMED;
+import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_RECENTS_ANIM;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
import static com.android.server.wm.ActivityTaskManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
@@ -80,11 +85,9 @@ import android.window.TransitionInfo;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.ColorUtils;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.protolog.ProtoLogGroup;
import com.android.internal.protolog.common.ProtoLog;
import com.android.internal.util.function.pooled.PooledLambda;
-import com.android.server.LocalServices;
import com.android.server.inputmethod.InputMethodManagerInternal;
import java.lang.annotation.Retention;
@@ -127,12 +130,18 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
*/
private static final int STATE_ABORT = 3;
+ /**
+ * This transition has finished playing successfully.
+ */
+ private static final int STATE_FINISHED = 4;
+
@IntDef(prefix = { "STATE_" }, value = {
STATE_PENDING,
STATE_COLLECTING,
STATE_STARTED,
STATE_PLAYING,
- STATE_ABORT
+ STATE_ABORT,
+ STATE_FINISHED
})
@Retention(RetentionPolicy.SOURCE)
@interface TransitionState {}
@@ -189,6 +198,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
private boolean mNavBarAttachedToApp = false;
private int mRecentsDisplayId = INVALID_DISPLAY;
+ /** The delay for light bar appearance animation. */
+ long mStatusBarTransitionDelay;
+
/** @see #setCanPipOnFinish */
private boolean mCanPipOnFinish = true;
@@ -198,6 +210,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mFlags = flags;
mController = controller;
mSyncEngine = syncEngine;
+
+ controller.mTransitionTracer.logState(this);
}
void addFlag(int flag) {
@@ -210,10 +224,28 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mTransientLaunches = new ArrayMap<>();
}
mTransientLaunches.put(activity, restoreBelow);
+ setTransientLaunchToChanges(activity);
+
+ if (restoreBelow != null) {
+ final ChangeInfo info = mChanges.get(restoreBelow);
+ if (info != null) {
+ info.mFlags |= ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH;
+ }
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Transition %d: Set %s as "
+ "transient-launch", mSyncId, activity);
}
+ boolean isTransientHide(@NonNull Task task) {
+ if (mTransientLaunches == null) return false;
+ for (int i = 0; i < mTransientLaunches.size(); ++i) {
+ if (mTransientLaunches.valueAt(i) == task) {
+ return true;
+ }
+ }
+ return false;
+ }
+
boolean isTransientLaunch(@NonNull ActivityRecord activity) {
return mTransientLaunches != null && mTransientLaunches.containsKey(activity);
}
@@ -238,6 +270,25 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
info.mFlags = info.mFlags | ChangeInfo.FLAG_SEAMLESS_ROTATION;
}
+ /**
+ * Only set flag to the parent tasks and activity itself.
+ */
+ private void setTransientLaunchToChanges(@NonNull WindowContainer wc) {
+ for (WindowContainer curr = wc; curr != null && mChanges.containsKey(curr);
+ curr = curr.getParent()) {
+ if (curr.asTask() == null && curr.asActivityRecord() == null) {
+ return;
+ }
+ final ChangeInfo info = mChanges.get(curr);
+ info.mFlags = info.mFlags | ChangeInfo.FLAG_TRANSIENT_LAUNCH;
+ }
+ }
+
+ @TransitionState
+ int getState() {
+ return mState;
+ }
+
@VisibleForTesting
int getSyncId() {
return mSyncId;
@@ -248,6 +299,20 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return mFlags;
}
+ @VisibleForTesting
+ SurfaceControl.Transaction getStartTransaction() {
+ return mStartTransaction;
+ }
+
+ @VisibleForTesting
+ SurfaceControl.Transaction getFinishTransaction() {
+ return mFinishTransaction;
+ }
+
+ private boolean isCollecting() {
+ return mState == STATE_COLLECTING || mState == STATE_STARTED;
+ }
+
/** Starts collecting phase. Once this starts, all relevant surface operations are sync. */
void startCollecting(long timeoutMs) {
if (mState != STATE_PENDING) {
@@ -255,6 +320,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
mState = STATE_COLLECTING;
mSyncId = mSyncEngine.startSyncSet(this, timeoutMs, TAG);
+
+ mController.mTransitionTracer.logState(this);
}
/**
@@ -272,6 +339,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Starting Transition %d",
mSyncId);
applyReady();
+
+ mController.mTransitionTracer.logState(this);
}
/**
@@ -281,7 +350,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (mState < STATE_COLLECTING) {
throw new IllegalStateException("Transition hasn't started collecting.");
}
- if (mSyncId < 0) return;
+ if (!isCollecting()) {
+ // Too late, transition already started playing, so don't collect.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Collecting in transition %d: %s",
mSyncId, wc);
// "snapshot" all parents (as potential promotion targets). Do this before checking
@@ -296,7 +368,12 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
}
if (mParticipants.contains(wc)) return;
- mSyncEngine.addToSyncSet(mSyncId, wc);
+ // Wallpaper is like in a static drawn state unless display may have changes, so exclude
+ // the case to reduce transition latency waiting for the unchanged wallpaper to redraw.
+ final boolean needSyncDraw = !isWallpaper(wc) || mParticipants.contains(wc.mDisplayContent);
+ if (needSyncDraw) {
+ mSyncEngine.addToSyncSet(mSyncId, wc);
+ }
ChangeInfo info = mChanges.get(wc);
if (info == null) {
info = new ChangeInfo(wc);
@@ -326,7 +403,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* or waiting until after the animation to close).
*/
void collectExistenceChange(@NonNull WindowContainer wc) {
- if (mSyncId < 0) return;
+ if (mState >= STATE_PLAYING) {
+ // Too late to collect. Don't check too-early here since `collect` will check that.
+ return;
+ }
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Existence Changed in transition %d:"
+ " %s", mSyncId, wc);
collect(wc);
@@ -360,7 +440,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
*/
void setOverrideAnimation(TransitionInfo.AnimationOptions options,
@Nullable IRemoteCallback startCallback, @Nullable IRemoteCallback finishCallback) {
- if (mSyncId < 0) return;
+ if (!isCollecting()) return;
mOverrideOptions = options;
sendRemoteCallback(mClientAnimationStartCallback);
mClientAnimationStartCallback = startCallback;
@@ -378,7 +458,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* The transition will wait for all groups to be ready.
*/
void setReady(WindowContainer wc, boolean ready) {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setReadyFrom(wc, ready);
applyReady();
}
@@ -396,7 +476,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* @see ReadyTracker#setAllReady.
*/
void setAllReady() {
- if (mSyncId < 0) return;
+ if (!isCollecting() || mSyncId < 0) return;
mReadyTracker.setAllReady();
applyReady();
}
@@ -419,7 +499,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
for (int i = mTargets.size() - 1; i >= 0; --i) {
final WindowContainer target = mTargets.get(i);
if (target.getParent() != null) {
- final SurfaceControl targetLeash = getLeashSurface(target);
+ final SurfaceControl targetLeash = getLeashSurface(target, null /* t */);
final SurfaceControl origParent = getOrigParentSurface(target);
// Ensure surfaceControls are re-parented back into the hierarchy.
t.reparent(targetLeash, origParent);
@@ -428,14 +508,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
t.setPosition(targetLeash, tmpPos.x, tmpPos.y);
final Rect clipRect;
// No need to clip the display in case seeing the clipped content when during the
- // display rotation.
- if (target.asDisplayContent() != null) {
+ // display rotation. No need to clip activities because they rely on clipping on
+ // task layers.
+ if (target.asDisplayContent() != null || target.asActivityRecord() != null) {
clipRect = null;
- } else if (target.asActivityRecord() != null) {
- // Always use parent bounds of activity because letterbox area (e.g. fixed
- // aspect ratio or size compat mode) should be included.
- clipRect = target.getParent().getRequestedOverrideBounds();
- clipRect.offset(-tmpPos.x, -tmpPos.y);
} else {
clipRect = target.getRequestedOverrideBounds();
clipRect.offset(-tmpPos.x, -tmpPos.y);
@@ -456,9 +532,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
// Need to update layers on involved displays since they were all paused while
// the animation played. This puts the layers back into the correct order.
- for (int i = displays.size() - 1; i >= 0; --i) {
- if (displays.valueAt(i) == null) continue;
- displays.valueAt(i).assignChildLayers(t);
+ mController.mBuildingFinishLayers = true;
+ try {
+ for (int i = displays.size() - 1; i >= 0; --i) {
+ if (displays.valueAt(i) == null) continue;
+ displays.valueAt(i).assignChildLayers(t);
+ }
+ } finally {
+ mController.mBuildingFinishLayers = false;
}
if (rootLeash.isValid()) {
t.reparent(rootLeash, null);
@@ -474,6 +555,58 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mCanPipOnFinish = canPipOnFinish;
}
+ private boolean didCommitTransientLaunch() {
+ if (mTransientLaunches == null) return false;
+ for (int j = 0; j < mTransientLaunches.size(); ++j) {
+ if (mTransientLaunches.keyAt(j).isVisibleRequested()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Check if pip-entry is possible after finishing and enter-pip if it is.
+ *
+ * @return true if we are *guaranteed* to enter-pip. This means we return false if there's
+ * a chance we won't thus legacy-entry (via pause+userLeaving) will return false.
+ */
+ private boolean checkEnterPipOnFinish(@NonNull ActivityRecord ar) {
+ if (!mCanPipOnFinish || !ar.isVisible() || ar.getTask() == null) return false;
+
+ if (ar.pictureInPictureArgs != null && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
+ if (didCommitTransientLaunch()) {
+ // force enable pip-on-task-switch now that we've committed to actually launching
+ // to the transient activity.
+ ar.supportsEnterPipOnTaskSwitch = true;
+ }
+ return mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs,
+ false /* fromClient */);
+ }
+
+ // Legacy pip-entry (not via isAutoEnterEnabled).
+ boolean canPip = ar.getDeferHidingClient();
+ if (!canPip && didCommitTransientLaunch()) {
+ // force enable pip-on-task-switch now that we've committed to actually launching to the
+ // transient activity, and then recalculate whether we can attempt pip.
+ ar.supportsEnterPipOnTaskSwitch = true;
+ canPip = ar.checkEnterPictureInPictureState(
+ "finishTransition", true /* beforeStopping */)
+ && ar.isState(RESUMED);
+ }
+ if (!canPip) return false;
+ try {
+ // Legacy PIP-enter requires pause event with user-leaving.
+ mController.mAtm.mTaskSupervisor.mUserLeaving = true;
+ ar.getTaskFragment().startPausing(false /* uiSleeping */,
+ null /* resuming */, "finishTransition");
+ } finally {
+ mController.mAtm.mTaskSupervisor.mUserLeaving = false;
+ }
+ // Return false anyway because there's no guarantee that the app will enter pip.
+ return false;
+ }
+
/**
* The transition has finished animating and is ready to finalize WM state. This should not
* be called directly; use {@link TransitionController#finishTransition} instead.
@@ -489,7 +622,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
// Commit all going-invisible containers
- boolean activitiesWentInvisible = false;
for (int i = 0; i < mParticipants.size(); ++i) {
final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
if (ar != null) {
@@ -501,32 +633,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// then doing commitVisibility here would actually be out-of-order and leave the
// activity in a bad state.
if (!visibleAtTransitionEnd && !ar.isVisibleRequested()) {
- boolean commitVisibility = true;
- if (mCanPipOnFinish && ar.isVisible() && ar.getTask() != null) {
- if (ar.pictureInPictureArgs != null
- && ar.pictureInPictureArgs.isAutoEnterEnabled()) {
- if (mTransientLaunches != null) {
- for (int j = 0; j < mTransientLaunches.size(); ++j) {
- if (mTransientLaunches.keyAt(j).isVisibleRequested()) {
- // force enable pip-on-task-switch now that we've committed
- // to actually launching to the transient activity.
- ar.supportsEnterPipOnTaskSwitch = true;
- break;
- }
- }
- }
- mController.mAtm.enterPictureInPictureMode(ar, ar.pictureInPictureArgs);
- // Avoid commit visibility to false here, or else we will get a sudden
- // "flash" / surface going invisible for a split second.
- commitVisibility = false;
- } else if (ar.getDeferHidingClient()) {
- // Legacy PIP-enter requires pause event with user-leaving.
- mController.mAtm.mTaskSupervisor.mUserLeaving = true;
- ar.getTaskFragment().startPausing(false /* uiSleeping */,
- null /* resuming */, "finishTransition");
- mController.mAtm.mTaskSupervisor.mUserLeaving = false;
- }
- }
+ final boolean commitVisibility = !checkEnterPipOnFinish(ar);
+ // Avoid commit visibility if entering pip or else we will get a sudden
+ // "flash" / surface going invisible for a split second.
if (commitVisibility) {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
" Commit activity becoming invisible: %s", ar);
@@ -540,7 +649,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
ar.commitVisibility(false /* visible */, false /* performLayout */,
true /* fromTransition */);
- activitiesWentInvisible = true;
}
}
if (mChanges.get(ar).mVisible != visibleAtTransitionEnd) {
@@ -567,12 +675,23 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
mController.dispatchLegacyAppTransitionFinished(ar);
}
}
- if (activitiesWentInvisible) {
- // Always schedule stop processing when transition finishes because activities don't
- // stop while they are in a transition thus their stop could still be pending.
- mController.mAtm.mTaskSupervisor
- .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
+
+ // Update the input-sink (touch-blocking) state now that the animation is finished.
+ SurfaceControl.Transaction inputSinkTransaction = null;
+ for (int i = 0; i < mParticipants.size(); ++i) {
+ final ActivityRecord ar = mParticipants.valueAt(i).asActivityRecord();
+ if (ar == null || !ar.isVisible() || ar.getParent() == null) continue;
+ if (inputSinkTransaction == null) {
+ inputSinkTransaction = new SurfaceControl.Transaction();
+ }
+ ar.mActivityRecordInputSink.applyChangesToSurfaceIfChanged(inputSinkTransaction);
}
+ if (inputSinkTransaction != null) inputSinkTransaction.apply();
+
+ // Always schedule stop processing when transition finishes because activities don't
+ // stop while they are in a transition thus their stop could still be pending.
+ mController.mAtm.mTaskSupervisor
+ .scheduleProcessStoppingAndFinishingActivitiesIfNeeded();
sendRemoteCallback(mClientAnimationFinishCallback);
@@ -614,6 +733,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
dc.removeImeSurfaceImmediately();
dc.handleCompleteDeferredRemoval();
}
+
+ mState = STATE_FINISHED;
+ mController.mTransitionTracer.logState(this);
}
void abort() {
@@ -667,6 +789,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
mState = STATE_PLAYING;
+ mStartTransaction = transaction;
+ mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
mController.moveToPlaying(this);
if (dc.isKeyguardLocked()) {
@@ -675,7 +799,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// Resolve the animating targets from the participants
mTargets = calculateTargets(mParticipants, mChanges);
- final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges);
+ final TransitionInfo info = calculateTransitionInfo(mType, mFlags, mTargets, mChanges,
+ transaction);
if (mOverrideOptions != null) {
info.setAnimationOptions(mOverrideOptions);
}
@@ -685,8 +810,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
handleNonAppWindowsInTransition(dc, mType, mFlags);
- reportStartReasonsToLogger();
-
// The callback is only populated for custom activity-level client animations
sendRemoteCallback(mClientAnimationStartCallback);
@@ -728,6 +851,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
for (int i = mParticipants.size() - 1; i >= 0; --i) {
final WindowContainer wc = mParticipants.valueAt(i);
if (wc.asWindowToken() == null || !wc.isVisibleRequested()) continue;
+ // don't include transient launches, though, since those are only temporarily visible.
+ if (mTransientLaunches != null && wc.asActivityRecord() != null
+ && mTransientLaunches.containsKey(wc.asActivityRecord())) continue;
mVisibleAtTransitionEndTokens.add(wc.asWindowToken());
}
@@ -750,11 +876,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (controller != null && mTargets.contains(dc)) {
controller.setupStartTransaction(transaction);
}
- mStartTransaction = transaction;
- mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get();
buildFinishTransaction(mFinishTransaction, info.getRootLeash());
if (mController.getTransitionPlayer() != null) {
- mController.dispatchLegacyAppTransitionStarting(info);
+ mController.dispatchLegacyAppTransitionStarting(info, mStatusBarTransitionDelay);
try {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS,
"Calling onTransitionReady: %s", info);
@@ -773,8 +897,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
// No player registered, so just finish/apply immediately
cleanUpOnFailure();
}
- mSyncId = -1;
mOverrideOptions = null;
+
+ reportStartReasonsToLogger();
}
/**
@@ -832,26 +957,6 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
}
- // Hiding IME/IME icon when starting quick-step with resents animation.
- if (!mTargetDisplays.get(mRecentsDisplayId).isImeAttachedToApp()) {
- // Hiding IME if IME window is not attached to app.
- // Since some windowing mode is not proper to snapshot Task with IME window
- // while the app transitioning to the next task (e.g. split-screen mode)
- final InputMethodManagerInternal inputMethodManagerInternal =
- LocalServices.getService(InputMethodManagerInternal.class);
- if (inputMethodManagerInternal != null) {
- inputMethodManagerInternal.hideCurrentInputMethod(
- SoftInputShowHideReason.HIDE_RECENTS_ANIMATION);
- }
- } else {
- // Disable IME icon explicitly when IME attached to the app in case
- // IME icon might flickering while swiping to the next app task still
- // in animating before the next app window focused, or IME icon
- // persists on the bottom when swiping the task to recents.
- InputMethodManagerInternal.get().updateImeWindowStatus(
- true /* disableImeIcon */);
- }
-
// The rest of this function handles nav-bar reparenting
if (!dc.getDisplayPolicy().shouldAttachNavBarToAppDuringTransition()
@@ -987,11 +1092,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
for (int i = mParticipants.size() - 1; i >= 0; --i) {
ActivityRecord r = mParticipants.valueAt(i).asActivityRecord();
if (r == null || !r.mVisibleRequested) continue;
+ int transitionReason = APP_TRANSITION_WINDOWS_DRAWN;
// At this point, r is "ready", but if it's not "ALL ready" then it is probably only
// ready due to starting-window.
- reasons.put(r, (r.mStartingData instanceof SplashScreenStartingData
- && !r.mLastAllReadyAtSync)
- ? APP_TRANSITION_SPLASH_SCREEN : APP_TRANSITION_WINDOWS_DRAWN);
+ if (r.mStartingData instanceof SplashScreenStartingData && !r.mLastAllReadyAtSync) {
+ transitionReason = APP_TRANSITION_SPLASH_SCREEN;
+ } else if (r.isActivityTypeHomeOrRecents() && isTransientLaunch(r)) {
+ transitionReason = APP_TRANSITION_RECENTS_ANIM;
+ }
+ reasons.put(r, transitionReason);
}
mController.mAtm.mTaskSupervisor.getActivityMetricsLogger().notifyTransitionStarting(
reasons);
@@ -1020,6 +1129,10 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return wc.asWallpaperToken() != null;
}
+ private static boolean isInputMethod(WindowContainer wc) {
+ return wc.getWindowType() == TYPE_INPUT_METHOD;
+ }
+
private static boolean occludesKeyguard(WindowContainer wc) {
final ActivityRecord ar = wc.asActivityRecord();
if (ar != null) {
@@ -1036,6 +1149,26 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
return false;
}
+ private static boolean isTranslucent(@NonNull WindowContainer wc) {
+ final TaskFragment taskFragment = wc.asTaskFragment();
+ if (taskFragment != null) {
+ if (taskFragment.isTranslucent(null /* starting */)) {
+ return true;
+ }
+ final TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+ if (adjacentTaskFragment != null) {
+ // Treat the TaskFragment as translucent if its adjacent TF is, otherwise everything
+ // behind two adjacent TaskFragments are occluded.
+ return adjacentTaskFragment.isTranslucent(null /* starting */);
+ }
+ }
+ // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
+ // it is effected by child visibility but needs to work even
+ // before visibility is committed. This means refactoring some
+ // checks to use requested visibility.
+ return !wc.fillsParent();
+ }
+
/**
* Under some conditions (eg. all visible targets within a parent container are transitioning
* the same way) the transition can be "promoted" to the parent container. This means an
@@ -1226,8 +1359,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
}
- /** Gets the leash surface for a window container */
- private static SurfaceControl getLeashSurface(WindowContainer wc) {
+ /**
+ * Gets the leash surface for a window container.
+ * @param t a transaction to create leashes on when necessary (fixed rotation at token-level).
+ * If t is null, then this will not create any leashes, just use one if it is there --
+ * this is relevant for building the finishTransaction since it needs to match the
+ * start state and not erroneously create a leash of its own.
+ */
+ private static SurfaceControl getLeashSurface(WindowContainer wc,
+ @Nullable SurfaceControl.Transaction t) {
final DisplayContent asDC = wc.asDisplayContent();
if (asDC != null) {
// DisplayContent is the "root", so we use the windowing layer instead to avoid
@@ -1239,7 +1379,8 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (asToken != null) {
// WindowTokens can have a fixed-rotation applied to them. In the current
// implementation this fact is hidden from the player, so we must create a leash.
- final SurfaceControl leash = asToken.getOrCreateFixedRotationLeash();
+ final SurfaceControl leash = t != null ? asToken.getOrCreateFixedRotationLeash(t)
+ : asToken.getFixedRotationLeash();
if (leash != null) return leash;
}
}
@@ -1268,12 +1409,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* Construct a TransitionInfo object from a set of targets and changes. Also populates the
* root surface.
* @param sortedTargets The targets sorted by z-order from top (index 0) to bottom.
+ * @param startT The start transaction - used to set-up new leashes.
*/
@VisibleForTesting
@NonNull
static TransitionInfo calculateTransitionInfo(@TransitionType int type, int flags,
ArrayList<WindowContainer> sortedTargets,
- ArrayMap<WindowContainer, ChangeInfo> changes) {
+ ArrayMap<WindowContainer, ChangeInfo> changes,
+ @Nullable SurfaceControl.Transaction startT) {
final TransitionInfo out = new TransitionInfo(type, flags);
WindowContainer<?> topApp = null;
@@ -1311,10 +1454,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
final SurfaceControl rootLeash = leashReference.makeAnimationLeash().setName(
"Transition Root: " + leashReference.getName()).build();
- SurfaceControl.Transaction t = ancestor.mWmService.mTransactionFactory.get();
- t.setLayer(rootLeash, leashReference.getLastLayer());
- t.apply();
- t.close();
+ startT.setLayer(rootLeash, leashReference.getLastLayer());
out.setRootLeash(rootLeash, ancestor.getBounds().left, ancestor.getBounds().top);
// Convert all the resolved ChangeInfos into TransactionInfo.Change objects in order.
@@ -1324,7 +1464,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
final ChangeInfo info = changes.get(target);
final TransitionInfo.Change change = new TransitionInfo.Change(
target.mRemoteToken != null ? target.mRemoteToken.toWindowContainerToken()
- : null, getLeashSurface(target));
+ : null, getLeashSurface(target, startT));
// TODO(shell-transitions): Use leash for non-organized windows.
if (info.mParent != null) {
change.setParent(info.mParent.mRemoteToken.toWindowContainerToken());
@@ -1481,7 +1621,7 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
boolean getLegacyIsReady() {
- return (mState == STATE_STARTED || mState == STATE_COLLECTING) && mSyncId >= 0;
+ return isCollecting() && mSyncId >= 0;
}
static Transition fromBinder(IBinder binder) {
@@ -1497,10 +1637,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
* seamless rotation. This is currently only used by DisplayContent during fixed-rotation.
*/
private static final int FLAG_SEAMLESS_ROTATION = 1;
+ private static final int FLAG_TRANSIENT_LAUNCH = 2;
+ private static final int FLAG_ABOVE_TRANSIENT_LAUNCH = 4;
@IntDef(prefix = { "FLAG_" }, value = {
FLAG_NONE,
- FLAG_SEAMLESS_ROTATION
+ FLAG_SEAMLESS_ROTATION,
+ FLAG_TRANSIENT_LAUNCH,
+ FLAG_ABOVE_TRANSIENT_LAUNCH
})
@Retention(RetentionPolicy.SOURCE)
@interface Flag {}
@@ -1537,6 +1681,11 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
}
boolean hasChanged(@NonNull WindowContainer newState) {
+ // the task including transient launch must promote to root task
+ if ((mFlags & ChangeInfo.FLAG_TRANSIENT_LAUNCH) != 0
+ || (mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+ return true;
+ }
// If it's invisible and hasn't changed visibility, always return false since even if
// something changed, it wouldn't be a visible change.
final boolean currVisible = newState.isVisibleRequested();
@@ -1552,6 +1701,9 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
@TransitionInfo.TransitionMode
int getTransitMode(@NonNull WindowContainer wc) {
+ if ((mFlags & ChangeInfo.FLAG_ABOVE_TRANSIENT_LAUNCH) != 0) {
+ return TRANSIT_CLOSE;
+ }
final boolean nowVisible = wc.isVisibleRequested();
if (nowVisible == mVisible) {
return TRANSIT_CHANGE;
@@ -1569,20 +1721,13 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (mShowWallpaper || wc.showWallpaper()) {
flags |= FLAG_SHOW_WALLPAPER;
}
- if (!wc.fillsParent()) {
- // TODO(b/172695805): hierarchical check. This is non-trivial because for containers
- // it is effected by child visibility but needs to work even
- // before visibility is committed. This means refactoring some
- // checks to use requested visibility.
+ if (isTranslucent(wc)) {
flags |= FLAG_TRANSLUCENT;
}
final Task task = wc.asTask();
if (task != null && task.voiceSession != null) {
flags |= FLAG_IS_VOICE_INTERACTION;
}
- if (task != null && task.isTranslucent(null)) {
- flags |= FLAG_TRANSLUCENT;
- }
final ActivityRecord record = wc.asActivityRecord();
if (record != null) {
if (record.mUseTransferredAnimation) {
@@ -1602,14 +1747,33 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
if (isWallpaper(wc)) {
flags |= FLAG_IS_WALLPAPER;
}
+ if (isInputMethod(wc)) {
+ flags |= FLAG_IS_INPUT_METHOD;
+ }
if (occludesKeyguard(wc)) {
flags |= FLAG_OCCLUDES_KEYGUARD;
}
+ if (wc.isEmbedded()) {
+ flags |= FLAG_IS_EMBEDDED;
+ }
return flags;
}
}
/**
+ * This transition will be considered not-ready until a corresponding call to
+ * {@link #continueTransitionReady}
+ */
+ void deferTransitionReady() {
+ ++mReadyTracker.mDeferReadyDepth;
+ }
+
+ /** This undoes one call to {@link #deferTransitionReady}. */
+ void continueTransitionReady() {
+ --mReadyTracker.mDeferReadyDepth;
+ }
+
+ /**
* The transition sync mechanism has 2 parts:
* 1. Whether all WM operations for a particular transition are "ready" (eg. did the app
* launch or stop or get a new configuration?).
@@ -1639,6 +1803,14 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
private boolean mReadyOverride = false;
/**
+ * When non-zero, this transition is forced not-ready (even over setAllReady()). Use this
+ * (via deferTransitionReady/continueTransitionReady) for situations where we want to do
+ * bulk operations which could trigger surface-placement but the existing ready-state
+ * isn't known.
+ */
+ private int mDeferReadyDepth = 0;
+
+ /**
* Adds a ready-group. Any setReady calls in this subtree will be tracked together. For
* now these are only DisplayContents.
*/
@@ -1678,8 +1850,15 @@ class Transition extends Binder implements BLASTSyncEngine.TransactionReadyListe
/** @return true if all tracked subtrees are ready. */
boolean allReady() {
ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, " allReady query: used=%b "
- + "override=%b states=[%s]", mUsed, mReadyOverride, groupsToString());
+ + "override=%b defer=%d states=[%s]", mUsed, mReadyOverride, mDeferReadyDepth,
+ groupsToString());
+ // If the readiness has never been touched, mUsed will be false. We never want to
+ // consider a transition ready if nothing has been reported on it.
if (!mUsed) return false;
+ // If we are deferring readiness, we never report ready. This is usually temporary.
+ if (mDeferReadyDepth > 0) return false;
+ // Next check all the ready groups to see if they are ready. We can short-cut this if
+ // ready-override is set (which is treated as "everything is marked ready").
if (mReadyOverride) return true;
for (int i = mReadyGroups.size() - 1; i >= 0; --i) {
final WindowContainer wc = mReadyGroups.keyAt(i);
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index c1c390eea932..a02be25bc8d2 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -75,6 +75,7 @@ class TransitionController {
private ITransitionPlayer mTransitionPlayer;
final TransitionMetricsReporter mTransitionMetricsReporter = new TransitionMetricsReporter();
+ final TransitionTracer mTransitionTracer;
private IApplicationThread mTransitionPlayerThread;
final ActivityTaskManagerService mAtm;
@@ -99,11 +100,21 @@ class TransitionController {
// TODO(b/188595497): remove when not needed.
final StatusBarManagerInternal mStatusBar;
+ /**
+ * `true` when building surface layer order for the finish transaction. We want to prevent
+ * wm from touching z-order of surfaces during transitions, but we still need to be able to
+ * calculate the layers for the finishTransaction. So, when assigning layers into the finish
+ * transaction, set this to true so that the {@link canAssignLayers} will allow it.
+ */
+ boolean mBuildingFinishLayers = false;
+
TransitionController(ActivityTaskManagerService atm,
- TaskSnapshotController taskSnapshotController) {
+ TaskSnapshotController taskSnapshotController,
+ TransitionTracer transitionTracer) {
mAtm = atm;
mStatusBar = LocalServices.getService(StatusBarManagerInternal.class);
mTaskSnapshotController = taskSnapshotController;
+ mTransitionTracer = transitionTracer;
mTransitionPlayerDeath = () -> {
synchronized (mAtm.mGlobalLock) {
// Clean-up/finish any playing transitions.
@@ -207,6 +218,18 @@ class TransitionController {
}
/**
+ * @return {@code true} if transition is actively collecting changes and `wc` is one of them
+ * or a descendant of one of them. {@code false} once playing.
+ */
+ boolean inCollectingTransition(@NonNull WindowContainer wc) {
+ if (!isCollecting()) return false;
+ for (WindowContainer p = wc; p != null; p = p.getParent()) {
+ if (mCollectingTransition.mParticipants.contains(p)) return true;
+ }
+ return false;
+ }
+
+ /**
* @return {@code true} if transition is actively playing. This is not necessarily {@code true}
* during collection.
*/
@@ -214,6 +237,18 @@ class TransitionController {
return !mPlayingTransitions.isEmpty();
}
+ /**
+ * @return {@code true} if one of the playing transitions contains `wc`.
+ */
+ boolean inPlayingTransition(@NonNull WindowContainer wc) {
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ for (WindowContainer p = wc; p != null; p = p.getParent()) {
+ if (mPlayingTransitions.get(i).mParticipants.contains(p)) return true;
+ }
+ }
+ return false;
+ }
+
/** @return {@code true} if a transition is running */
boolean inTransition() {
// TODO(shell-transitions): eventually properly support multiple
@@ -222,19 +257,7 @@ class TransitionController {
/** @return {@code true} if a transition is running in a participant subtree of wc */
boolean inTransition(@NonNull WindowContainer wc) {
- if (isCollecting()) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- if (isCollecting(p)) return true;
- }
- }
- for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
- for (WindowContainer p = wc; p != null; p = p.getParent()) {
- if (mPlayingTransitions.get(i).mParticipants.contains(p)) {
- return true;
- }
- }
- }
- return false;
+ return inCollectingTransition(wc) || inPlayingTransition(wc);
}
boolean inRecentsTransition(@NonNull WindowContainer wc) {
@@ -270,6 +293,16 @@ class TransitionController {
return false;
}
+ boolean isTransientHide(@NonNull Task task) {
+ if (mCollectingTransition != null && mCollectingTransition.isTransientHide(task)) {
+ return true;
+ }
+ for (int i = mPlayingTransitions.size() - 1; i >= 0; --i) {
+ if (mPlayingTransitions.get(i).isTransientHide(task)) return true;
+ }
+ return false;
+ }
+
/**
* @return {@code true} if {@param ar} is part of a transient-launch activity in an active
* transition.
@@ -284,6 +317,15 @@ class TransitionController {
return false;
}
+ /**
+ * Whether WM can assign layers to window surfaces at this time. This is usually false while
+ * playing, but can be "opened-up" for certain transition operations like calculating layers
+ * for finishTransaction.
+ */
+ boolean canAssignLayers() {
+ return mBuildingFinishLayers || !isPlaying();
+ }
+
@WindowConfiguration.WindowingMode
int getWindowingModeAtStart(@NonNull WindowContainer wc) {
if (mCollectingTransition == null) return wc.getWindowingMode();
@@ -418,23 +460,32 @@ class TransitionController {
* Collects the window containers which need to be synced with the changing display (e.g.
* rotating) to the given transition or the current collecting transition.
*/
- void collectForDisplayChange(@NonNull DisplayContent dc, @Nullable Transition incoming) {
+ void collectForDisplayAreaChange(@NonNull DisplayArea<?> wc, @Nullable Transition incoming) {
if (incoming == null) incoming = mCollectingTransition;
if (incoming == null) return;
final Transition transition = incoming;
// Collect all visible tasks.
- dc.forAllLeafTasks(task -> {
+ wc.forAllLeafTasks(task -> {
if (task.isVisible()) {
transition.collect(task);
}
}, true /* traverseTopToBottom */);
// Collect all visible non-app windows which need to be drawn before the animation starts.
- dc.forAllWindows(w -> {
- if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
- && dc.shouldSyncRotationChange(w)) {
- transition.collect(w.mToken);
- }
- }, true /* traverseTopToBottom */);
+ final DisplayContent dc = wc.asDisplayContent();
+ if (dc != null) {
+ wc.forAllWindows(w -> {
+ if (w.mActivityRecord == null && w.isVisible() && !isCollecting(w.mToken)
+ && dc.shouldSyncRotationChange(w)) {
+ transition.collect(w.mToken);
+ }
+ }, true /* traverseTopToBottom */);
+ }
+ }
+
+ /** @see Transition#mStatusBarTransitionDelay */
+ void setStatusBarTransitionDelay(long delay) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.mStatusBarTransitionDelay = delay;
}
/** @see Transition#setOverrideAnimation */
@@ -455,6 +506,24 @@ class TransitionController {
setReady(wc, true);
}
+ /** @see Transition#deferTransitionReady */
+ void deferTransitionReady() {
+ if (!isShellTransitionsEnabled()) return;
+ if (mCollectingTransition == null) {
+ throw new IllegalStateException("No collecting transition to defer readiness for.");
+ }
+ mCollectingTransition.deferTransitionReady();
+ }
+
+ /** @see Transition#continueTransitionReady */
+ void continueTransitionReady() {
+ if (!isShellTransitionsEnabled()) return;
+ if (mCollectingTransition == null) {
+ throw new IllegalStateException("No collecting transition to defer readiness for.");
+ }
+ mCollectingTransition.continueTransitionReady();
+ }
+
/** @see Transition#finishTransition */
void finishTransition(@NonNull IBinder token) {
// It is usually a no-op but make sure that the metric consumer is removed.
@@ -484,6 +553,7 @@ class TransitionController {
setAnimationRunning(true /* running */);
}
mPlayingTransitions.add(transition);
+ mTransitionTracer.logState(transition);
}
private void setAnimationRunning(boolean running) {
@@ -502,6 +572,7 @@ class TransitionController {
}
transition.abort();
mCollectingTransition = null;
+ mTransitionTracer.logState(transition);
}
/**
@@ -526,6 +597,12 @@ class TransitionController {
}
}
+ /** @see Transition#setCanPipOnFinish */
+ void setCanPipOnFinish(boolean canPipOnFinish) {
+ if (mCollectingTransition == null) return;
+ mCollectingTransition.setCanPipOnFinish(canPipOnFinish);
+ }
+
void legacyDetachNavigationBarFromApp(@NonNull IBinder token) {
final Transition transition = Transition.fromBinder(token);
if (transition == null || !mPlayingTransitions.contains(transition)) {
@@ -549,13 +626,14 @@ class TransitionController {
}
}
- void dispatchLegacyAppTransitionStarting(TransitionInfo info) {
+ void dispatchLegacyAppTransitionStarting(TransitionInfo info, long statusBarTransitionDelay) {
final boolean keyguardGoingAway = info.isKeyguardGoingAway();
for (int i = 0; i < mLegacyListeners.size(); ++i) {
// TODO(shell-transitions): handle (un)occlude transition.
mLegacyListeners.get(i).onAppTransitionStartingLocked(keyguardGoingAway,
false /* keyguardOcclude */, 0 /* durationHint */,
- SystemClock.uptimeMillis(), AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
+ SystemClock.uptimeMillis() + statusBarTransitionDelay,
+ AnimationAdapter.STATUS_BAR_TRANSITION_DURATION);
}
}
diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java
new file mode 100644
index 000000000000..c1927d864320
--- /dev/null
+++ b/services/core/java/com/android/server/wm/TransitionTracer.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.os.Build.IS_USER;
+
+import static com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS;
+import static com.android.server.wm.shell.ChangeInfo.HAS_CHANGED;
+import static com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE;
+import static com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER;
+import static com.android.server.wm.shell.Transition.CHANGE;
+import static com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID;
+import static com.android.server.wm.shell.Transition.FLAGS;
+import static com.android.server.wm.shell.Transition.ID;
+import static com.android.server.wm.shell.Transition.START_TRANSACTION_ID;
+import static com.android.server.wm.shell.Transition.STATE;
+import static com.android.server.wm.shell.Transition.TIMESTAMP;
+import static com.android.server.wm.shell.Transition.TRANSITION_TYPE;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H;
+import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L;
+import static com.android.server.wm.shell.TransitionTraceProto.TRANSITION;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.SystemClock;
+import android.os.Trace;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.util.TraceBuffer;
+import com.android.server.wm.Transition.ChangeInfo;
+import com.android.server.wm.shell.TransitionTraceProto;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * Helper class to collect and dump transition traces.
+ */
+public class TransitionTracer {
+
+ private static final String LOG_TAG = "TransitionTracer";
+
+ /**
+ * Maximum buffer size, currently defined as 5 MB
+ */
+ private static final int BUFFER_CAPACITY = 5120 * 1024; // 5 MB
+ static final String WINSCOPE_EXT = ".winscope";
+ private static final String TRACE_FILE = "/data/misc/wmtrace/transition_trace" + WINSCOPE_EXT;
+ private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L;
+
+ private final TransitionTraceBuffer mTraceBuffer = new TransitionTraceBuffer();
+
+ private final Object mEnabledLock = new Object();
+ private volatile boolean mEnabled = false;
+
+ private long mTraceStartTimestamp;
+
+ private class TransitionTraceBuffer {
+ private final TraceBuffer mBuffer = new TraceBuffer(BUFFER_CAPACITY);
+
+ private void pushTransitionState(Transition transition) {
+ final ProtoOutputStream outputStream = new ProtoOutputStream();
+ final long transitionEntryToken = outputStream.start(TRANSITION);
+
+ outputStream.write(ID, transition.getSyncId());
+ outputStream.write(TIMESTAMP, SystemClock.elapsedRealtimeNanos());
+ outputStream.write(TRANSITION_TYPE, transition.mType);
+ outputStream.write(STATE, transition.getState());
+ outputStream.write(FLAGS, transition.getFlags());
+ if (transition.getStartTransaction() != null) {
+ outputStream.write(START_TRANSACTION_ID, transition.getStartTransaction().getId());
+ }
+ if (transition.getFinishTransaction() != null) {
+ outputStream.write(FINISH_TRANSACTION_ID,
+ transition.getFinishTransaction().getId());
+ }
+
+ for (int i = 0; i < transition.mChanges.size(); ++i) {
+ final WindowContainer window = transition.mChanges.keyAt(i);
+ final ChangeInfo changeInfo = transition.mChanges.valueAt(i);
+ writeChange(outputStream, window, changeInfo);
+ }
+
+ outputStream.end(transitionEntryToken);
+
+ mBuffer.add(outputStream);
+ }
+
+ private void writeChange(ProtoOutputStream outputStream, WindowContainer window,
+ ChangeInfo changeInfo) {
+ Trace.beginSection("TransitionProto#addChange");
+ final long changeEntryToken = outputStream.start(CHANGE);
+
+ final int transitMode = changeInfo.getTransitMode(window);
+ final boolean hasChanged = changeInfo.hasChanged(window);
+ final int changeFlags = changeInfo.getChangeFlags(window);
+
+ outputStream.write(TRANSIT_MODE, transitMode);
+ outputStream.write(HAS_CHANGED, hasChanged);
+ outputStream.write(CHANGE_FLAGS, changeFlags);
+ window.writeIdentifierToProto(outputStream, WINDOW_IDENTIFIER);
+
+ outputStream.end(changeEntryToken);
+ Trace.endSection();
+ }
+
+ public void writeToFile(File file, ProtoOutputStream proto) throws IOException {
+ mBuffer.writeTraceToFile(file, proto);
+ }
+
+ public void reset() {
+ mBuffer.resetBuffer();
+ }
+ }
+
+ /**
+ * Records the current state of a transition in the transition trace (if it is running).
+ * @param transition the transition that we want to record the state of.
+ */
+ public void logState(com.android.server.wm.Transition transition) {
+ if (!mEnabled) {
+ return;
+ }
+
+ Log.d(LOG_TAG, "Logging state of transition " + transition);
+ mTraceBuffer.pushTransitionState(transition);
+ }
+
+ /**
+ * Starts collecting transitions for the trace.
+ * If called while a trace is already running, this will reset the trace.
+ */
+ public void startTrace(@Nullable PrintWriter pw) {
+ if (IS_USER) {
+ LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+ return;
+ }
+ Trace.beginSection("TransitionTracer#startTrace");
+ LogAndPrintln.i(pw, "Starting shell transition trace.");
+ synchronized (mEnabledLock) {
+ mTraceStartTimestamp = SystemClock.elapsedRealtime();
+ mEnabled = true;
+ mTraceBuffer.reset();
+ }
+ Trace.endSection();
+ }
+
+ /**
+ * Stops collecting the transition trace and dump to trace to file.
+ *
+ * Dumps the trace to @link{TRACE_FILE}.
+ */
+ public void stopTrace(@Nullable PrintWriter pw) {
+ stopTrace(pw, new File(TRACE_FILE));
+ }
+
+ /**
+ * Stops collecting the transition trace and dump to trace to file.
+ * @param outputFile The file to dump the transition trace to.
+ */
+ public void stopTrace(@Nullable PrintWriter pw, File outputFile) {
+ if (IS_USER) {
+ LogAndPrintln.e(pw, "Tracing is not supported on user builds.");
+ return;
+ }
+ Trace.beginSection("TransitionTracer#stopTrace");
+ LogAndPrintln.i(pw, "Stopping shell transition trace.");
+ synchronized (mEnabledLock) {
+ if (!mEnabled) {
+ LogAndPrintln.e(pw,
+ "Error: Tracing can't be stopped because it hasn't been started.");
+ return;
+ }
+
+ mEnabled = false;
+ writeTraceToFileLocked(pw, outputFile);
+ }
+ Trace.endSection();
+ }
+
+ boolean isEnabled() {
+ return mEnabled;
+ }
+
+ private void writeTraceToFileLocked(@Nullable PrintWriter pw, File file) {
+ Trace.beginSection("TransitionTracer#writeTraceToFileLocked");
+ try {
+ ProtoOutputStream proto = new ProtoOutputStream();
+ proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
+ proto.write(TransitionTraceProto.TIMESTAMP, mTraceStartTimestamp);
+ int pid = android.os.Process.myPid();
+ LogAndPrintln.i(pw, "Writing file to " + file.getAbsolutePath()
+ + " from process " + pid);
+ mTraceBuffer.writeToFile(file, proto);
+ } catch (IOException e) {
+ LogAndPrintln.e(pw, "Unable to write buffer to file", e);
+ }
+ Trace.endSection();
+ }
+
+ private static class LogAndPrintln {
+ private static void i(@Nullable PrintWriter pw, String msg) {
+ Log.i(LOG_TAG, msg);
+ if (pw != null) {
+ pw.println(msg);
+ pw.flush();
+ }
+ }
+
+ private static void e(@Nullable PrintWriter pw, String msg) {
+ Log.e(LOG_TAG, msg);
+ if (pw != null) {
+ pw.println("ERROR: " + msg);
+ pw.flush();
+ }
+ }
+
+ private static void e(@Nullable PrintWriter pw, String msg, @NonNull Exception e) {
+ Log.e(LOG_TAG, msg, e);
+ if (pw != null) {
+ pw.println("ERROR: " + msg + " ::\n " + e);
+ pw.flush();
+ }
+ }
+ }
+}
diff --git a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
index 5e963cc5ae8e..41c1e793dd90 100644
--- a/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
+++ b/services/core/java/com/android/server/wm/UnknownAppVisibilityController.java
@@ -69,6 +69,10 @@ class UnknownAppVisibilityController {
return mUnknownApps.isEmpty();
}
+ boolean isVisibilityUnknown(ActivityRecord r) {
+ return mUnknownApps.containsKey(r);
+ }
+
void clear() {
mUnknownApps.clear();
}
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index a32a6087a04d..6245005606d7 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -21,6 +21,7 @@ import static android.app.WallpaperManager.COMMAND_UNFREEZE;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
@@ -181,15 +182,11 @@ class WallpaperController {
mFindResults.setUseTopWallpaperAsTarget(true);
}
- final RecentsAnimationController recentsAnimationController =
- mService.getRecentsAnimationController();
final boolean animationWallpaper = animatingContainer != null
&& animatingContainer.getAnimation() != null
&& animatingContainer.getAnimation().getShowWallpaper();
final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
- final boolean isRecentsTransitionTarget = (recentsAnimationController != null
- && recentsAnimationController.isWallpaperVisible(w));
- if (isRecentsTransitionTarget) {
+ if (isRecentsTransitionTarget(w)) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
return true;
@@ -213,6 +210,22 @@ class WallpaperController {
return false;
};
+ private boolean isRecentsTransitionTarget(WindowState w) {
+ if (w.mTransitionController.isShellTransitionsEnabled()) {
+ // Because the recents activity is invisible in background while keyguard is occluded
+ // (the activity window is on screen while keyguard is locked) with recents animation,
+ // the task animating by recents needs to be wallpaper target to make wallpaper visible.
+ // While for unlocked case, because recents activity will be moved to top, it can be
+ // the wallpaper target naturally.
+ return w.mActivityRecord != null && w.mAttrs.type == TYPE_BASE_APPLICATION
+ && mDisplayContent.isKeyguardLocked()
+ && w.mTransitionController.isTransientHide(w.getTask());
+ }
+ // The window is either the recents activity or is in the task animating by the recents.
+ final RecentsAnimationController controller = mService.getRecentsAnimationController();
+ return controller != null && controller.isWallpaperVisible(w);
+ }
+
/**
* @see #computeLastWallpaperZoomOut()
*/
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index d7257180f01b..d9b25adb8c4c 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -48,6 +48,7 @@ import static com.android.server.wm.IdentifierProto.TITLE;
import static com.android.server.wm.IdentifierProto.USER_ID;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_ALL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
+import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
@@ -428,6 +429,13 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (insetsTypes == null || insetsTypes.length == 0) {
throw new IllegalArgumentException("Insets type not specified.");
}
+ if (mDisplayContent == null) {
+ // This is possible this container is detached when WM shell is responding to a previous
+ // request. WM shell will be updated when this container is attached again and the
+ // insets need to be updated.
+ Slog.w(TAG, "Can't add local rect insets source provider when detached. " + this);
+ return;
+ }
if (mLocalInsetsSourceProviders == null) {
mLocalInsetsSourceProviders = new SparseArray<>();
}
@@ -1010,6 +1018,9 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (dc != null && dc != this) {
dc.getPendingTransaction().merge(mPendingTransaction);
}
+ if (dc != this && mLocalInsetsSourceProviders != null) {
+ mLocalInsetsSourceProviders.clear();
+ }
for (int i = mChildren.size() - 1; i >= 0; --i) {
final WindowContainer child = mChildren.get(i);
child.onDisplayChanged(dc);
@@ -1159,6 +1170,19 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
/**
+ * Returns {@code true} if self or the parent container of the window is in transition, e.g.
+ * the app or recents transition. This method is only used when legacy and shell transition
+ * have the same condition to check the animation state.
+ */
+ boolean inTransitionSelfOrParent() {
+ if (!mTransitionController.isShellTransitionsEnabled()) {
+ return isAnimating(PARENTS | TRANSITION,
+ ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
+ }
+ return inTransition();
+ }
+
+ /**
* @return Whether our own container running an animation at the moment.
*/
final boolean isAnimating() {
@@ -1180,7 +1204,8 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
if (!mTransitionController.isShellTransitionsEnabled()) {
return isAnimating(TRANSITION | CHILDREN, WindowState.EXIT_ANIMATING_TYPES);
}
- if (mTransitionController.isCollecting(this)) {
+ // Only check leaf containers because inTransition() includes parent.
+ if (mChildren.isEmpty() && inTransition()) {
return true;
}
@@ -2458,7 +2483,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void assignLayer(Transaction t, int layer) {
// Don't assign layers while a transition animation is playing
// TODO(b/173528115): establish robust best-practices around z-order fighting.
- if (mTransitionController.isPlaying()) return;
+ if (!mTransitionController.canAssignLayers()) return;
final boolean changed = layer != mLastLayer || mLastRelativeToLayer != null;
if (mSurfaceControl != null && changed) {
setLayer(t, layer);
@@ -2679,9 +2704,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* will be applied.
*/
void scheduleAnimation() {
- if (mParent != null) {
- mParent.scheduleAnimation();
- }
+ mWmService.scheduleAnimationLocked();
}
/**
@@ -2801,6 +2824,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* snapshot from {@link #getFreezeSnapshotTarget()}.
*/
void initializeChangeTransition(Rect startBounds, @Nullable SurfaceControl freezeTarget) {
+ if (mDisplayContent.mTransitionController.isShellTransitionsEnabled()) {
+ // TODO(b/207070762): request shell transition for activityEmbedding change.
+ return;
+ }
mDisplayContent.prepareAppTransition(TRANSIT_CHANGE);
mDisplayContent.mChangingContainers.add(this);
// Calculate the relative position in parent container.
@@ -2925,18 +2952,34 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
getAnimationPosition(mTmpPoint);
mTmpRect.offsetTo(0, 0);
- final RemoteAnimationController controller =
- getDisplayContent().mAppTransition.getRemoteAnimationController();
+ final AppTransition appTransition = getDisplayContent().mAppTransition;
+ final RemoteAnimationController controller = appTransition.getRemoteAnimationController();
final boolean isChanging = AppTransition.isChangeTransitOld(transit) && enter
&& isChangingAppTransition();
// Delaying animation start isn't compatible with remote animations at all.
if (controller != null && !mSurfaceAnimator.isAnimationStartDelayed()) {
+ // Here we load App XML in order to read com.android.R.styleable#Animation_showBackdrop.
+ boolean showBackdrop = false;
+ // Optionally set backdrop color if App explicitly provides it through
+ // {@link Activity#overridePendingTransition(int, int, int)}.
+ @ColorInt int backdropColor = 0;
+ if (controller.isFromActivityEmbedding()) {
+ final int animAttr = AppTransition.mapOpenCloseTransitTypes(transit, enter);
+ final Animation a = animAttr != 0
+ ? appTransition.loadAnimationAttr(lp, animAttr, transit) : null;
+ showBackdrop = a != null && a.getShowBackdrop();
+ backdropColor = appTransition.getNextAppTransitionBackgroundColor();
+ }
final Rect localBounds = new Rect(mTmpRect);
localBounds.offsetTo(mTmpPoint.x, mTmpPoint.y);
final RemoteAnimationController.RemoteAnimationRecord adapters =
- controller.createRemoteAnimationRecord(this, mTmpPoint, localBounds,
- screenBounds, (isChanging ? mSurfaceFreezer.mFreezeBounds : null));
+ controller.createRemoteAnimationRecord(
+ this, mTmpPoint, localBounds, screenBounds,
+ (isChanging ? mSurfaceFreezer.mFreezeBounds : null), showBackdrop);
+ if (backdropColor != 0) {
+ adapters.setBackDropColor(backdropColor);
+ }
if (!isChanging) {
adapters.setMode(enter
? RemoteAnimationTarget.MODE_OPENING
@@ -3183,6 +3226,10 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
void resetSurfacePositionForAnimationLeash(Transaction t) {
t.setPosition(mSurfaceControl, 0, 0);
+ if (mSyncState != SYNC_STATE_NONE && t != mSyncTransaction) {
+ // Avoid restoring to old position if the sync transaction is applied later.
+ mSyncTransaction.setPosition(mSurfaceControl, 0, 0);
+ }
mLastSurfacePosition.set(0, 0);
}
@@ -3664,10 +3711,6 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
mChildren.get(i).finishSync(outMergedTransaction, cancel);
}
if (cancel && mSyncGroup != null) mSyncGroup.onCancelSync(this);
- clearSyncState();
- }
-
- void clearSyncState() {
mSyncState = SYNC_STATE_NONE;
mSyncGroup = null;
}
@@ -3727,6 +3770,15 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
* hierarchy change implies a configuration change.
*/
private void onSyncReparent(WindowContainer oldParent, WindowContainer newParent) {
+ // Check if this is changing displays. If so, mark the old display as "ready" for
+ // transitions. This is to work around the problem where setting readiness against this
+ // container will only set the new display as ready and leave the old display as unready.
+ if (mSyncState != SYNC_STATE_NONE && oldParent != null
+ && oldParent.getDisplayContent() != null && (newParent == null
+ || oldParent.getDisplayContent() != newParent.getDisplayContent())) {
+ mTransitionController.setReady(oldParent.getDisplayContent());
+ }
+
if (newParent == null || newParent.mSyncState == SYNC_STATE_NONE) {
if (mSyncState == SYNC_STATE_NONE) {
return;
diff --git a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
index 9b6f4d947694..b000a9841d06 100644
--- a/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
+++ b/services/core/java/com/android/server/wm/WindowContainerThumbnail.java
@@ -33,7 +33,6 @@ import android.graphics.GraphicBuffer;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.hardware.HardwareBuffer;
-import android.os.Process;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl;
import android.view.SurfaceControl.Builder;
@@ -91,7 +90,7 @@ class WindowContainerThumbnail implements Animatable {
.setBLASTLayer()
.setFormat(PixelFormat.TRANSLUCENT)
.setMetadata(METADATA_WINDOW_TYPE, mWindowContainer.getWindowingMode())
- .setMetadata(METADATA_OWNER_UID, Process.myUid())
+ .setMetadata(METADATA_OWNER_UID, WindowManagerService.MY_UID)
.setCallsite("WindowContainerThumbnail")
.build();
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 902218621cd0..9d6e250e8a47 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -48,6 +48,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON
import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES;
import static android.provider.Settings.Global.DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR;
import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH;
+import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.Display.INVALID_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
@@ -89,6 +90,7 @@ import static android.view.WindowManager.REMOVE_CONTENT_MODE_UNDEFINED;
import static android.view.WindowManager.TRANSIT_NONE;
import static android.view.WindowManager.TRANSIT_RELAUNCH;
import static android.view.WindowManagerGlobal.ADD_OKAY;
+import static android.view.WindowManagerGlobal.RELAYOUT_RES_CANCEL_AND_REDRAW;
import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
import static android.view.WindowManagerPolicyConstants.NAV_BAR_INVALID;
import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
@@ -238,10 +240,10 @@ import android.view.DisplayInfo;
import android.view.Gravity;
import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.ICrossWindowBlurEnabledListener;
+import android.view.IDisplayChangeWindowController;
import android.view.IDisplayFoldListener;
import android.view.IDisplayWindowInsetsController;
import android.view.IDisplayWindowListener;
-import android.view.IDisplayWindowRotationController;
import android.view.IInputFilter;
import android.view.IOnKeyguardExitResult;
import android.view.IPinnedTaskListener;
@@ -287,6 +289,7 @@ import android.view.displayhash.VerifiedDisplayHash;
import android.window.ClientWindowFrames;
import android.window.ITaskFpsCallback;
import android.window.TaskSnapshot;
+import android.window.WindowContainerToken;
import com.android.internal.R;
import com.android.internal.annotations.VisibleForTesting;
@@ -397,9 +400,8 @@ public class WindowManagerService extends IWindowManager.Stub
private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
- // Used to indicate that if there is already a transition set, it should be preserved when
- // trying to apply a new one.
- private static final boolean ALWAYS_KEEP_CURRENT = true;
+ static final int MY_PID = myPid();
+ static final int MY_UID = myUid();
static final int LOGTAG_INPUT_FOCUS = 62001;
@@ -460,6 +462,7 @@ public class WindowManagerService extends IWindowManager.Stub
final WindowManagerConstants mConstants;
final WindowTracing mWindowTracing;
+ final TransitionTracer mTransitionTracer;
private final DisplayAreaPolicy.Provider mDisplayAreaPolicyProvider;
@@ -682,9 +685,9 @@ public class WindowManagerService extends IWindowManager.Stub
final WallpaperVisibilityListeners mWallpaperVisibilityListeners =
new WallpaperVisibilityListeners();
- IDisplayWindowRotationController mDisplayRotationController = null;
- private final DeathRecipient mDisplayRotationControllerDeath =
- () -> mDisplayRotationController = null;
+ IDisplayChangeWindowController mDisplayChangeController = null;
+ private final DeathRecipient mDisplayChangeControllerDeath =
+ () -> mDisplayChangeController = null;
final DisplayWindowListenerController mDisplayNotificationController;
final TaskSystemBarsListenerController mTaskSystemBarsListenerController;
@@ -1017,7 +1020,7 @@ public class WindowManagerService extends IWindowManager.Stub
private int mExitAnimId, mEnterAnimId;
/** The display that the rotation animation is applying to. */
- private int mFrozenDisplayId;
+ private int mFrozenDisplayId = INVALID_DISPLAY;
/** Skip repeated ActivityRecords initialization. Note that AppWindowsToken's version of this
* is a long initialized to Long.MIN_VALUE so that it doesn't match this value on startup. */
@@ -1067,7 +1070,6 @@ public class WindowManagerService extends IWindowManager.Stub
Function<SurfaceSession, SurfaceControl.Builder> mSurfaceControlFactory;
Supplier<SurfaceControl.Transaction> mTransactionFactory;
- final Supplier<Surface> mSurfaceFactory;
private final SurfaceControl.Transaction mTransaction;
@@ -1150,7 +1152,7 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
ActivityTaskManagerService atm) {
return main(context, im, showBootMsgs, onlyCore, policy, atm,
- new DisplayWindowSettingsProvider(), SurfaceControl.Transaction::new, Surface::new,
+ new DisplayWindowSettingsProvider(), SurfaceControl.Transaction::new,
SurfaceControl.Builder::new);
}
@@ -1163,12 +1165,11 @@ public class WindowManagerService extends IWindowManager.Stub
final boolean showBootMsgs, final boolean onlyCore, WindowManagerPolicy policy,
ActivityTaskManagerService atm, DisplayWindowSettingsProvider
displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory,
- Supplier<Surface> surfaceFactory,
Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
final WindowManagerService[] wms = new WindowManagerService[1];
DisplayThread.getHandler().runWithScissors(() ->
wms[0] = new WindowManagerService(context, im, showBootMsgs, onlyCore, policy,
- atm, displayWindowSettingsProvider, transactionFactory, surfaceFactory,
+ atm, displayWindowSettingsProvider, transactionFactory,
surfaceControlFactory), 0);
return wms[0];
}
@@ -1178,7 +1179,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void run() {
WindowManagerPolicyThread.set(Thread.currentThread(), Looper.myLooper());
- mPolicy.init(mContext, WindowManagerService.this, WindowManagerService.this);
+ mPolicy.init(mContext, WindowManagerService.this);
}
}, 0);
}
@@ -1193,7 +1194,6 @@ public class WindowManagerService extends IWindowManager.Stub
boolean showBootMsgs, boolean onlyCore, WindowManagerPolicy policy,
ActivityTaskManagerService atm, DisplayWindowSettingsProvider
displayWindowSettingsProvider, Supplier<SurfaceControl.Transaction> transactionFactory,
- Supplier<Surface> surfaceFactory,
Function<SurfaceSession, SurfaceControl.Builder> surfaceControlFactory) {
installLock(this, INDEX_WINDOW);
mGlobalLock = atm.getGlobalLock();
@@ -1208,8 +1208,7 @@ public class WindowManagerService extends IWindowManager.Stub
com.android.internal.R.bool.config_hasPermanentDpad);
mInTouchMode = context.getResources().getBoolean(
com.android.internal.R.bool.config_defaultInTouchMode);
- inputManager.setInTouchMode(
- mInTouchMode, myPid(), myUid(), /* hasPermission = */ true);
+ inputManager.setInTouchMode(mInTouchMode, MY_PID, MY_UID, true /* hasPermission */);
mDrawLockTimeoutMillis = context.getResources().getInteger(
com.android.internal.R.integer.config_drawLockTimeoutMillis);
mAllowAnimationsInLowPowerMode = context.getResources().getBoolean(
@@ -1233,7 +1232,6 @@ public class WindowManagerService extends IWindowManager.Stub
mSurfaceControlFactory = surfaceControlFactory;
mTransactionFactory = transactionFactory;
- mSurfaceFactory = surfaceFactory;
mTransaction = mTransactionFactory.get();
mPolicy = policy;
@@ -1251,6 +1249,7 @@ public class WindowManagerService extends IWindowManager.Stub
mWindowTracing = WindowTracing.createDefaultAndStartLooper(this,
Choreographer.getInstance());
+ mTransitionTracer = new TransitionTracer();
LocalServices.addService(WindowManagerPolicy.class, mPolicy);
@@ -1449,7 +1448,7 @@ public class WindowManagerService extends IWindowManager.Stub
public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
int displayId, int requestUserId, InsetsVisibilities requestedVisibilities,
InputChannel outInputChannel, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
+ InsetsSourceControl[] outActiveControls, Rect outAttachedFrame) {
Arrays.fill(outActiveControls, null);
int[] appOp = new int[1];
final boolean isRoundedCornerOverlay = (attrs.privateFlags
@@ -1864,6 +1863,13 @@ public class WindowManagerService extends IWindowManager.Stub
outInsetsState.set(win.getCompatInsetsState(), win.isClientLocal());
getInsetsSourceControls(win, outActiveControls);
+
+ if (win.mLayoutAttached) {
+ outAttachedFrame.set(win.getParentWindow().getCompatFrame());
+ } else {
+ // Make this invalid which indicates a null attached frame.
+ outAttachedFrame.set(0, 0, -1, -1);
+ }
}
Binder.restoreCallingIdentity(origId);
@@ -2083,7 +2089,7 @@ public class WindowManagerService extends IWindowManager.Stub
if (win.mAttrs.type == TYPE_WALLPAPER) {
dc.mWallpaperController.clearLastWallpaperTimeoutTime();
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
- } else if (win.hasWallpaper()) {
+ } else if (dc.mWallpaperController.isWallpaperTarget(win)) {
dc.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
}
@@ -2216,6 +2222,20 @@ public class WindowManagerService extends IWindowManager.Stub
== PackageManager.PERMISSION_GRANTED;
}
+ /**
+ * Returns whether this window can proceed with drawing or needs to retry later.
+ */
+ public boolean cancelDraw(Session session, IWindow client) {
+ synchronized (mGlobalLock) {
+ final WindowState win = windowForClientLocked(session, client, false);
+ if (win == null) {
+ return false;
+ }
+
+ return win.cancelAndRedraw();
+ }
+ }
+
public int relayoutWindow(Session session, IWindow client, LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility, int flags,
ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
@@ -2232,6 +2252,11 @@ public class WindowManagerService extends IWindowManager.Stub
if (win == null) {
return 0;
}
+
+ if (win.cancelAndRedraw() && win.mPrepareSyncSeqId <= win.mLastSeqIdSentToRelayout) {
+ result |= RELAYOUT_RES_CANCEL_AND_REDRAW;
+ }
+
final DisplayContent displayContent = win.getDisplayContent();
final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
@@ -2258,9 +2283,21 @@ public class WindowManagerService extends IWindowManager.Stub
throw new IllegalArgumentException(
"Window type can not be changed after the window is added.");
}
- if (!Arrays.equals(win.mAttrs.providesInsetsTypes, attrs.providesInsetsTypes)) {
- throw new IllegalArgumentException(
- "Insets types can not be changed after the window is added.");
+ if (!(win.mAttrs.providedInsets == null && attrs.providedInsets == null)) {
+ if (win.mAttrs.providedInsets == null || attrs.providedInsets == null
+ || (win.mAttrs.providedInsets.length != attrs.providedInsets.length)) {
+ throw new IllegalArgumentException(
+ "Insets types can not be changed after the window is added.");
+ } else {
+ final int insetsTypes = attrs.providedInsets.length;
+ for (int i = 0; i < insetsTypes; i++) {
+ if (win.mAttrs.providedInsets[i].type != attrs.providedInsets[i].type) {
+ throw new IllegalArgumentException(
+ "Insets types can not be changed after the window is "
+ + "added.");
+ }
+ }
+ }
}
flagChanges = win.mAttrs.flags ^ attrs.flags;
@@ -2634,29 +2671,6 @@ public class WindowManagerService extends IWindowManager.Stub
return result;
}
- int updateViewVisibility(Session session, IWindow client, LayoutParams attrs,
- int viewVisibility, MergedConfiguration outMergedConfiguration,
- SurfaceControl outSurfaceControl, InsetsState outInsetsState,
- InsetsSourceControl[] outActiveControls) {
- // TODO(b/161810301): Finish the implementation.
- return 0;
- }
-
- void updateWindowLayout(Session session, IWindow client, LayoutParams attrs, int flags,
- ClientWindowFrames clientWindowFrames, int requestedWidth, int requestedHeight) {
- final long origId = Binder.clearCallingIdentity();
- synchronized (mGlobalLock) {
- final WindowState win = windowForClientLocked(session, client, false);
- if (win == null) {
- return;
- }
- win.setFrames(clientWindowFrames, requestedWidth, requestedHeight);
-
- // TODO(b/161810301): Finish the implementation.
- }
- Binder.restoreCallingIdentity(origId);
- }
-
public boolean outOfMemoryWindow(Session session, IWindow client) {
final long origId = Binder.clearCallingIdentity();
@@ -2704,7 +2718,7 @@ public class WindowManagerService extends IWindowManager.Stub
}
boolean checkCallingPermission(String permission, String func, boolean printLog) {
- if (Binder.getCallingPid() == myPid()) {
+ if (Binder.getCallingPid() == MY_PID) {
return true;
}
@@ -2864,7 +2878,7 @@ public class WindowManagerService extends IWindowManager.Stub
// registration in DisplayContent#onParentChanged at DisplayContent initialization.
final DisplayContent dc = mRoot.getDisplayContent(displayId);
if (dc == null) {
- if (Binder.getCallingPid() != myPid()) {
+ if (Binder.getCallingPid() != MY_PID) {
throw new WindowManager.InvalidDisplayException("attachToDisplayContent: "
+ "trying to attach to a non-existing display:" + displayId);
}
@@ -4260,12 +4274,13 @@ public class WindowManagerService extends IWindowManager.Stub
.notifyOnActivityRotation(displayContent.mDisplayId);
}
- final boolean pendingRemoteRotation = rotationChanged
- && (displayContent.getDisplayRotation().isWaitingForRemoteRotation()
+ final boolean pendingRemoteDisplayChange = rotationChanged
+ && (displayContent.mRemoteDisplayChangeController
+ .isWaitingForRemoteDisplayChange()
|| displayContent.mTransitionController.isCollecting());
// Even if alwaysSend, we are waiting for a transition or remote to provide
- // rotated configuration, so we can't update configuration yet.
- if (!pendingRemoteRotation) {
+ // updated configuration, so we can't update configuration yet.
+ if (!pendingRemoteDisplayChange) {
if (!rotationChanged || forceRelayout) {
displayContent.setLayoutNeeded();
layoutNeeded = true;
@@ -4297,17 +4312,17 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
- public void setDisplayWindowRotationController(IDisplayWindowRotationController controller) {
+ public void setDisplayChangeWindowController(IDisplayChangeWindowController controller) {
mAtmService.enforceTaskPermission("setDisplayWindowRotationController");
try {
synchronized (mGlobalLock) {
- if (mDisplayRotationController != null) {
- mDisplayRotationController.asBinder().unlinkToDeath(
- mDisplayRotationControllerDeath, 0);
- mDisplayRotationController = null;
+ if (mDisplayChangeController != null) {
+ mDisplayChangeController.asBinder().unlinkToDeath(
+ mDisplayChangeControllerDeath, 0);
+ mDisplayChangeController = null;
}
- controller.asBinder().linkToDeath(mDisplayRotationControllerDeath, 0);
- mDisplayRotationController = controller;
+ controller.asBinder().linkToDeath(mDisplayChangeControllerDeath, 0);
+ mDisplayChangeController = controller;
}
} catch (RemoteException e) {
throw new RuntimeException("Unable to set rotation controller");
@@ -5694,6 +5709,25 @@ public class WindowManagerService extends IWindowManager.Stub
}
}
+ void setSandboxDisplayApis(int displayId, boolean sandboxDisplayApis) {
+ if (mContext.checkCallingOrSelfPermission(WRITE_SECURE_SETTINGS)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Must hold permission " + WRITE_SECURE_SETTINGS);
+ }
+
+ final long ident = Binder.clearCallingIdentity();
+ try {
+ synchronized (mGlobalLock) {
+ final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+ if (displayContent != null) {
+ displayContent.setSandboxDisplayApis(sandboxDisplayApis);
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(ident);
+ }
+ }
+
/** The global settings only apply to default display. */
private boolean applyForcedPropertiesForDefaultDisplay() {
boolean changed = false;
@@ -5874,6 +5908,21 @@ public class WindowManagerService extends IWindowManager.Stub
}
@Override
+ public void startTransitionTrace() {
+ mTransitionTracer.startTrace(null /* printwriter */);
+ }
+
+ @Override
+ public void stopTransitionTrace() {
+ mTransitionTracer.stopTrace(null /* printwriter */);
+ }
+
+ @Override
+ public boolean isTransitionTraceEnabled() {
+ return mTransitionTracer.isEnabled();
+ }
+
+ @Override
public boolean registerCrossWindowBlurEnabledListener(
ICrossWindowBlurEnabledListener listener) {
return mBlurController.registerCrossWindowBlurEnabledListener(listener);
@@ -5919,10 +5968,10 @@ public class WindowManagerService extends IWindowManager.Stub
}
void makeWindowFreezingScreenIfNeededLocked(WindowState w) {
- // If the screen is currently frozen or off, then keep
- // it frozen/off until this window draws at its new
- // orientation.
- if (!w.mToken.okToDisplay() && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
+ // If the screen is currently frozen, then keep it frozen until this window draws at its
+ // new orientation.
+ if (mFrozenDisplayId != INVALID_DISPLAY && mFrozenDisplayId == w.getDisplayId()
+ && mWindowsFreezingScreen != WINDOWS_FREEZING_SCREENS_TIMEOUT) {
ProtoLog.v(WM_DEBUG_ORIENTATION, "Changing surface while display frozen: %s", w);
w.setOrientationChanging(true);
if (mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_NONE) {
@@ -5998,9 +6047,7 @@ public class WindowManagerService extends IWindowManager.Stub
/** Note that Locked in this case is on mLayoutToAnim */
void scheduleAnimationLocked() {
- if (mAnimator != null) {
- mAnimator.scheduleAnimation();
- }
+ mAnimator.scheduleAnimation();
}
boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
@@ -6087,24 +6134,24 @@ public class WindowManagerService extends IWindowManager.Stub
final DisplayContent displayContent = mRoot.getDisplayContent(mFrozenDisplayId);
final int numOpeningApps;
final boolean waitingForConfig;
- final boolean waitingForRemoteRotation;
+ final boolean waitingForRemoteDisplayChange;
if (displayContent != null) {
numOpeningApps = displayContent.mOpeningApps.size();
waitingForConfig = displayContent.mWaitingForConfig;
- waitingForRemoteRotation =
- displayContent.getDisplayRotation().isWaitingForRemoteRotation();
+ waitingForRemoteDisplayChange = displayContent.mRemoteDisplayChangeController
+ .isWaitingForRemoteDisplayChange();
} else {
- waitingForConfig = waitingForRemoteRotation = false;
+ waitingForConfig = waitingForRemoteDisplayChange = false;
numOpeningApps = 0;
}
- if (waitingForConfig || waitingForRemoteRotation || mAppsFreezingScreen > 0
+ if (waitingForConfig || waitingForRemoteDisplayChange || mAppsFreezingScreen > 0
|| mWindowsFreezingScreen == WINDOWS_FREEZING_SCREENS_ACTIVE
|| mClientFreezingScreen || numOpeningApps > 0) {
ProtoLog.d(WM_DEBUG_ORIENTATION, "stopFreezingDisplayLocked: Returning "
- + "waitingForConfig=%b, waitingForRemoteRotation=%b, "
+ + "waitingForConfig=%b, waitingForRemoteDisplayChange=%b, "
+ "mAppsFreezingScreen=%d, mWindowsFreezingScreen=%d, "
+ "mClientFreezingScreen=%b, mOpeningApps.size()=%d",
- waitingForConfig, waitingForRemoteRotation,
+ waitingForConfig, waitingForRemoteDisplayChange,
mAppsFreezingScreen, mWindowsFreezingScreen,
mClientFreezingScreen, numOpeningApps);
return;
@@ -8236,6 +8283,26 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public void setContentRecordingSession(@Nullable ContentRecordingSession incomingSession) {
synchronized (mGlobalLock) {
+ // Allow the controller to handle teardown or a non-task session.
+ if (incomingSession == null
+ || incomingSession.getContentToRecord() != RECORD_CONTENT_TASK) {
+ mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
+ WindowManagerService.this);
+ return;
+ }
+ // For a task session, find the activity identified by the launch cookie.
+ final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+ incomingSession.getTokenToRecord());
+ if (wct == null) {
+ Slog.w(TAG, "Handling a new recording session; unable to find the "
+ + "WindowContainerToken");
+ mContentRecordingController.setContentRecordingSessionLocked(null,
+ WindowManagerService.this);
+ return;
+ }
+ // Replace the launch cookie in the session details with the task's
+ // WindowContainerToken.
+ incomingSession.setTokenToRecord(wct.asBinder());
mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
WindowManagerService.this);
}
@@ -8494,6 +8561,38 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
+ * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
+ * with the given launch cookie.
+ *
+ * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
+ * activity
+ * @return a token representing the task containing the activity started with the given launch
+ * cookie, or {@code null} if the token couldn't be found.
+ */
+ @VisibleForTesting
+ @Nullable
+ WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+ // Find the activity identified by the launch cookie.
+ final ActivityRecord targetActivity = mRoot.getActivity(
+ activity -> activity.mLaunchCookie == launchCookie);
+ if (targetActivity == null) {
+ Slog.w(TAG, "Unable to find the activity for this launch cookie");
+ return null;
+ }
+ if (targetActivity.getTask() == null) {
+ Slog.w(TAG, "Unable to find the task for this launch cookie");
+ return null;
+ }
+ WindowContainerToken taskWindowContainerToken =
+ targetActivity.getTask().mRemoteToken.toWindowContainerToken();
+ if (taskWindowContainerToken == null) {
+ Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
+ return null;
+ }
+ return taskWindowContainerToken;
+ }
+
+ /**
* You need ALLOW_SLIPPERY_TOUCHES permission to be able to set FLAG_SLIPPERY.
*/
private int sanitizeFlagSlippery(int flags, String windowName, int callingUid, int callingPid) {
@@ -8753,7 +8852,7 @@ public class WindowManagerService extends IWindowManager.Stub
@Override
public boolean getWindowInsets(WindowManager.LayoutParams attrs, int displayId,
InsetsState outInsetsState) {
- final boolean fromLocal = Binder.getCallingPid() == myPid();
+ final boolean fromLocal = Binder.getCallingPid() == MY_PID;
final int uid = Binder.getCallingUid();
final long origId = Binder.clearCallingIdentity();
try {
diff --git a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
index 34c93482ecfe..ff43a96d6afc 100644
--- a/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
+++ b/services/core/java/com/android/server/wm/WindowManagerShellCommand.java
@@ -19,6 +19,19 @@ package com.android.server.wm;
import static android.os.Build.IS_USER;
import static android.view.CrossWindowBlurListeners.CROSS_WINDOW_BLUR_SUPPORTED;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+
+import android.content.res.Resources.NotFoundException;
+import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.ParcelFileDescriptor;
@@ -36,6 +49,9 @@ import com.android.internal.os.ByteTransferPipe;
import com.android.internal.protolog.ProtoLogImpl;
import com.android.server.LocalServices;
import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.LetterboxConfiguration.LetterboxBackgroundType;
+import com.android.server.wm.LetterboxConfiguration.LetterboxHorizontalReachabilityPosition;
+import com.android.server.wm.LetterboxConfiguration.LetterboxVerticalReachabilityPosition;
import java.io.IOException;
import java.io.PrintWriter;
@@ -58,10 +74,12 @@ public class WindowManagerShellCommand extends ShellCommand {
// Internal service impl -- must perform security checks before touching.
private final WindowManagerService mInternal;
+ private final LetterboxConfiguration mLetterboxConfiguration;
public WindowManagerShellCommand(WindowManagerService service) {
mInterface = service;
mInternal = service;
+ mLetterboxConfiguration = service.mLetterboxConfiguration;
}
@Override
@@ -113,6 +131,14 @@ public class WindowManagerShellCommand extends ShellCommand {
return runGetIgnoreOrientationRequest(pw);
case "dump-visible-window-views":
return runDumpVisibleWindowViews(pw);
+ case "set-letterbox-style":
+ return runSetLetterboxStyle(pw);
+ case "get-letterbox-style":
+ return runGetLetterboxStyle(pw);
+ case "reset-letterbox-style":
+ return runResetLetterboxStyle(pw);
+ case "set-sandbox-display-apis":
+ return runSandboxDisplayApis(pw);
case "set-multi-window-config":
return runSetMultiWindowConfig();
case "get-multi-window-config":
@@ -123,6 +149,8 @@ public class WindowManagerShellCommand extends ShellCommand {
return runReset(pw);
case "disable-blur":
return runSetBlurDisabled(pw);
+ case "shell":
+ return runWmShellCommand(pw);
default:
return handleDefaultCommands(cmd);
}
@@ -331,6 +359,37 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ /**
+ * Override display size and metrics to reflect the DisplayArea of the calling activity.
+ */
+ private int runSandboxDisplayApis(PrintWriter pw) throws RemoteException {
+ int displayId = Display.DEFAULT_DISPLAY;
+ String arg = getNextArgRequired();
+ if ("-d".equals(arg)) {
+ displayId = Integer.parseInt(getNextArgRequired());
+ arg = getNextArgRequired();
+ }
+
+ final boolean sandboxDisplayApis;
+ switch (arg) {
+ case "true":
+ case "1":
+ sandboxDisplayApis = true;
+ break;
+ case "false":
+ case "0":
+ sandboxDisplayApis = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expecting true, 1, false, 0, but we "
+ + "get " + arg);
+ return -1;
+ }
+
+ mInternal.setSandboxDisplayApis(displayId, sandboxDisplayApis);
+ return 0;
+ }
+
private int runDismissKeyguard(PrintWriter pw) throws RemoteException {
mInterface.dismissKeyguard(null /* callback */, null /* message */);
return 0;
@@ -553,6 +612,497 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private int runSetFixedOrientationLetterboxAspectRatio(PrintWriter pw) throws RemoteException {
+ final float aspectRatio;
+ try {
+ String arg = getNextArgRequired();
+ aspectRatio = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: aspect ratio should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(aspectRatio);
+ }
+ return 0;
+ }
+
+ private int runSetDefaultMinAspectRatioForUnresizableApps(PrintWriter pw)
+ throws RemoteException {
+ final float aspectRatio;
+ try {
+ String arg = getNextArgRequired();
+ aspectRatio = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad aspect ratio format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: aspect ratio should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps(aspectRatio);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxActivityCornersRadius(PrintWriter pw) throws RemoteException {
+ final int cornersRadius;
+ try {
+ String arg = getNextArgRequired();
+ cornersRadius = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad corners radius format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: corners radius should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxActivityCornersRadius(cornersRadius);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundType(PrintWriter pw) throws RemoteException {
+ @LetterboxBackgroundType final int backgroundType;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "solid_color":
+ backgroundType = LETTERBOX_BACKGROUND_SOLID_COLOR;
+ break;
+ case "app_color_background":
+ backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND;
+ break;
+ case "app_color_background_floating":
+ backgroundType = LETTERBOX_BACKGROUND_APP_COLOR_BACKGROUND_FLOATING;
+ break;
+ case "wallpaper":
+ backgroundType = LETTERBOX_BACKGROUND_WALLPAPER;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'solid_color', 'app_color_background' or "
+ + "'wallpaper' should be provided as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'solid_color', 'app_color_background' or "
+ + "'wallpaper' should be provided as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundType(backgroundType);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundColorResource(PrintWriter pw) throws RemoteException {
+ final int colorId;
+ try {
+ String arg = getNextArgRequired();
+ colorId = mInternal.mContext.getResources()
+ .getIdentifier(arg, "color", "com.android.internal");
+ } catch (NotFoundException e) {
+ getErrPrintWriter().println(
+ "Error: color in '@android:color/resource_name' format should be provided as "
+ + "an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundColorResourceId(colorId);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundColor(PrintWriter pw) throws RemoteException {
+ final Color color;
+ try {
+ String arg = getNextArgRequired();
+ color = Color.valueOf(Color.parseColor(arg));
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: color in #RRGGBB format should be provided as "
+ + "an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundColor(color);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundWallpaperBlurRadius(PrintWriter pw)
+ throws RemoteException {
+ final int radius;
+ try {
+ String arg = getNextArgRequired();
+ radius = Integer.parseInt(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: blur radius format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: blur radius should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundWallpaperBlurRadius(radius);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxBackgroundWallpaperDarkScrimAlpha(PrintWriter pw)
+ throws RemoteException {
+ final float alpha;
+ try {
+ String arg = getNextArgRequired();
+ alpha = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad alpha format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: alpha should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxBackgroundWallpaperDarkScrimAlpha(alpha);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxHorizontalPositionMultiplier(PrintWriter pw) throws RemoteException {
+ final float multiplier;
+ try {
+ String arg = getNextArgRequired();
+ multiplier = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad multiplier format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: multiplier should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(multiplier);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxVerticalPositionMultiplier(PrintWriter pw) throws RemoteException {
+ final float multiplier;
+ try {
+ String arg = getNextArgRequired();
+ multiplier = Float.parseFloat(arg);
+ } catch (NumberFormatException e) {
+ getErrPrintWriter().println("Error: bad multiplier format " + e);
+ return -1;
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: multiplier should be provided as an argument " + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(multiplier);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxIsHorizontalReachabilityEnabled(PrintWriter pw)
+ throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(enabled);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxIsVerticalReachabilityEnabled(PrintWriter pw)
+ throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsVerticalReachabilityEnabled(enabled);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxDefaultPositionForHorizontalReachability(PrintWriter pw)
+ throws RemoteException {
+ @LetterboxHorizontalReachabilityPosition final int position;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "left":
+ position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
+ break;
+ case "center":
+ position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
+ break;
+ case "right":
+ position = LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'left', 'center' or 'right' are expected as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setDefaultPositionForHorizontalReachability(position);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxDefaultPositionForVerticalReachability(PrintWriter pw)
+ throws RemoteException {
+ @LetterboxVerticalReachabilityPosition final int position;
+ try {
+ String arg = getNextArgRequired();
+ switch (arg) {
+ case "top":
+ position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
+ break;
+ case "center":
+ position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
+ break;
+ case "bottom":
+ position = LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: 'top', 'center' or 'bottom' are expected as an argument");
+ return -1;
+ }
+ } catch (IllegalArgumentException e) {
+ getErrPrintWriter().println(
+ "Error: 'top', 'center' or 'bottom' are expected as an argument" + e);
+ return -1;
+ }
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setDefaultPositionForVerticalReachability(position);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxIsEducationEnabled(PrintWriter pw) throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsEducationEnabled(enabled);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(PrintWriter pw)
+ throws RemoteException {
+ String arg = getNextArg();
+ final boolean enabled;
+ switch (arg) {
+ case "true":
+ case "1":
+ enabled = true;
+ break;
+ case "false":
+ case "0":
+ enabled = false;
+ break;
+ default:
+ getErrPrintWriter().println("Error: expected true, 1, false, 0, but got " + arg);
+ return -1;
+ }
+
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.setIsSplitScreenAspectRatioForUnresizableAppsEnabled(enabled);
+ }
+ return 0;
+ }
+
+ private int runSetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ if (peekNextArg() == null) {
+ getErrPrintWriter().println("Error: No arguments provided.");
+ }
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "--aspectRatio":
+ runSetFixedOrientationLetterboxAspectRatio(pw);
+ break;
+ case "--minAspectRatioForUnresizable":
+ runSetDefaultMinAspectRatioForUnresizableApps(pw);
+ break;
+ case "--cornerRadius":
+ runSetLetterboxActivityCornersRadius(pw);
+ break;
+ case "--backgroundType":
+ runSetLetterboxBackgroundType(pw);
+ break;
+ case "--backgroundColor":
+ runSetLetterboxBackgroundColor(pw);
+ break;
+ case "--backgroundColorResource":
+ runSetLetterboxBackgroundColorResource(pw);
+ break;
+ case "--wallpaperBlurRadius":
+ runSetLetterboxBackgroundWallpaperBlurRadius(pw);
+ break;
+ case "--wallpaperDarkScrimAlpha":
+ runSetLetterboxBackgroundWallpaperDarkScrimAlpha(pw);
+ break;
+ case "--horizontalPositionMultiplier":
+ runSetLetterboxHorizontalPositionMultiplier(pw);
+ break;
+ case "--verticalPositionMultiplier":
+ runSetLetterboxVerticalPositionMultiplier(pw);
+ break;
+ case "--isHorizontalReachabilityEnabled":
+ runSetLetterboxIsHorizontalReachabilityEnabled(pw);
+ break;
+ case "--isVerticalReachabilityEnabled":
+ runSetLetterboxIsVerticalReachabilityEnabled(pw);
+ break;
+ case "--defaultPositionForHorizontalReachability":
+ runSetLetterboxDefaultPositionForHorizontalReachability(pw);
+ break;
+ case "--defaultPositionForVerticalReachability":
+ runSetLetterboxDefaultPositionForVerticalReachability(pw);
+ break;
+ case "--isEducationEnabled":
+ runSetLetterboxIsEducationEnabled(pw);
+ break;
+ case "--isSplitScreenAspectRatioForUnresizableAppsEnabled":
+ runSetLetterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled(pw);
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized letterbox style option: " + arg);
+ return -1;
+ }
+ }
+ return 0;
+ }
+
+ private int runResetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ if (peekNextArg() == null) {
+ resetLetterboxStyle();
+ }
+ synchronized (mInternal.mGlobalLock) {
+ while (peekNextArg() != null) {
+ String arg = getNextArg();
+ switch (arg) {
+ case "aspectRatio":
+ mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+ break;
+ case "minAspectRatioForUnresizable":
+ mLetterboxConfiguration.resetDefaultMinAspectRatioForUnresizableApps();
+ break;
+ case "cornerRadius":
+ mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+ break;
+ case "backgroundType":
+ mLetterboxConfiguration.resetLetterboxBackgroundType();
+ break;
+ case "backgroundColor":
+ mLetterboxConfiguration.resetLetterboxBackgroundColor();
+ break;
+ case "wallpaperBlurRadius":
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ break;
+ case "wallpaperDarkScrimAlpha":
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+ break;
+ case "horizontalPositionMultiplier":
+ mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ break;
+ case "verticalPositionMultiplier":
+ mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
+ break;
+ case "isHorizontalReachabilityEnabled":
+ mLetterboxConfiguration.getIsHorizontalReachabilityEnabled();
+ break;
+ case "isVerticalReachabilityEnabled":
+ mLetterboxConfiguration.getIsVerticalReachabilityEnabled();
+ break;
+ case "defaultPositionForHorizontalReachability":
+ mLetterboxConfiguration.getDefaultPositionForHorizontalReachability();
+ break;
+ case "defaultPositionForVerticalReachability":
+ mLetterboxConfiguration.getDefaultPositionForVerticalReachability();
+ break;
+ case "isEducationEnabled":
+ mLetterboxConfiguration.getIsEducationEnabled();
+ break;
+ case "isSplitScreenAspectRatioForUnresizableAppsEnabled":
+ mLetterboxConfiguration
+ .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ break;
+ default:
+ getErrPrintWriter().println(
+ "Error: Unrecognized letterbox style option: " + arg);
+ return -1;
+ }
+ }
+ }
+ return 0;
+ }
+
private int runSetMultiWindowConfig() {
if (peekNextArg() == null) {
getErrPrintWriter().println("Error: No arguments provided.");
@@ -627,6 +1177,107 @@ public class WindowManagerShellCommand extends ShellCommand {
return 0;
}
+ private void resetLetterboxStyle() {
+ synchronized (mInternal.mGlobalLock) {
+ mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
+ mLetterboxConfiguration.resetDefaultMinAspectRatioForUnresizableApps();
+ mLetterboxConfiguration.resetLetterboxActivityCornersRadius();
+ mLetterboxConfiguration.resetLetterboxBackgroundType();
+ mLetterboxConfiguration.resetLetterboxBackgroundColor();
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperBlurRadius();
+ mLetterboxConfiguration.resetLetterboxBackgroundWallpaperDarkScrimAlpha();
+ mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
+ mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
+ mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
+ mLetterboxConfiguration.resetDefaultPositionForHorizontalReachability();
+ mLetterboxConfiguration.resetDefaultPositionForVerticalReachability();
+ mLetterboxConfiguration.resetIsEducationEnabled();
+ mLetterboxConfiguration.resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+ }
+ }
+
+ private int runGetLetterboxStyle(PrintWriter pw) throws RemoteException {
+ synchronized (mInternal.mGlobalLock) {
+ pw.println("Corner radius: "
+ + mLetterboxConfiguration.getLetterboxActivityCornersRadius());
+ pw.println("Horizontal position multiplier: "
+ + mLetterboxConfiguration.getLetterboxHorizontalPositionMultiplier());
+ pw.println("Vertical position multiplier: "
+ + mLetterboxConfiguration.getLetterboxVerticalPositionMultiplier());
+ pw.println("Aspect ratio: "
+ + mLetterboxConfiguration.getFixedOrientationLetterboxAspectRatio());
+ pw.println("Default min aspect ratio for unresizable apps: "
+ + mLetterboxConfiguration.getDefaultMinAspectRatioForUnresizableApps());
+ pw.println("Is horizontal reachability enabled: "
+ + mLetterboxConfiguration.getIsHorizontalReachabilityEnabled());
+ pw.println("Is vertical reachability enabled: "
+ + mLetterboxConfiguration.getIsVerticalReachabilityEnabled());
+ pw.println("Default position for horizontal reachability: "
+ + LetterboxConfiguration.letterboxHorizontalReachabilityPositionToString(
+ mLetterboxConfiguration.getDefaultPositionForHorizontalReachability()));
+ pw.println("Default position for vertical reachability: "
+ + LetterboxConfiguration.letterboxVerticalReachabilityPositionToString(
+ mLetterboxConfiguration.getDefaultPositionForVerticalReachability()));
+ pw.println("Is education enabled: "
+ + mLetterboxConfiguration.getIsEducationEnabled());
+ pw.println("Is using split screen aspect ratio as aspect ratio for unresizable apps: "
+ + mLetterboxConfiguration
+ .getIsSplitScreenAspectRatioForUnresizableAppsEnabled());
+
+ pw.println("Background type: "
+ + LetterboxConfiguration.letterboxBackgroundTypeToString(
+ mLetterboxConfiguration.getLetterboxBackgroundType()));
+ pw.println(" Background color: " + Integer.toHexString(
+ mLetterboxConfiguration.getLetterboxBackgroundColor().toArgb()));
+ pw.println(" Wallpaper blur radius: "
+ + mLetterboxConfiguration.getLetterboxBackgroundWallpaperBlurRadius());
+ pw.println(" Wallpaper dark scrim alpha: "
+ + mLetterboxConfiguration.getLetterboxBackgroundWallpaperDarkScrimAlpha());
+ }
+ return 0;
+ }
+
+ private int runWmShellCommand(PrintWriter pw) {
+ String arg = getNextArg();
+
+ switch (arg) {
+ case "tracing":
+ return runWmShellTracing(pw);
+ case "help":
+ default:
+ return runHelp(pw);
+ }
+ }
+
+ private int runHelp(PrintWriter pw) {
+ pw.println("Window Manager Shell commands:");
+ pw.println(" help");
+ pw.println(" Print this help text.");
+ pw.println(" tracing <start/stop>");
+ pw.println(" Start/stop shell transition tracing.");
+
+ return 0;
+ }
+
+ private int runWmShellTracing(PrintWriter pw) {
+ String arg = getNextArg();
+
+ switch (arg) {
+ case "start":
+ mInternal.mTransitionTracer.startTrace(pw);
+ break;
+ case "stop":
+ mInternal.mTransitionTracer.stopTrace(pw);
+ break;
+ default:
+ getErrPrintWriter()
+ .println("Error: expected 'start' or 'stop', but got '" + arg + "'");
+ return -1;
+ }
+
+ return 0;
+ }
+
private int runReset(PrintWriter pw) throws RemoteException {
int displayId = getDisplayId(getNextArg());
@@ -651,6 +1302,12 @@ public class WindowManagerShellCommand extends ShellCommand {
// set-ignore-orientation-request
mInterface.setIgnoreOrientationRequest(displayId, false /* ignoreOrientationRequest */);
+ // set-letterbox-style
+ resetLetterboxStyle();
+
+ // set-sandbox-display-apis
+ mInternal.setSandboxDisplayApis(displayId, /* sandboxDisplayApis= */ true);
+
// set-multi-window-config
runResetMultiWindowConfig();
@@ -685,7 +1342,12 @@ public class WindowManagerShellCommand extends ShellCommand {
pw.println(" set-ignore-orientation-request [-d DISPLAY_ID] [true|1|false|0]");
pw.println(" get-ignore-orientation-request [-d DISPLAY_ID] ");
pw.println(" If app requested orientation should be ignored.");
+ pw.println(" set-sandbox-display-apis [true|1|false|0]");
+ pw.println(" Sets override of Display APIs getRealSize / getRealMetrics to reflect ");
+ pw.println(" DisplayArea of the activity, or the window bounds if in letterbox or");
+ pw.println(" Size Compat Mode.");
+ printLetterboxHelp(pw);
printMultiWindowConfigHelp(pw);
pw.println(" reset [-d DISPLAY_ID]");
@@ -698,6 +1360,84 @@ public class WindowManagerShellCommand extends ShellCommand {
}
}
+ private void printLetterboxHelp(PrintWriter pw) {
+ pw.println(" set-letterbox-style");
+ pw.println(" Sets letterbox style using the following options:");
+ pw.println(" --aspectRatio aspectRatio");
+ pw.println(" Aspect ratio of letterbox for fixed orientation. If aspectRatio <= "
+ + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
+ pw.println(" be ignored and framework implementation will determine aspect ratio.");
+ pw.println(" --minAspectRatioForUnresizable aspectRatio");
+ pw.println(" Default min aspect ratio for unresizable apps which is used when an");
+ pw.println(" app is eligible for the size compat mode. If aspectRatio <= "
+ + LetterboxConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+ pw.println(" both it and R.dimen.config_fixedOrientationLetterboxAspectRatio will");
+ pw.println(" be ignored and framework implementation will determine aspect ratio.");
+ pw.println(" --cornerRadius radius");
+ pw.println(" Corners radius for activities in the letterbox mode. If radius < 0,");
+ pw.println(" both it and R.integer.config_letterboxActivityCornersRadius will be");
+ pw.println(" ignored and corners of the activity won't be rounded.");
+ pw.println(" --backgroundType [reset|solid_color|app_color_background");
+ pw.println(" |app_color_background_floating|wallpaper]");
+ pw.println(" Type of background used in the letterbox mode.");
+ pw.println(" --backgroundColor color");
+ pw.println(" Color of letterbox which is be used when letterbox background type");
+ pw.println(" is 'solid-color'. Use (set)get-letterbox-style to check and control");
+ pw.println(" letterbox background type. See Color#parseColor for allowed color");
+ pw.println(" formats (#RRGGBB and some colors by name, e.g. magenta or olive).");
+ pw.println(" --backgroundColorResource resource_name");
+ pw.println(" Color resource name of letterbox background which is used when");
+ pw.println(" background type is 'solid-color'. Use (set)get-letterbox-style to");
+ pw.println(" check and control background type. Parameter is a color resource");
+ pw.println(" name, for example, @android:color/system_accent2_50.");
+ pw.println(" --wallpaperBlurRadius radius");
+ pw.println(" Blur radius for 'wallpaper' letterbox background. If radius <= 0");
+ pw.println(" both it and R.dimen.config_letterboxBackgroundWallpaperBlurRadius");
+ pw.println(" are ignored and 0 is used.");
+ pw.println(" --wallpaperDarkScrimAlpha alpha");
+ pw.println(" Alpha of a black translucent scrim shown over 'wallpaper'");
+ pw.println(" letterbox background. If alpha < 0 or >= 1 both it and");
+ pw.println(" R.dimen.config_letterboxBackgroundWallaperDarkScrimAlpha are ignored");
+ pw.println(" and 0.0 (transparent) is used instead.");
+ pw.println(" --horizontalPositionMultiplier multiplier");
+ pw.println(" Horizontal position of app window center. If multiplier < 0 or > 1,");
+ pw.println(" both it and R.dimen.config_letterboxHorizontalPositionMultiplier");
+ pw.println(" are ignored and central position (0.5) is used.");
+ pw.println(" --verticalPositionMultiplier multiplier");
+ pw.println(" Vertical position of app window center. If multiplier < 0 or > 1,");
+ pw.println(" both it and R.dimen.config_letterboxVerticalPositionMultiplier");
+ pw.println(" are ignored and central position (0.5) is used.");
+ pw.println(" --isHorizontalReachabilityEnabled [true|1|false|0]");
+ pw.println(" Whether horizontal reachability repositioning is allowed for ");
+ pw.println(" letterboxed fullscreen apps in landscape device orientation.");
+ pw.println(" --isVerticalReachabilityEnabled [true|1|false|0]");
+ pw.println(" Whether vertical reachability repositioning is allowed for ");
+ pw.println(" letterboxed fullscreen apps in portrait device orientation.");
+ pw.println(" --defaultPositionForHorizontalReachability [left|center|right]");
+ pw.println(" Default position of app window when horizontal reachability is.");
+ pw.println(" enabled.");
+ pw.println(" --defaultPositionForVerticalReachability [top|center|bottom]");
+ pw.println(" Default position of app window when vertical reachability is.");
+ pw.println(" enabled.");
+ pw.println(" --isEducationEnabled [true|1|false|0]");
+ pw.println(" Whether education is allowed for letterboxed fullscreen apps.");
+ pw.println(" --isSplitScreenAspectRatioForUnresizableAppsEnabled [true|1|false|0]");
+ pw.println(" Whether using split screen aspect ratio as a default aspect ratio for");
+ pw.println(" unresizable apps.");
+ pw.println(" reset-letterbox-style [aspectRatio|cornerRadius|backgroundType");
+ pw.println(" |backgroundColor|wallpaperBlurRadius|wallpaperDarkScrimAlpha");
+ pw.println(" |horizontalPositionMultiplier|verticalPositionMultiplier");
+ pw.println(" |isHorizontalReachabilityEnabled|isVerticalReachabilityEnabled");
+ pw.println(" isEducationEnabled||defaultPositionMultiplierForHorizontalReachability");
+ pw.println(" ||defaultPositionMultiplierForVerticalReachability]");
+ pw.println(" Resets overrides to default values for specified properties separated");
+ pw.println(" by space, e.g. 'reset-letterbox-style aspectRatio cornerRadius'.");
+ pw.println(" If no arguments provided, all values will be reset.");
+ pw.println(" get-letterbox-style");
+ pw.println(" Prints letterbox style configuration.");
+ }
+
private void printMultiWindowConfigHelp(PrintWriter pw) {
pw.println(" set-multi-window-config");
pw.println(" Sets options to determine if activity should be shown in multi window:");
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index aee66faa8faa..0f5655c74f31 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -18,8 +18,10 @@ package com.android.server.wm;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.app.ActivityManager.isStartResultSuccessful;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOW_CONFIG_BOUNDS;
import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_RECT_INSETS_PROVIDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
@@ -43,6 +45,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
import static com.android.server.wm.ActivityTaskManagerService.LAYOUT_REASON_CONFIG_CHANGED;
import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -148,7 +151,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
void setWindowManager(WindowManagerService wms) {
- mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController);
+ mTransitionController = new TransitionController(mService, wms.mTaskSnapshotController,
+ wms.mTransitionTracer);
mTransitionController.registerLegacyListener(wms.mActivityManagerAppTransitionNotifier);
}
@@ -326,7 +330,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
adapter.setCallingPidUid(caller.mPid, caller.mUid);
dc.prepareAppTransition(type);
- dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */);
+ dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */,
+ false /* isActivityEmbedding */);
syncId = startSyncWithOrganizer(callback);
applyTransaction(t, syncId, null /* transition */, caller);
setSyncReady(syncId);
@@ -393,11 +398,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
// Go through all tasks and collect them before the rotation
// TODO(shell-transitions): move collect() to onConfigurationChange once
// wallpaper handling is synchronized.
- dc.mTransitionController.collectForDisplayChange(dc, transition);
+ dc.mTransitionController.collectForDisplayAreaChange(dc, transition);
dc.sendNewConfiguration();
effects |= TRANSACT_EFFECTS_LIFECYCLE;
}
}
+ final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
+ final int hopSize = hops.size();
ArraySet<WindowContainer> haveConfigChanges = new ArraySet<>();
Iterator<Map.Entry<IBinder, WindowContainerTransaction.Change>> entries =
t.getChanges().entrySet().iterator();
@@ -415,19 +422,53 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
addToSyncSet(syncId, wc);
}
if (transition != null) transition.collect(wc);
+ final DisplayArea da = wc.asDisplayArea();
+ // Only check DisplayArea here as a similar thing is done for DisplayContent above.
+ if (da != null && wc.asDisplayContent() == null
+ && entry.getValue().getWindowingMode() != da.getWindowingMode()) {
+ // Go through all tasks and collect them before changing the windowing mode of a
+ // display-level container.
+ // TODO(shell-transitions): handle this more elegantly.
+ da.mTransitionController.collectForDisplayAreaChange(da, transition);
+ }
- if (finishTransition != null) {
- // Deal with edge-cases in recents where it pretends to finish itself.
- if ((entry.getValue().getChangeMask()
- & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
+ if ((entry.getValue().getChangeMask()
+ & WindowContainerTransaction.Change.CHANGE_FORCE_NO_PIP) != 0) {
+ // Disable entering pip (eg. when recents pretends to finish itself)
+ if (finishTransition != null) {
finishTransition.setCanPipOnFinish(false /* canPipOnFinish */);
+ } else if (transition != null) {
+ transition.setCanPipOnFinish(false /* canPipOnFinish */);
}
}
+ // A bit hacky, but we need to detect "remove PiP" so that we can "wrap" the
+ // setWindowingMode call in force-hidden.
+ boolean forceHiddenForPip = false;
+ if (wc.asTask() != null && wc.inPinnedWindowingMode()
+ && entry.getValue().getWindowingMode() == WINDOWING_MODE_UNDEFINED) {
+ // We are in pip and going to undefined. Now search hierarchy ops to determine
+ // whether we are removing pip or expanding pip.
+ for (int i = 0; i < hopSize; ++i) {
+ final WindowContainerTransaction.HierarchyOp hop = hops.get(i);
+ if (hop.getType() != HIERARCHY_OP_TYPE_REORDER) continue;
+ final WindowContainer hopWc = WindowContainer.fromBinder(
+ hop.getContainer());
+ if (!wc.equals(hopWc)) continue;
+ forceHiddenForPip = !hop.getToTop();
+ }
+ }
+ if (forceHiddenForPip) {
+ wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, true /* set */);
+ }
int containerEffect = applyWindowContainerChange(wc, entry.getValue(),
t.getErrorCallbackToken());
effects |= containerEffect;
+ if (forceHiddenForPip) {
+ wc.asTask().setForceHidden(FLAG_FORCE_HIDDEN_FOR_PINNED_TASK, false /* set */);
+ }
+
// Lifecycle changes will trigger ensureConfig for everything.
if ((effects & TRANSACT_EFFECTS_LIFECYCLE) == 0
&& (containerEffect & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
@@ -435,8 +476,6 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
// Hierarchy changes
- final List<WindowContainerTransaction.HierarchyOp> hops = t.getHierarchyOps();
- final int hopSize = hops.size();
if (hopSize > 0) {
final boolean isInLockTaskMode = mService.isInLockTaskMode();
for (int i = 0; i < hopSize; ++i) {
@@ -498,7 +537,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
- if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) == 0) {
+ if ((effects & TRANSACT_EFFECTS_CLIENT_CONFIG) != 0) {
mService.addWindowLayoutReasons(LAYOUT_REASON_CONFIG_CHANGED);
}
} finally {
@@ -552,6 +591,13 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
+ " windowing mode during locked task mode.");
}
+ if (windowingMode == WindowConfiguration.WINDOWING_MODE_PINNED) {
+ // Do not directly put the container into PINNED mode as it may not support it or
+ // the app may not want to enter it. Instead, send a signal to request PIP
+ // mode to the app if they wish to support it below in #applyTaskChanges.
+ return effects;
+ }
+
final int prevMode = container.getWindowingMode();
container.setWindowingMode(windowingMode);
if (prevMode != container.getWindowingMode()) {
@@ -608,6 +654,28 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
tr.mDisplayContent.mPinnedTaskController.setEnterPipBounds(enterPipBounds);
}
+ if (c.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_PINNED
+ && !tr.inPinnedWindowingMode()) {
+ final ActivityRecord activity = tr.getTopNonFinishingActivity();
+ if (activity != null) {
+ final boolean lastSupportsEnterPipOnTaskSwitch =
+ activity.supportsEnterPipOnTaskSwitch;
+ // Temporarily force enable enter PIP on task switch so that PIP is requested
+ // regardless of whether the activity is resumed or paused.
+ activity.supportsEnterPipOnTaskSwitch = true;
+ boolean canEnterPip = activity.checkEnterPictureInPictureState(
+ "applyTaskChanges", true /* beforeStopping */);
+ if (canEnterPip) {
+ canEnterPip = mService.mActivityClientController
+ .requestPictureInPictureMode(activity);
+ }
+ if (!canEnterPip) {
+ // Restore the flag to its previous state when the activity cannot enter PIP.
+ activity.supportsEnterPipOnTaskSwitch = lastSupportsEnterPipOnTaskSwitch;
+ }
+ }
+ }
+
return effects;
}
@@ -697,7 +765,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
bottomActivity)) {
Slog.w(TAG, "Skip removing TaskFragment due in lock task mode.");
sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
- new IllegalStateException(
+ taskFragment, type, new IllegalStateException(
"Not allow to delete task fragment in lock task mode."));
break;
}
@@ -711,13 +779,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (tf == null) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to operate with invalid fragment token");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf, type,
+ exception);
break;
}
if (tf.isEmbeddedTaskFragmentInPip()) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to start activity in PIP TaskFragment");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf, type,
+ exception);
break;
}
final Intent activityIntent = hop.getActivityIntent();
@@ -727,7 +797,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
hop.getCallingActivity(), caller.mUid, caller.mPid,
errorCallbackToken);
if (!isStartResultSuccessful(result)) {
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken,
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf, type,
convertStartFailureToThrowable(result, activityIntent));
} else {
effects |= TRANSACT_EFFECTS_LIFECYCLE;
@@ -748,28 +818,34 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (parent == null || activity == null) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to operate with invalid fragment token or activity.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
if (parent.isEmbeddedTaskFragmentInPip()) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to reparent activity to PIP TaskFragment");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
if (parent.isAllowedToEmbedActivity(activity) != EMBEDDING_ALLOWED) {
final Throwable exception = new SecurityException(
"The task fragment is not allowed to embed the given activity.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
if (parent.getTask() != activity.getTask()) {
final Throwable exception = new SecurityException("The reparented activity is"
+ " not in the same Task as the target TaskFragment.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, parent, type,
+ exception);
break;
}
+ prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(parent,
+ activity);
activity.reparent(parent, POSITION_TOP);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
break;
@@ -784,17 +860,19 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (tf1 == null || (adjacentFragmentToken != null && tf2 == null)) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to set adjacent on invalid fragment tokens");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf1, type,
+ exception);
break;
}
if (tf1.isEmbeddedTaskFragmentInPip()
|| (tf2 != null && tf2.isEmbeddedTaskFragmentInPip())) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to set adjacent on TaskFragment in PIP Task");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, tf1, type,
+ exception);
break;
}
- tf1.setAdjacentTaskFragment(tf2, false /* moveAdjacentTogether */);
+ tf1.setAdjacentTaskFragment(tf2);
effects |= TRANSACT_EFFECTS_LIFECYCLE;
final Bundle bundle = hop.getLaunchOptions();
@@ -836,22 +914,22 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
tf.getDisplayContent().setFocusedApp(targetFocus);
break;
}
- default: {
- // The other operations may change task order so they are skipped while in lock
- // task mode. The above operations are still allowed because they don't move
- // tasks. And it may be necessary such as clearing launch root after entering
- // lock task mode.
- if (isInLockTaskMode) {
- Slog.w(TAG, "Skip applying hierarchy operation " + hop
- + " while in lock task mode");
- return effects;
- }
- }
- }
-
- switch (type) {
case HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT: {
- effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId);
+ effects |= reparentChildrenTasksHierarchyOp(hop, transition, syncId,
+ isInLockTaskMode);
+ break;
+ }
+ case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
+ mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
+ "launchTask HierarchyOp");
+ final Bundle launchOpts = hop.getLaunchOptions();
+ final int taskId = launchOpts.getInt(
+ WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
+ launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
+ final SafeActivityOptions safeOptions =
+ SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
+ waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
+ caller.mPid, caller.mUid, taskId, safeOptions));
break;
}
case HIERARCHY_OP_TYPE_REORDER:
@@ -861,6 +939,16 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
Slog.e(TAG, "Attempt to operate on detached container: " + wc);
break;
}
+ // There is no use case to ask the reparent operation in lock-task mode now, so keep
+ // skipping this operation as usual.
+ if (isInLockTaskMode && type == HIERARCHY_OP_TYPE_REPARENT) {
+ Slog.w(TAG, "Skip applying hierarchy operation " + hop
+ + " while in lock task mode");
+ break;
+ }
+ if (isLockTaskModeViolation(wc.getParent(), wc.asTask(), isInLockTaskMode)) {
+ break;
+ }
if (syncId >= 0) {
addToSyncSet(syncId, wc);
}
@@ -886,19 +974,20 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
effects |= sanitizeAndApplyHierarchyOp(wc, hop);
break;
}
- case HIERARCHY_OP_TYPE_LAUNCH_TASK: {
- mService.mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
- "launchTask HierarchyOp");
- final Bundle launchOpts = hop.getLaunchOptions();
- final int taskId = launchOpts.getInt(
- WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
- launchOpts.remove(WindowContainerTransaction.HierarchyOp.LAUNCH_KEY_TASK_ID);
- final SafeActivityOptions safeOptions =
- SafeActivityOptions.fromBundle(launchOpts, caller.mPid, caller.mUid);
- waitAsyncStart(() -> mService.mTaskSupervisor.startActivityFromRecents(
- caller.mPid, caller.mUid, taskId, safeOptions));
- break;
+ default: {
+ // The other operations may change task order so they are skipped while in lock
+ // task mode. The above operations are still allowed because they don't move
+ // tasks. And it may be necessary such as clearing launch root after entering
+ // lock task mode.
+ if (isInLockTaskMode) {
+ Slog.w(TAG, "Skip applying hierarchy operation " + hop
+ + " while in lock task mode");
+ return effects;
+ }
}
+ }
+
+ switch (type) {
case HIERARCHY_OP_TYPE_PENDING_INTENT: {
String resolvedType = hop.getActivityIntent() != null
? hop.getActivityIntent().resolveTypeIfNeeded(
@@ -983,6 +1072,41 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects;
}
+ private void prepareActivityEmbeddingTransitionForReparentActivityToTaskFragment(
+ @NonNull TaskFragment taskFragment, @NonNull ActivityRecord activity) {
+ if (!taskFragment.canHaveEmbeddingActivityTransition(activity)) {
+ return;
+ }
+
+ // The reparent can happen in the following cases:
+ // 1. Reparent an existing activity to split when app launches new intent.
+ // - This happens after app calls to start activity, but before the activity is actually
+ // started, so we don't expect any collecting transition, but if it does, we can't
+ // queue the WCT because the start activity won't wait.
+ // 2. Reparent an existing activity to split to launch placeholder when Task size changed.
+ // - We expect to have a collecting transition for the Task resize, so just collect.
+ // 3. Reparent a new launching activity to an always-expand container.
+ // 4. Reparent a new launching activity to split to launch placeholder together.
+ // 5. Reparent a new launching activity to an existing split.
+ // - The new launching activity should have start an OPEN transition, so just collect.
+ // 6. Reparent PiP activity back to the original Task.
+ // - This should be part of the exiting PiP transition, so just collect.
+
+ if (!taskFragment.getBounds().equals(activity.getBounds()) && activity.isVisible()
+ && !mTransitionController.isCollecting()) {
+ // 1. Reparent an existing activity to split when app launches new intent.
+ mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, activity);
+ }
+
+ // We expect the activity to be in the transition already, so just collect the TaskFragment.
+ if (mTransitionController.isCollecting(activity)) {
+ taskFragment.collectEmbeddedTaskFragmentIfNeeded();
+ } else {
+ ProtoLog.w(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Reparenting Activity"
+ + " to embedded TaskFragment, but the Activity is not collected");
+ }
+ }
+
/** A helper method to send minimum dimension violation error to the client. */
private void sendMinimumDimensionViolation(TaskFragment taskFragment, Point minDimensions,
IBinder errorCallbackToken, String reason) {
@@ -993,7 +1117,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
+ taskFragment.getBounds() + " does not satisfy minimum dimensions:"
+ minDimensions + " " + reason);
sendTaskFragmentOperationFailure(taskFragment.getTaskFragmentOrganizer(),
- errorCallbackToken, exception);
+ errorCallbackToken, taskFragment, -1 /* opType */, exception);
}
/**
@@ -1090,8 +1214,25 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return TRANSACT_EFFECTS_LIFECYCLE;
}
+ private boolean isLockTaskModeViolation(WindowContainer parent, Task task,
+ boolean isInLockTaskMode) {
+ if (!isInLockTaskMode || parent == null || task == null) {
+ return false;
+ }
+ final LockTaskController lockTaskController = mService.getLockTaskController();
+ boolean taskViolation = lockTaskController.isLockTaskModeViolation(task);
+ if (!taskViolation && parent.asTask() != null) {
+ taskViolation = lockTaskController.isLockTaskModeViolation(parent.asTask());
+ }
+ if (taskViolation) {
+ Slog.w(TAG, "Can't support the operation since in lock task mode violation. "
+ + " Task: " + task + " Parent : " + parent);
+ }
+ return taskViolation;
+ }
+
private int reparentChildrenTasksHierarchyOp(WindowContainerTransaction.HierarchyOp hop,
- @Nullable Transition transition, int syncId) {
+ @Nullable Transition transition, int syncId, boolean isInLockTaskMode) {
WindowContainer<?> currentParent = hop.getContainer() != null
? WindowContainer.fromBinder(hop.getContainer()) : null;
WindowContainer newParent = hop.getNewParent() != null
@@ -1129,6 +1270,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
? newParent.asTask().getDisplayArea()
: newParent.asTaskDisplayArea();
final WindowContainer finalCurrentParent = currentParent;
+ final WindowContainer finalNewParent = newParent;
Slog.i(TAG, "reparentChildrenTasksHierarchyOp"
+ " currentParent=" + currentParent + " newParent=" + newParent + " hop=" + hop);
@@ -1148,8 +1290,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
+ " task=" + task);
return false;
}
- if (!ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())
- || !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
+ if (!ArrayUtils.isEmpty(hop.getActivityTypes())
+ && !ArrayUtils.contains(hop.getActivityTypes(), task.getActivityType())) {
+ return false;
+ }
+ if (!ArrayUtils.isEmpty(hop.getWindowingModes())
+ && !ArrayUtils.contains(hop.getWindowingModes(), task.getWindowingMode())) {
+ return false;
+ }
+ if (isLockTaskModeViolation(finalNewParent, task, isInLockTaskMode)) {
return false;
}
@@ -1196,7 +1345,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
Slog.e(TAG, "Attempt to set adjacent TaskFragment in PIP Task");
return 0;
}
- root1.setAdjacentTaskFragment(root2, hop.getMoveAdjacentTogether());
+ root1.setAdjacentTaskFragment(root2);
return TRANSACT_EFFECTS_LIFECYCLE;
}
@@ -1521,13 +1670,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (ownerActivity == null || ownerActivity.getTask() == null) {
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid ownerToken");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
if (!ownerActivity.isResizeable()) {
final IllegalArgumentException exception = new IllegalArgumentException("Not allowed"
+ " to operate with non-resizable owner Activity");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
// The ownerActivity has to belong to the same app as the target Task.
@@ -1537,13 +1688,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final Throwable exception =
new SecurityException("Not allowed to operate with the ownerToken while "
+ "the root activity of the target task belong to the different app");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
if (ownerTask.inPinnedWindowingMode()) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to create TaskFragment in PIP Task");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, null /* taskFragment */,
+ HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT, exception);
return;
}
final TaskFragment taskFragment = new TaskFragment(mService,
@@ -1571,7 +1724,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (newParentTF == null) {
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid container");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
if (newParentTF.getTaskFragmentOrganizer() != null) {
@@ -1582,20 +1736,23 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
if (isEmbeddingDisallowed) {
final Throwable exception = new SecurityException(
"The new parent is not allowed to embed the activities.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
}
if (newParentTF.isEmbeddedTaskFragmentInPip() || oldParent.isEmbeddedTaskFragmentInPip()) {
final Throwable exception = new SecurityException(
"Not allow to reparent in TaskFragment in PIP Task.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
if (newParentTF.getTask() != oldParent.getTask()) {
final Throwable exception = new SecurityException(
"The new parent is not in the same Task as the old parent.");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, newParentTF,
+ HIERARCHY_OP_TYPE_REPARENT_CHILDREN, exception);
return;
}
while (oldParent.hasChild()) {
@@ -1610,7 +1767,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
final Throwable exception =
new IllegalArgumentException("Not allowed to operate with invalid "
+ "taskFragment");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT, exception);
return 0;
}
if (taskFragment.isEmbeddedTaskFragmentInPip()
@@ -1619,7 +1777,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
&& taskFragment.getTopNonFinishingActivity() != null) {
final Throwable exception = new IllegalArgumentException(
"Not allowed to delete TaskFragment in PIP Task");
- sendTaskFragmentOperationFailure(organizer, errorCallbackToken, exception);
+ sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+ HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT, exception);
return 0;
}
mLaunchTaskFragments.removeAt(index);
@@ -1647,12 +1806,14 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
void sendTaskFragmentOperationFailure(@NonNull ITaskFragmentOrganizer organizer,
- @Nullable IBinder errorCallbackToken, @NonNull Throwable exception) {
+ @Nullable IBinder errorCallbackToken, @Nullable TaskFragment taskFragment, int opType,
+ @NonNull Throwable exception) {
if (organizer == null) {
throw new IllegalArgumentException("Not allowed to operate with invalid organizer");
}
mService.mTaskFragmentOrganizerController
- .onTaskFragmentError(organizer, errorCallbackToken, exception);
+ .onTaskFragmentError(organizer, errorCallbackToken, taskFragment, opType,
+ exception);
}
private Throwable convertStartFailureToThrowable(int result, Intent intent) {
diff --git a/services/core/java/com/android/server/wm/WindowOrientationListener.java b/services/core/java/com/android/server/wm/WindowOrientationListener.java
index de87ab9dcce0..3e165e442d79 100644
--- a/services/core/java/com/android/server/wm/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/wm/WindowOrientationListener.java
@@ -296,9 +296,9 @@ public abstract class WindowOrientationListener {
/**
* Whether the device is in the lock screen.
- * @return returns true if the screen is locked. Otherwise, returns false.
+ * @return returns true if the key guard is showing on the lock screen.
*/
- public abstract boolean isKeyguardLocked();
+ public abstract boolean isKeyguardShowingAndNotOccluded();
public void dumpDebug(ProtoOutputStream proto, long fieldId) {
final long token = proto.start(fieldId);
@@ -1151,7 +1151,7 @@ public abstract class WindowOrientationListener {
FrameworkStatsLog.DEVICE_ROTATED__ROTATION_EVENT_TYPE__ACTUAL_EVENT);
if (isRotationResolverEnabled()) {
- if (isKeyguardLocked()) {
+ if (isKeyguardShowingAndNotOccluded()) {
if (mLastRotationResolution != ROTATION_UNSET
&& SystemClock.uptimeMillis() - mLastRotationResolutionTimeStamp
< mRotationMemorizationTimeoutMillis) {
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 40417a4857d3..3ff912cca091 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.ActivityManager.PROCESS_STATE_CACHED_ACTIVITY;
import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ASSETS_SEQ_UNDEFINED;
@@ -24,7 +25,6 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
import static com.android.internal.util.Preconditions.checkArgument;
-import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.wm.ActivityRecord.State.DESTROYED;
import static com.android.server.wm.ActivityRecord.State.DESTROYING;
import static com.android.server.wm.ActivityRecord.State.PAUSED;
@@ -39,6 +39,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.ActivityTaskManagerService.INSTRUMENTATION_KEY_DISPATCHING_TIMEOUT_MILLIS;
import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
+import static com.android.server.wm.WindowManagerService.MY_PID;
import android.Manifest;
import android.annotation.NonNull;
@@ -195,6 +196,11 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
/** Whether the process configuration is waiting to be dispatched to the process. */
private boolean mHasPendingConfigurationChange;
+ /** If the process state is in (<=) the cached state, then defer delivery of the config. */
+ private static final int CACHED_CONFIG_PROC_STATE = PROCESS_STATE_CACHED_ACTIVITY;
+ /** Whether {@link #mLastReportedConfiguration} is deferred by the cached state. */
+ private volatile boolean mHasCachedConfiguration;
+
/**
* Registered {@link DisplayArea} as a listener to override config changes. {@code null} if not
* registered.
@@ -316,8 +322,27 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
return mCurProcState;
}
+ /**
+ * Sets the computed process state from the oom adjustment calculation. This is frequently
+ * called in activity manager's lock, so don't use window manager lock here.
+ */
+ @HotPath(caller = HotPath.OOM_ADJUSTMENT)
public void setReportedProcState(int repProcState) {
+ final int prevProcState = mRepProcState;
mRepProcState = repProcState;
+
+ // Deliver the cached config if the app changes from cached state to non-cached state.
+ final IApplicationThread thread = mThread;
+ if (prevProcState >= CACHED_CONFIG_PROC_STATE && repProcState < CACHED_CONFIG_PROC_STATE
+ && thread != null && mHasCachedConfiguration) {
+ final Configuration config;
+ synchronized (mLastReportedConfiguration) {
+ config = new Configuration(mLastReportedConfiguration);
+ }
+ // Schedule immediately to make sure the app component (e.g. receiver, service) can get
+ // the latest configuration in their lifecycle callbacks (e.g. onReceive, onCreate).
+ scheduleConfigurationChange(thread, config);
+ }
}
int getReportedProcState() {
@@ -1126,6 +1151,13 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
mAtm.mH.sendMessage(m);
}
+ /** Refreshes oom adjustment and process state of this process. */
+ void scheduleUpdateOomAdj() {
+ mAtm.mH.sendMessage(PooledLambda.obtainMessage(WindowProcessListener::updateProcessInfo,
+ mListener, false /* updateServiceConnectionActivities */,
+ false /* activityChange */, true /* updateOomAdj */));
+ }
+
/** Makes the process have top state before oom-adj is computed from a posted message. */
void addToPendingTop() {
mAtm.mAmInternal.addPendingTopUid(mUid, mPid, mThread);
@@ -1328,12 +1360,22 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
@Override
public void onConfigurationChanged(Configuration newGlobalConfig) {
super.onConfigurationChanged(newGlobalConfig);
- updateConfiguration();
- }
+ final Configuration config = getConfiguration();
+ if (mLastReportedConfiguration.equals(config)) {
+ // Nothing changed.
+ if (Build.IS_DEBUGGABLE && mHasImeService) {
+ // TODO (b/135719017): Temporary log for debugging IME service.
+ Slog.w(TAG_CONFIGURATION, "Current config: " + config
+ + " unchanged for IME proc " + mName);
+ }
+ return;
+ }
- @Override
- public void onRequestedOverrideConfigurationChanged(Configuration overrideConfiguration) {
- super.onRequestedOverrideConfigurationChanged(overrideConfiguration);
+ if (mPauseConfigurationDispatchCount > 0) {
+ mHasPendingConfigurationChange = true;
+ return;
+ }
+ dispatchConfiguration(config);
}
@Override
@@ -1359,25 +1401,6 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
resolvedConfig.seq = newParentConfig.seq;
}
- private void updateConfiguration() {
- final Configuration config = getConfiguration();
- if (mLastReportedConfiguration.diff(config) == 0) {
- // Nothing changed.
- if (Build.IS_DEBUGGABLE && mHasImeService) {
- // TODO (b/135719017): Temporary log for debugging IME service.
- Slog.w(TAG_CONFIGURATION, "Current config: " + config
- + " unchanged for IME proc " + mName);
- }
- return;
- }
-
- if (mPauseConfigurationDispatchCount > 0) {
- mHasPendingConfigurationChange = true;
- return;
- }
- dispatchConfiguration(config);
- }
-
void dispatchConfiguration(Configuration config) {
mHasPendingConfigurationChange = false;
if (mThread == null) {
@@ -1388,29 +1411,47 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
return;
}
+
+ config.seq = mAtm.increaseConfigurationSeqLocked();
+ setLastReportedConfiguration(config);
+
+ // A cached process doesn't have running application components, so it is unnecessary to
+ // notify the configuration change. The last-reported-configuration is still set because
+ // setReportedProcState() should not write any fields that require WM lock.
+ if (mRepProcState >= CACHED_CONFIG_PROC_STATE) {
+ mHasCachedConfiguration = true;
+ // Because there are 2 volatile accesses in setReportedProcState(): mRepProcState and
+ // mHasCachedConfiguration, check again in case mRepProcState is changed but hasn't
+ // read the change of mHasCachedConfiguration.
+ if (mRepProcState >= CACHED_CONFIG_PROC_STATE) {
+ return;
+ }
+ }
+
+ scheduleConfigurationChange(mThread, config);
+ }
+
+ private void scheduleConfigurationChange(IApplicationThread thread, Configuration config) {
ProtoLog.v(WM_DEBUG_CONFIGURATION, "Sending to proc %s new config %s", mName,
config);
if (Build.IS_DEBUGGABLE && mHasImeService) {
// TODO (b/135719017): Temporary log for debugging IME service.
Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
}
-
+ mHasCachedConfiguration = false;
try {
- config.seq = mAtm.increaseConfigurationSeqLocked();
- mAtm.getLifecycleManager().scheduleTransaction(mThread,
+ mAtm.getLifecycleManager().scheduleTransaction(thread,
ConfigurationChangeItem.obtain(config));
- setLastReportedConfiguration(config);
} catch (Exception e) {
- Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
+ Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change: " + mOwner, e);
}
}
void setLastReportedConfiguration(Configuration config) {
- mLastReportedConfiguration.setTo(config);
- }
-
- Configuration getLastReportedConfiguration() {
- return mLastReportedConfiguration;
+ // Synchronize for the access from setReportedProcState().
+ synchronized (mLastReportedConfiguration) {
+ mLastReportedConfiguration.setTo(config);
+ }
}
void pauseConfigurationDispatch() {
@@ -1461,6 +1502,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
// config seq. This increment ensures that the client won't ignore the configuration.
config.seq = mAtm.increaseConfigurationSeqLocked();
}
+ // LaunchActivityItem includes the latest process configuration.
+ mHasCachedConfiguration = false;
return config;
}
@@ -1688,7 +1731,8 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio
}
pw.println(prefix + " Configuration=" + getConfiguration());
pw.println(prefix + " OverrideConfiguration=" + getRequestedOverrideConfiguration());
- pw.println(prefix + " mLastReportedConfiguration=" + mLastReportedConfiguration);
+ pw.println(prefix + " mLastReportedConfiguration=" + (mHasCachedConfiguration
+ ? ("(cached) " + mLastReportedConfiguration) : mLastReportedConfiguration));
final int stateFlags = mActivityStateFlags;
if (stateFlags != ACTIVITY_STATE_FLAG_MASK_MIN_TASK_LAYER) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 4c32edc6d709..5c9cb05ee153 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -115,8 +115,8 @@ import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_RESIZE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_STARTING_WINDOW;
+import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SYNC_ENGINE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_INSETS;
-import static com.android.server.am.ActivityManagerService.MY_PID;
import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
@@ -137,7 +137,6 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITIO
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_STARTING_REVEAL;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
-import static com.android.server.wm.WindowContainer.AnimationFlags.CHILDREN;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.WINDOW;
@@ -151,6 +150,7 @@ import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
import static com.android.server.wm.WindowManagerService.H.WINDOW_STATE_BLAST_SYNC_TIMEOUT;
import static com.android.server.wm.WindowManagerService.MAX_ANIMATION_DURATION;
+import static com.android.server.wm.WindowManagerService.MY_PID;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_REMOVING_FOCUS;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_WILL_PLACE_SURFACES;
@@ -392,6 +392,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
int mSyncSeqId = 0;
int mLastSeqIdSentToRelayout = 0;
+ /** The last syncId associated with a prepareSync or 0 when no sync is active. */
+ int mPrepareSyncSeqId = 0;
+
/**
* {@code true} when the client was still drawing for sync when the sync-set was finished or
* cancelled. This can happen if the window goes away during a sync. In this situation we need
@@ -616,11 +619,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
int mLastVisibleLayoutRotation = -1;
/**
- * Set when we need to report the orientation change to client to trigger a relayout.
- */
- boolean mReportOrientationChanged;
-
- /**
* How long we last kept the screen frozen.
*/
int mLastFreezeDuration;
@@ -814,7 +812,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
};
private final Consumer<SurfaceControl.Transaction> mSetSurfacePositionConsumer = t -> {
- if (mSurfaceControl != null && mSurfaceControl.isValid()) {
+ // Only apply the position to the surface when there's no leash created.
+ if (mSurfaceControl != null && mSurfaceControl.isValid() && !mSurfaceAnimator.hasLeash()) {
t.setPosition(mSurfaceControl, mSurfacePosition.x, mSurfacePosition.y);
}
};
@@ -1251,7 +1250,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mSession.windowAddedLocked();
}
- boolean updateGlobalScale() {
+ void updateGlobalScale() {
if (hasCompatScale()) {
if (mOverrideScale != 1f) {
mGlobalScale = mToken.hasSizeCompatBounds()
@@ -1261,11 +1260,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mGlobalScale = mToken.getSizeCompatScale();
}
mInvGlobalScale = 1f / mGlobalScale;
- return true;
+ return;
}
mGlobalScale = mInvGlobalScale = 1f;
- return false;
}
/**
@@ -1481,15 +1479,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mAttrs;
}
- WindowManager.LayoutParams getLayoutingAttrs(int rotation) {
- final WindowManager.LayoutParams[] paramsForRotation = mAttrs.paramsForRotation;
- if (paramsForRotation == null || paramsForRotation.length != 4
- || paramsForRotation[rotation] == null) {
- return mAttrs;
- }
- return paramsForRotation[rotation];
- }
-
/** Retrieves the flags used to disable system UI functions. */
int getDisableFlags() {
return mDisableFlags;
@@ -1527,29 +1516,27 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final boolean dragResizingChanged = isDragResizeChanged()
&& !isDragResizingChangeReported();
+ final boolean attachedFrameChanged = LOCAL_LAYOUT
+ && mLayoutAttached && getParentWindow().frameChanged();
+
if (DEBUG) {
Slog.v(TAG_WM, "Resizing " + this + ": configChanged=" + configChanged
+ " dragResizingChanged=" + dragResizingChanged
+ " last=" + mWindowFrames.mLastFrame + " frame=" + mWindowFrames.mFrame);
}
- // We update mLastFrame always rather than in the conditional with the last inset
- // variables, because mFrameSizeChanged only tracks the width and height changing.
- updateLastFrames();
-
// Add a window that is using blastSync to the resizing list if it hasn't been reported
// already. This because the window is waiting on a finishDrawing from the client.
if (didFrameInsetsChange
|| configChanged
|| insetsChanged
|| dragResizingChanged
- || mReportOrientationChanged
- || shouldSendRedrawForSync()) {
+ || shouldSendRedrawForSync()
+ || attachedFrameChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
- "Resize reasons for w=%s: %s configChanged=%b "
- + "dragResizingChanged=%b reportOrientationChanged=%b",
+ "Resize reasons for w=%s: %s configChanged=%b dragResizingChanged=%b",
this, mWindowFrames.getInsetsChangedInfo(),
- configChanged, dragResizingChanged, mReportOrientationChanged);
+ configChanged, dragResizingChanged);
if (insetsChanged) {
mWindowFrames.setInsetsChanged(false);
@@ -1601,6 +1588,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
}
}
+ private boolean frameChanged() {
+ return !mWindowFrames.mFrame.equals(mWindowFrames.mLastFrame);
+ }
+
boolean getOrientationChanging() {
// In addition to the local state flag, we must also consider the difference in the last
// reported configuration vs. the current state. If the client code has not been informed of
@@ -1711,7 +1702,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
final InsetsState insetsStateForWindow = insetsPolicy
.enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
- getWindowingMode(), isAlwaysOnTop(), rawInsetsState);
+ getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState);
return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
includeTransient);
}
@@ -2180,6 +2171,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return;
}
if (mActivityRecord != null) {
+ if (!mActivityRecord.mVisibleRequested) return;
if (mActivityRecord.allDrawn) {
// The allDrawn of activity is reset when the visibility is changed to visible, so
// the content should be ready if allDrawn is set.
@@ -2456,8 +2448,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
final DisplayContent dc = getDisplayContent();
if (isImeLayeringTarget()) {
- // Remove the IME screenshot surface if the layering target is not animating.
- dc.removeImeScreenshotIfPossible();
+ // Remove the attached IME screenshot surface.
+ dc.removeImeSurfaceByTarget(this);
// Make sure to set mImeLayeringTarget as null when the removed window is the
// IME target, in case computeImeTarget may use the outdated target.
dc.setImeLayeringTarget(null);
@@ -3468,10 +3460,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mClient.asBinder().isBinderAlive();
}
- boolean isClosing() {
- return mAnimatingExit || (mActivityRecord != null && mActivityRecord.isClosingOrEnteringPip());
- }
-
void sendAppVisibilityToClients() {
super.sendAppVisibilityToClients();
@@ -3579,10 +3567,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mAnimatingExit = false;
ProtoLog.d(WM_DEBUG_ANIM, "Clear animatingExit: reason=destroySurface win=%s", this);
- // Clear the flag so the buffer requested for the next new surface won't be dropped by
- // mistaking the surface size needs to update.
- mReportOrientationChanged = false;
-
if (useBLASTSync()) {
immediatelyNotifyBlastSync();
}
@@ -3599,6 +3583,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
} else {
logExclusionRestrictions(EXCLUSION_LEFT);
logExclusionRestrictions(EXCLUSION_RIGHT);
+ getDisplayContent().removeImeSurfaceByTarget(this);
}
// Exclude toast because legacy apps may show toast window by themselves, so the misused
// apps won't always be considered as foreground state.
@@ -3855,6 +3840,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mInvGlobalScale != 1.0f && hasCompatScale()) {
outFrames.displayFrame.scale(mInvGlobalScale);
}
+ if (mLayoutAttached) {
+ if (outFrames.attachedFrame == null) {
+ outFrames.attachedFrame = new Rect();
+ }
+ outFrames.attachedFrame.set(getParentWindow().getCompatFrame());
+ }
// Note: in the cases where the window is tied to an activity, we should not send a
// configuration update when the window has requested to be hidden. Doing so can lead to
@@ -3899,21 +3890,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
ProtoLog.i(WM_DEBUG_ORIENTATION, "Resizing %s WITH DRAW PENDING", this);
}
- final boolean reportOrientation = mReportOrientationChanged;
// Always reset these states first, so if {@link IWindow#resized} fails, this
// window won't be added to {@link WindowManagerService#mResizingWindows} and set
// {@link #mOrientationChanging} to true again by {@link #updateResizingWindowIfNeeded}
// that may cause WINDOW_FREEZE_TIMEOUT because resizing the client keeps failing.
- mReportOrientationChanged = false;
mDragResizingChangeReported = true;
mWindowFrames.clearReportResizeHints();
+ // We update mLastFrame always rather than in the conditional with the last inset
+ // variables, because mFrameSizeChanged only tracks the width and height changing.
+ updateLastFrames();
+
+ final int prevRotation = mLastReportedConfiguration
+ .getMergedConfiguration().windowConfiguration.getRotation();
fillClientWindowFramesAndConfiguration(mClientWindowFrames, mLastReportedConfiguration,
true /* useLatestConfig */, false /* relayoutVisible */);
final boolean syncRedraw = shouldSendRedrawForSync();
final boolean reportDraw = syncRedraw || drawPending;
final boolean isDragResizeChanged = isDragResizeChanged();
- final boolean forceRelayout = syncRedraw || reportOrientation || isDragResizeChanged;
+ final boolean forceRelayout = syncRedraw || isDragResizeChanged;
final DisplayContent displayContent = getDisplayContent();
final boolean alwaysConsumeSystemBars =
displayContent.getDisplayPolicy().areSystemBarsForcedShownLw();
@@ -3940,7 +3935,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mClient.resized(mClientWindowFrames, reportDraw, mLastReportedConfiguration,
getCompatInsetsState(), forceRelayout, alwaysConsumeSystemBars, displayId,
mSyncSeqId, resizeMode);
- if (drawPending && reportOrientation && mOrientationChanging) {
+ if (drawPending && prevRotation >= 0 && prevRotation != mLastReportedConfiguration
+ .getMergedConfiguration().windowConfiguration.getRotation()) {
mOrientationChangeRedrawRequestTime = SystemClock.elapsedRealtime();
ProtoLog.v(WM_DEBUG_ORIENTATION,
"Requested redraw for orientation change: %s", this);
@@ -4365,12 +4361,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
+ " mDestroying=" + mDestroying
+ " mRemoved=" + mRemoved);
}
- if (getOrientationChanging() || mAppFreezing || mReportOrientationChanged) {
+ if (getOrientationChanging() || mAppFreezing) {
pw.println(prefix + "mOrientationChanging=" + mOrientationChanging
+ " configOrientationChanging="
+ (getLastReportedConfiguration().orientation != getConfiguration().orientation)
- + " mAppFreezing=" + mAppFreezing
- + " mReportOrientationChanged=" + mReportOrientationChanged);
+ + " mAppFreezing=" + mAppFreezing);
}
if (mLastFreezeDuration != 0) {
pw.print(prefix + "mLastFreezeDuration=");
@@ -4424,6 +4419,8 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
pw.println(prefix + "Requested visibilities: " + visibilityString);
}
}
+
+ pw.println(prefix + "mPrepareSyncSeqId=" + mPrepareSyncSeqId);
}
@Override
@@ -4973,22 +4970,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
|| isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
}
- boolean isExitAnimationRunningSelfOrChild() {
- return isAnimating(CHILDREN, ANIMATION_TYPE_WINDOW_ANIMATION);
- }
-
- /**
- * @return {@code true} if self or the parent container of the window is in transition.
- * (e.g. The app or recents transition)
- */
- boolean inTransitionSelfOrParent() {
- if (!mTransitionController.isShellTransitionsEnabled()) {
- return isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION | ANIMATION_TYPE_RECENTS);
- }
- return mTransitionController.inTransition(this);
- }
-
private boolean shouldFinishAnimatingExit() {
// Exit animation might be applied soon.
if (inTransition()) {
@@ -5575,7 +5556,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mLastSurfaceInsets.set(mAttrs.surfaceInsets);
}
if (surfaceSizeChanged && mWinAnimator.getShown() && !canPlayMoveAnimation()
- && okToDisplay()) {
+ && okToDisplay() && mSyncState == SYNC_STATE_NONE) {
applyWithNextDraw(mSetSurfacePositionConsumer);
} else {
mSetSurfacePositionConsumer.accept(t);
@@ -5927,11 +5908,25 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mWinAnimator.getSurfaceControl();
}
+ /** Drops a buffer for this window's view-root from a transaction */
+ private void dropBufferFrom(Transaction t) {
+ SurfaceControl viewSurface = getClientViewRootSurface();
+ if (viewSurface == null) return;
+ t.setBuffer(viewSurface, (android.hardware.HardwareBuffer) null);
+ }
+
@Override
boolean prepareSync() {
+ if (!mDrawHandlers.isEmpty()) {
+ Slog.w(TAG, "prepareSync with mDrawHandlers, " + this + ", " + Debug.getCallers(8));
+ }
if (!super.prepareSync()) {
return false;
}
+ if (mIsWallpaper) {
+ // TODO(b/233286785): Add sync support to wallpaper.
+ return false;
+ }
// In the WindowContainer implementation we immediately mark ready
// since a generic WindowContainer only needs to wait for its
// children to finish and is immediately ready from its own
@@ -5939,7 +5934,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// to draw even if the children draw first or don't need to sync, so we start
// in WAITING state rather than READY.
mSyncState = SYNC_STATE_WAITING_FOR_DRAW;
+
+ if (mPrepareSyncSeqId > 0) {
+ // another prepareSync during existing sync (eg. reparented), so pre-emptively
+ // drop buffer (if exists). If the buffer hasn't been received yet, it will be
+ // dropped in finishDrawing.
+ ProtoLog.d(WM_DEBUG_SYNC_ENGINE, "Preparing to sync a window that was already in the"
+ + " sync, so try dropping buffer. win=%s", this);
+ dropBufferFrom(mSyncTransaction);
+ }
+
mSyncSeqId++;
+ mPrepareSyncSeqId = mSyncSeqId;
requestRedrawForSync();
return true;
}
@@ -5960,6 +5966,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mSyncState == SYNC_STATE_WAITING_FOR_DRAW && mRedrawForSyncReported) {
mClientWasDrawingForSync = true;
}
+ mPrepareSyncSeqId = 0;
+ if (cancel) {
+ // This is leaving sync so any buffers left in the sync have a chance of
+ // being applied out-of-order and can also block the buffer queue for this
+ // window. To prevent this, drop the buffer.
+ dropBufferFrom(mSyncTransaction);
+ }
super.finishSync(outMergedTransaction, cancel);
}
@@ -5981,6 +5994,17 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
.notifyStartingWindowDrawn(mActivityRecord);
}
+ final boolean syncActive = mPrepareSyncSeqId > 0;
+ final boolean syncStillPending = syncActive && mPrepareSyncSeqId > syncSeqId;
+ if (syncStillPending && postDrawTransaction != null) {
+ ProtoLog.d(WM_DEBUG_SYNC_ENGINE, "Got a buffer for request id=%d but latest request is"
+ + " id=%d. Since the buffer is out-of-date, drop it. win=%s", syncSeqId,
+ mPrepareSyncSeqId, this);
+ // sync is waiting for a newer seqId, so this buffer is obsolete and can be dropped
+ // to free up the buffer queue.
+ dropBufferFrom(postDrawTransaction);
+ }
+
final boolean hasSyncHandlers = executeDrawHandlers(postDrawTransaction, syncSeqId);
boolean skipLayout = false;
@@ -5990,15 +6014,18 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (asyncRotationController != null
&& asyncRotationController.handleFinishDrawing(this, postDrawTransaction)) {
// Consume the transaction because the controller will apply it with fade animation.
- // Layout is not needed because the window will be hidden by the fade leash. Clear
- // sync state because its sync transaction doesn't need to be merged to sync group.
+ // Layout is not needed because the window will be hidden by the fade leash.
postDrawTransaction = null;
skipLayout = true;
- clearSyncState();
- } else if (onSyncFinishedDrawing() && postDrawTransaction != null) {
- mSyncTransaction.merge(postDrawTransaction);
- // Consume the transaction because the sync group will merge it.
- postDrawTransaction = null;
+ } else if (syncActive) {
+ if (!syncStillPending) {
+ onSyncFinishedDrawing();
+ }
+ if (postDrawTransaction != null) {
+ mSyncTransaction.merge(postDrawTransaction);
+ // Consume the transaction because the sync group will merge it.
+ postDrawTransaction = null;
+ }
}
final boolean layoutNeeded =
@@ -6012,7 +6039,6 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
// We could be more subtle with Integer.MAX_VALUE and track a seqId in the timeout.
finishDrawing(null, Integer.MAX_VALUE);
mWmService.mH.removeMessages(WINDOW_STATE_BLAST_SYNC_TIMEOUT, this);
- if (!useBLASTSync()) return;
}
@Override
@@ -6049,6 +6075,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
if (mRedrawForSyncReported) {
return false;
}
+ // TODO(b/233286785): Remove mIsWallpaper once WallpaperService handles syncId of relayout.
+ if (mInRelayout && !mIsWallpaper) {
+ // The last sync seq id will return to the client, so there is no need to request the
+ // client to redraw.
+ return false;
+ }
return useBLASTSync();
}
@@ -6081,6 +6113,10 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
* See {@link WindowState#mDrawHandlers}
*/
void applyWithNextDraw(Consumer<SurfaceControl.Transaction> consumer) {
+ if (mSyncState != SYNC_STATE_NONE) {
+ Slog.w(TAG, "applyWithNextDraw with mSyncState=" + mSyncState + ", " + this
+ + ", " + Debug.getCallers(8));
+ }
mSyncSeqId++;
mDrawHandlers.add(new DrawHandler(mSyncSeqId, consumer));
@@ -6219,4 +6255,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
@WindowTraceLogLevel int logLevel) {
dumpDebug(proto, fieldId, logLevel);
}
+
+ public boolean cancelAndRedraw() {
+ // Cancel any draw requests during a sync.
+ return mPrepareSyncSeqId > 0;
+ }
}
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 5f43800bd9d5..4a5c4737cbbc 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -126,8 +126,10 @@ class WindowSurfaceController {
try {
transaction.hide(mSurfaceControl);
if (mAnimator.mIsWallpaper) {
+ final DisplayContent dc = mAnimator.mWin.getDisplayContent();
EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- mAnimator.mWin.getDisplayId(), 0 /* request hidden */);
+ dc.mDisplayId, 0 /* request hidden */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
}
} catch (RuntimeException e) {
Slog.w(TAG, "Exception hiding surface in " + this);
@@ -139,6 +141,12 @@ class WindowSurfaceController {
"Destroying surface %s called by %s", this, Debug.getCallers(8));
try {
if (mSurfaceControl != null) {
+ if (mAnimator.mIsWallpaper && !mAnimator.mWin.mWindowRemovalAllowed
+ && !mAnimator.mWin.mRemoveOnExit) {
+ // The wallpaper surface should have the same lifetime as its window.
+ Slog.e(TAG, "Unexpected removing wallpaper surface of " + mAnimator.mWin
+ + " by " + Debug.getCallers(8));
+ }
t.remove(mSurfaceControl);
}
} catch (RuntimeException e) {
@@ -260,8 +268,10 @@ class WindowSurfaceController {
setShown(true);
t.show(mSurfaceControl);
if (mAnimator.mIsWallpaper) {
+ final DisplayContent dc = mAnimator.mWin.getDisplayContent();
EventLog.writeEvent(EventLogTags.WM_WALLPAPER_SURFACE,
- mAnimator.mWin.getDisplayId(), 1 /* request shown */);
+ dc.mDisplayId, 1 /* request shown */,
+ String.valueOf(dc.mWallpaperController.getWallpaperTarget()));
}
return true;
}
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index d2e56faa0914..bbb21f8122c5 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -18,15 +18,12 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_MOVEMENT;
-import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainerChildProto.WINDOW_TOKEN;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -37,6 +34,7 @@ import static com.android.server.wm.WindowTokenProto.WAITING_TO_SHOW;
import static com.android.server.wm.WindowTokenProto.WINDOW_CONTAINER;
import android.annotation.CallSuper;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.res.Configuration;
import android.graphics.Rect;
@@ -363,11 +361,7 @@ class WindowToken extends WindowContainer<WindowState> {
@Override
void assignLayer(SurfaceControl.Transaction t, int layer) {
- if (windowType == TYPE_DOCK_DIVIDER) {
- // See {@link DisplayContent#mSplitScreenDividerAnchor}
- super.assignRelativeLayer(t,
- mDisplayContent.getDefaultTaskDisplayArea().getSplitScreenDividerAnchor(), 1);
- } else if (mRoundedCornerOverlay) {
+ if (mRoundedCornerOverlay) {
super.assignLayer(t, WindowManagerPolicy.COLOR_FADE_LAYER + 1);
} else {
super.assignLayer(t, layer);
@@ -454,8 +448,14 @@ class WindowToken extends WindowContainer<WindowState> {
if (mFixedRotationTransformState != null) {
mFixedRotationTransformState.disassociate(this);
}
+ // TODO(b/233855302): Remove TaskFragment override if the DisplayContent uses the same
+ // bounds for screenLayout calculation.
+ final Configuration overrideConfig = new Configuration(config);
+ overrideConfig.screenLayout = TaskFragment.computeScreenLayoutOverride(
+ overrideConfig.screenLayout, overrideConfig.screenWidthDp,
+ overrideConfig.screenHeightDp);
mFixedRotationTransformState = new FixedRotationTransformState(info, displayFrames,
- new Configuration(config), mDisplayContent.getRotation());
+ overrideConfig, mDisplayContent.getRotation());
mFixedRotationTransformState.mAssociatedTokens.add(this);
mDisplayContent.getDisplayPolicy().simulateLayoutDisplay(displayFrames);
onFixedRotationStatePrepared();
@@ -505,7 +505,7 @@ class WindowToken extends WindowContainer<WindowState> {
for (int i = mFixedRotationTransformState.mAssociatedTokens.size() - 1; i >= 0; i--) {
final ActivityRecord r =
mFixedRotationTransformState.mAssociatedTokens.get(i).asActivityRecord();
- if (r != null && r.isAnimating(TRANSITION | PARENTS)) {
+ if (r != null && r.isInTransition()) {
return true;
}
}
@@ -569,13 +569,12 @@ class WindowToken extends WindowContainer<WindowState> {
* the same rotation.
*/
@Nullable
- SurfaceControl getOrCreateFixedRotationLeash() {
+ SurfaceControl getOrCreateFixedRotationLeash(@NonNull SurfaceControl.Transaction t) {
if (!mTransitionController.isShellTransitionsEnabled()) return null;
final int rotation = getRelativeDisplayRotation();
if (rotation == Surface.ROTATION_0) return mFixedRotationTransformLeash;
if (mFixedRotationTransformLeash != null) return mFixedRotationTransformLeash;
- final SurfaceControl.Transaction t = getSyncTransaction();
final SurfaceControl leash = makeSurface().setContainerLayer()
.setParent(getParentSurfaceControl())
.setName(getSurfaceControl() + " - rotation-leash")
@@ -591,6 +590,15 @@ class WindowToken extends WindowContainer<WindowState> {
return mFixedRotationTransformLeash;
}
+ /**
+ * @return the leash which represents this window as if it was non-rotated. Will be null if
+ * there isn't one.
+ */
+ @Nullable
+ SurfaceControl getFixedRotationLeash() {
+ return mFixedRotationTransformLeash;
+ }
+
void removeFixedRotationLeash() {
if (mFixedRotationTransformLeash == null) return;
final SurfaceControl.Transaction t = getSyncTransaction();
@@ -664,6 +672,15 @@ class WindowToken extends WindowContainer<WindowState> {
}
}
+ @Override
+ boolean prepareSync() {
+ if (mDisplayContent != null && mDisplayContent.isRotationChanging()
+ && AsyncRotationController.canBeAsync(this)) {
+ return false;
+ }
+ return super.prepareSync();
+ }
+
@CallSuper
@Override
public void dumpDebug(ProtoOutputStream proto, long fieldId,
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 7895f708a5ca..77b361f0f6a5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12533,8 +12533,8 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
.setContentIntent(locationSettingsIntent)
.setAutoCancel(true)
.build();
- mInjector.getNotificationManager().notify(SystemMessage.NOTE_LOCATION_CHANGED,
- notification);
+ mHandler.post(() -> mInjector.getNotificationManager().notify(
+ SystemMessage.NOTE_LOCATION_CHANGED, notification));
}
private String getLocationChangedTitle() {
diff --git a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
index dcffc9e73c0e..f041fbd7bf90 100644
--- a/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
+++ b/services/smartspace/java/com/android/server/smartspace/SmartspacePerUserService.java
@@ -334,18 +334,7 @@ public class SmartspacePerUserService extends
@NonNull
private final SmartspaceConfig mSmartspaceConfig;
private final RemoteCallbackList<ISmartspaceCallback> mCallbacks =
- new RemoteCallbackList<ISmartspaceCallback>() {
- @Override
- public void onCallbackDied(ISmartspaceCallback callback) {
- if (DEBUG) {
- Slog.d(TAG, "Binder died for session Id=" + mSessionId
- + " and callback=" + callback.asBinder());
- }
- if (mCallbacks.getRegisteredCallbackCount() == 0) {
- destroy();
- }
- }
- };
+ new RemoteCallbackList<>();
SmartspaceSessionInfo(
@NonNull final SmartspaceSessionId id,
diff --git a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
index 494246491e47..80de823a6a1b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/alarm/AlarmManagerServiceTest.java
@@ -722,6 +722,25 @@ public class AlarmManagerServiceTest {
}
@Test
+ public void testAlarmBroadcastOption() throws Exception {
+ final long triggerTime = mNowElapsedTest + 5000;
+ final PendingIntent alarmPi = getNewMockPendingIntent();
+ setTestAlarm(ELAPSED_REALTIME_WAKEUP, triggerTime, alarmPi);
+
+ mNowElapsedTest = mTestTimer.getElapsed();
+ mTestTimer.expire();
+
+ final ArgumentCaptor<PendingIntent.OnFinished> onFinishedCaptor =
+ ArgumentCaptor.forClass(PendingIntent.OnFinished.class);
+ final ArgumentCaptor<Bundle> optionsCaptor = ArgumentCaptor.forClass(Bundle.class);
+ verify(alarmPi).send(eq(mMockContext), eq(0), any(Intent.class),
+ onFinishedCaptor.capture(), any(Handler.class), isNull(),
+ optionsCaptor.capture());
+ assertTrue(optionsCaptor.getValue()
+ .getBoolean(BroadcastOptions.KEY_ALARM_BROADCAST, false));
+ }
+
+ @Test
public void testUpdateConstants() {
setDeviceConfigLong(KEY_MIN_FUTURITY, 5);
setDeviceConfigLong(KEY_MIN_INTERVAL, 10);
@@ -2338,10 +2357,15 @@ public class AlarmManagerServiceTest {
mBinder.set(TEST_CALLING_PACKAGE, RTC_WAKEUP, 1234, WINDOW_EXACT, 0, 0,
alarmPi, null, null, null, alarmClock);
+ final ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
verify(mService).setImpl(eq(RTC_WAKEUP), eq(1234L), eq(WINDOW_EXACT), eq(0L),
eq(alarmPi), isNull(), isNull(), eq(FLAG_STANDALONE | FLAG_WAKE_FROM_IDLE),
- isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE), isNull(),
- eq(EXACT_ALLOW_REASON_COMPAT));
+ isNull(), eq(alarmClock), eq(TEST_CALLING_UID), eq(TEST_CALLING_PACKAGE),
+ bundleCaptor.capture(), eq(EXACT_ALLOW_REASON_COMPAT));
+
+ final BroadcastOptions idleOptions = new BroadcastOptions(bundleCaptor.getValue());
+ final int type = idleOptions.getTemporaryAppAllowlistType();
+ assertEquals(TEMPORARY_ALLOWLIST_TYPE_FOREGROUND_SERVICE_ALLOWED, type);
}
@Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 09565b42e515..d675b0aa4973 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -19,6 +19,7 @@ package com.android.server.app;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@@ -49,6 +50,7 @@ import android.content.res.XmlResourceParser;
import android.hardware.power.Mode;
import android.os.Bundle;
import android.os.PowerManagerInternal;
+import android.os.UserManager;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.provider.DeviceConfig;
@@ -69,6 +71,8 @@ import org.mockito.Mock;
import org.mockito.MockitoSession;
import org.mockito.quality.Strictness;
+import java.io.File;
+import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -79,7 +83,7 @@ import java.util.function.Supplier;
@Presubmit
public class GameManagerServiceTests {
@Mock MockContext mMockContext;
- private static final String TAG = "GameServiceTests";
+ private static final String TAG = "GameManagerServiceTests";
private static final String PACKAGE_NAME_INVALID = "com.android.app";
private static final int USER_ID_1 = 1001;
private static final int USER_ID_2 = 1002;
@@ -91,6 +95,8 @@ public class GameManagerServiceTests {
private PackageManager mMockPackageManager;
@Mock
private PowerManagerInternal mMockPowerManager;
+ @Mock
+ private UserManager mMockUserManager;
// Stolen from ConnectivityServiceTest.MockContext
class MockContext extends ContextWrapper {
@@ -150,6 +156,15 @@ public class GameManagerServiceTests {
public PackageManager getPackageManager() {
return mMockPackageManager;
}
+
+ @Override
+ public Object getSystemService(String name) {
+ switch (name) {
+ case Context.USER_SERVICE:
+ return mMockUserManager;
+ }
+ throw new UnsupportedOperationException("Couldn't find system service: " + name);
+ }
}
@Before
@@ -198,6 +213,19 @@ public class GameManagerServiceTests {
mTestLooper.dispatchAll();
}
+ private void switchUser(GameManagerService gameManagerService, int from, int to) {
+ UserInfo userInfoFrom = new UserInfo(from, "name", 0);
+ UserInfo userInfoTo = new UserInfo(to, "name", 0);
+ gameManagerService.onUserSwitching(/* from */ new SystemService.TargetUser(userInfoFrom),
+ /* to */ new SystemService.TargetUser(userInfoTo));
+ mTestLooper.dispatchAll();
+ }
+
+ private void mockManageUsersGranted() {
+ mMockContext.setPermission(Manifest.permission.MANAGE_USERS,
+ PackageManager.PERMISSION_GRANTED);
+ }
+
private void mockModifyGameModeGranted() {
mMockContext.setPermission(Manifest.permission.MANAGE_GAME_MODE,
PackageManager.PERMISSION_GRANTED);
@@ -819,6 +847,7 @@ public class GameManagerServiceTests {
GameManagerService gameManagerService = new GameManagerService(
mMockContext, mTestLooper.getLooper());
startUser(gameManagerService, USER_ID_1);
+
gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
GameManager.GAME_MODE_PERFORMANCE, "120", "0.3");
gameManagerService.setGameModeConfigOverride(mPackageName, USER_ID_1,
@@ -1246,4 +1275,133 @@ public class GameManagerServiceTests {
public void testSetGameStateNotLoading() {
setGameState(false);
}
+
+ private List<String> readGameModeInterventionList() throws Exception {
+ final File interventionFile = new File(InstrumentationRegistry.getContext().getFilesDir(),
+ "system/game_mode_intervention.list");
+ assertNotNull(interventionFile);
+ List<String> output = Files.readAllLines(interventionFile.toPath());
+ return output;
+ }
+
+ private void mockInterventionListForMultipleUsers() {
+ final String[] packageNames = new String[] {"com.android.app0",
+ "com.android.app1", "com.android.app2"};
+
+ final ApplicationInfo[] applicationInfos = new ApplicationInfo[3];
+ final PackageInfo[] pis = new PackageInfo[3];
+ for (int i = 0; i < 3; ++i) {
+ applicationInfos[i] = new ApplicationInfo();
+ applicationInfos[i].category = ApplicationInfo.CATEGORY_GAME;
+ applicationInfos[i].packageName = packageNames[i];
+
+ pis[i] = new PackageInfo();
+ pis[i].packageName = packageNames[i];
+ pis[i].applicationInfo = applicationInfos[i];
+ }
+
+ final List<PackageInfo> userOnePackages = new ArrayList<>();
+ final List<PackageInfo> userTwoPackages = new ArrayList<>();
+ userOnePackages.add(pis[1]);
+ userTwoPackages.add(pis[0]);
+ userTwoPackages.add(pis[2]);
+
+ final List<UserInfo> userInfos = new ArrayList<>(2);
+ userInfos.add(new UserInfo());
+ userInfos.add(new UserInfo());
+ userInfos.get(0).id = USER_ID_1;
+ userInfos.get(1).id = USER_ID_2;
+
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_1)))
+ .thenReturn(userOnePackages);
+ when(mMockPackageManager.getInstalledPackagesAsUser(anyInt(), eq(USER_ID_2)))
+ .thenReturn(userTwoPackages);
+ when(mMockUserManager.getUsers()).thenReturn(userInfos);
+ }
+
+ @Test
+ public void testVerifyInterventionList() throws Exception {
+ mockDeviceConfigAll();
+ mockInterventionListForMultipleUsers();
+ mockManageUsersGranted();
+ mockModifyGameModeGranted();
+ final Context context = InstrumentationRegistry.getContext();
+ GameManagerService gameManagerService =
+ new GameManagerService(mMockContext,
+ mTestLooper.getLooper(),
+ context.getFilesDir());
+ startUser(gameManagerService, USER_ID_1);
+ startUser(gameManagerService, USER_ID_2);
+
+ gameManagerService.setGameModeConfigOverride("com.android.app0", USER_ID_2,
+ GameManager.GAME_MODE_PERFORMANCE, "120", "0.6");
+ gameManagerService.setGameModeConfigOverride("com.android.app2", USER_ID_2,
+ GameManager.GAME_MODE_BATTERY, "60", "0.5");
+ mTestLooper.dispatchAll();
+
+ /* Expected fileOutput (order may vary)
+ com.android.app2 <UID> 0 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.5,fps=60
+ com.android.app1 <UID> 1 2 angle=0,scaling=0.5,fps=90 3 angle=0,scaling=0.7,fps=30
+ com.android.app0 <UID> 0 2 angle=0,scaling=0.6,fps=120 3 angle=0,scaling=0.7,fps=30
+
+ The current game mode would only be set to non-zero if the current user have that game
+ installed.
+ */
+
+ List<String> fileOutput = readGameModeInterventionList();
+ assertEquals(fileOutput.size(), 3);
+
+ String[] splitLine = fileOutput.get(0).split("\\s+");
+ assertEquals(splitLine[0], "com.android.app2");
+ assertEquals(splitLine[2], "3");
+ assertEquals(splitLine[3], "2");
+ assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
+ assertEquals(splitLine[5], "3");
+ assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
+ splitLine = fileOutput.get(1).split("\\s+");
+ assertEquals(splitLine[0], "com.android.app1");
+ assertEquals(splitLine[2], "0");
+ assertEquals(splitLine[3], "2");
+ assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
+ assertEquals(splitLine[5], "3");
+ assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
+ splitLine = fileOutput.get(2).split("\\s+");
+ assertEquals(splitLine[0], "com.android.app0");
+ assertEquals(splitLine[2], "2");
+ assertEquals(splitLine[3], "2");
+ assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
+ assertEquals(splitLine[5], "3");
+ assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
+
+ switchUser(gameManagerService, USER_ID_2, USER_ID_1);
+ gameManagerService.setGameMode("com.android.app1",
+ GameManager.GAME_MODE_BATTERY, USER_ID_1);
+ mTestLooper.dispatchAll();
+
+ fileOutput = readGameModeInterventionList();
+ assertEquals(fileOutput.size(), 3);
+
+ splitLine = fileOutput.get(0).split("\\s+");
+ assertEquals(splitLine[0], "com.android.app2");
+ assertEquals(splitLine[2], "0");
+ assertEquals(splitLine[3], "2");
+ assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
+ assertEquals(splitLine[5], "3");
+ assertEquals(splitLine[6], "angle=0,scaling=0.5,fps=60");
+ splitLine = fileOutput.get(1).split("\\s+");
+ assertEquals(splitLine[0], "com.android.app1");
+ assertEquals(splitLine[2], "3");
+ assertEquals(splitLine[3], "2");
+ assertEquals(splitLine[4], "angle=0,scaling=0.5,fps=90");
+ assertEquals(splitLine[5], "3");
+ assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
+ splitLine = fileOutput.get(2).split("\\s+");
+ assertEquals(splitLine[0], "com.android.app0");
+ assertEquals(splitLine[2], "0");
+ assertEquals(splitLine[3], "2");
+ assertEquals(splitLine[4], "angle=0,scaling=0.6,fps=120");
+ assertEquals(splitLine[5], "3");
+ assertEquals(splitLine[6], "angle=0,scaling=0.7,fps=30");
+
+ }
}
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 52d0494bd9f0..2f61908aa63d 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -312,4 +312,71 @@ public class BatteryControllerTest {
assertFalse(jobLateFg.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
assertFalse(jobLateFgLow.isConstraintSatisfied(JobStatus.CONSTRAINT_CHARGING));
}
+
+ @Test
+ public void testControllerOnlyTracksPowerJobs() {
+ JobStatus batteryJob = createJobStatus("testControllerOnlyTracksPowerJobs",
+ SOURCE_PACKAGE, mSourceUid,
+ createBaseJobInfoBuilder(1).setRequiresBatteryNotLow(true).build());
+ JobStatus chargingJob = createJobStatus("testControllerOnlyTracksPowerJobs",
+ SOURCE_PACKAGE, mSourceUid,
+ createBaseJobInfoBuilder(2).setRequiresCharging(true).build());
+ JobStatus bothPowerJob = createJobStatus("testControllerOnlyTracksPowerJobs",
+ SOURCE_PACKAGE, mSourceUid,
+ createBaseJobInfoBuilder(3)
+ .setRequiresCharging(true)
+ .setRequiresBatteryNotLow(true)
+ .build());
+ JobStatus unrelatedJob = createJobStatus("testControllerOnlyTracksPowerJobs",
+ SOURCE_PACKAGE, mSourceUid, createBaseJobInfoBuilder(4).build());
+
+ // Follow the lifecycle of tracking
+ // Start tracking
+ trackJobs(batteryJob, chargingJob, bothPowerJob, unrelatedJob);
+ final ArraySet<JobStatus> trackedJobs = mBatteryController.getTrackedJobs();
+ final ArraySet<JobStatus> topStartedJobs = mBatteryController.getTopStartedJobs();
+ assertTrue(trackedJobs.contains(batteryJob));
+ assertTrue(trackedJobs.contains(chargingJob));
+ assertTrue(trackedJobs.contains(bothPowerJob));
+ assertFalse(trackedJobs.contains(unrelatedJob));
+ assertFalse(topStartedJobs.contains(batteryJob));
+ assertFalse(topStartedJobs.contains(chargingJob));
+ assertFalse(topStartedJobs.contains(bothPowerJob));
+ assertFalse(topStartedJobs.contains(unrelatedJob));
+
+ // Procstate change shouldn't affect anything
+ setUidBias(mSourceUid, JobInfo.BIAS_TOP_APP);
+ assertTrue(trackedJobs.contains(batteryJob));
+ assertTrue(trackedJobs.contains(chargingJob));
+ assertTrue(trackedJobs.contains(bothPowerJob));
+ assertFalse(trackedJobs.contains(unrelatedJob));
+ assertFalse(topStartedJobs.contains(batteryJob));
+ assertFalse(topStartedJobs.contains(chargingJob));
+ assertFalse(topStartedJobs.contains(bothPowerJob));
+ assertFalse(topStartedJobs.contains(unrelatedJob));
+
+ // Job starts running
+ mBatteryController.prepareForExecutionLocked(batteryJob);
+ mBatteryController.prepareForExecutionLocked(chargingJob);
+ mBatteryController.prepareForExecutionLocked(bothPowerJob);
+ mBatteryController.prepareForExecutionLocked(unrelatedJob);
+ assertTrue(topStartedJobs.contains(batteryJob));
+ assertTrue(topStartedJobs.contains(chargingJob));
+ assertTrue(topStartedJobs.contains(bothPowerJob));
+ assertFalse(topStartedJobs.contains(unrelatedJob));
+
+ // Job cleanup
+ mBatteryController.maybeStopTrackingJobLocked(batteryJob, null, false);
+ mBatteryController.maybeStopTrackingJobLocked(chargingJob, null, false);
+ mBatteryController.maybeStopTrackingJobLocked(bothPowerJob, null, false);
+ mBatteryController.maybeStopTrackingJobLocked(unrelatedJob, null, false);
+ assertFalse(trackedJobs.contains(batteryJob));
+ assertFalse(trackedJobs.contains(chargingJob));
+ assertFalse(trackedJobs.contains(bothPowerJob));
+ assertFalse(trackedJobs.contains(unrelatedJob));
+ assertFalse(topStartedJobs.contains(batteryJob));
+ assertFalse(topStartedJobs.contains(chargingJob));
+ assertFalse(topStartedJobs.contains(bothPowerJob));
+ assertFalse(topStartedJobs.contains(unrelatedJob));
+ }
}
diff --git a/services/tests/servicestests/AndroidTest.xml b/services/tests/servicestests/AndroidTest.xml
index 158bd39a4fd0..7092092fef25 100644
--- a/services/tests/servicestests/AndroidTest.xml
+++ b/services/tests/servicestests/AndroidTest.xml
@@ -50,5 +50,6 @@
<option name="package" value="com.android.frameworks.servicestests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<option name="hidden-api-checks" value="false"/>
+ <option name="exclude-annotation" value="androidx.test.filters.FlakyTest" />
</test>
</configuration>
diff --git a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
index 91c45b4f8b60..adcbe6bb12c0 100644
--- a/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/audio/AudioServiceTest.java
@@ -15,12 +15,18 @@
*/
package com.android.server.audio;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import android.app.AppOpsManager;
import android.content.Context;
+import android.media.AudioSystem;
import android.os.Looper;
import android.os.UserHandle;
import android.util.Log;
@@ -33,6 +39,7 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mock;
import org.mockito.Spy;
@MediumTest
@@ -46,6 +53,7 @@ public class AudioServiceTest {
private AudioSystemAdapter mAudioSystem;
@Spy private SystemServerAdapter mSpySystemServer;
private SettingsAdapter mSettingsAdapter;
+ @Mock private AppOpsManager mMockAppOpsManager;
// the class being unit-tested here
private AudioService mAudioService;
@@ -61,8 +69,11 @@ public class AudioServiceTest {
mAudioSystem = new NoOpAudioSystemAdapter();
mSpySystemServer = spy(new NoOpSystemServerAdapter());
mSettingsAdapter = new NoOpSettingsAdapter();
+ mMockAppOpsManager = mock(AppOpsManager.class);
+ when(mMockAppOpsManager.noteOp(anyInt(), anyInt(), anyString(), anyString(), anyString()))
+ .thenReturn(AppOpsManager.MODE_ALLOWED);
mAudioService = new AudioService(mContext, mAudioSystem, mSpySystemServer,
- mSettingsAdapter, null);
+ mSettingsAdapter, null, mMockAppOpsManager);
}
/**
@@ -113,4 +124,33 @@ public class AudioServiceTest {
reset(mSpySystemServer);
}
}
+
+ @Test
+ public void testRingNotifAlias() throws Exception {
+ Log.i(TAG, "running testRingNotifAlias");
+ Assert.assertNotNull(mAudioService);
+ // TODO add initialization message that can be caught here instead of sleeping
+ Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS); // wait for full AudioService initialization
+
+ // test with aliasing RING and NOTIFICATION
+ mAudioService.setNotifAliasRingForTest(true);
+ final int ringMaxVol = mAudioService.getStreamMaxVolume(AudioSystem.STREAM_RING);
+ final int ringMinVol = mAudioService.getStreamMinVolume(AudioSystem.STREAM_RING);
+ final int ringVol = ringMinVol + 1;
+ // set a value for NOTIFICATION so it's not at the target test value (ringMaxVol)
+ mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION,
+ ringVol, 0, "bla");
+ mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringMaxVol, 0, "bla");
+ Thread.sleep(MAX_MESSAGE_HANDLING_DELAY_MS);
+ Assert.assertEquals(ringMaxVol,
+ mAudioService.getStreamVolume(AudioSystem.STREAM_NOTIFICATION));
+
+ // test with no aliasing between RING and NOTIFICATION
+ mAudioService.setNotifAliasRingForTest(false);
+ mAudioService.setStreamVolume(AudioSystem.STREAM_RING, ringVol, 0, "bla");
+ mAudioService.setStreamVolume(AudioSystem.STREAM_NOTIFICATION, ringMaxVol, 0, "bla");
+ Assert.assertEquals(ringVol, mAudioService.getStreamVolume(AudioSystem.STREAM_RING));
+ Assert.assertEquals(ringMaxVol, mAudioService.getStreamVolume(
+ AudioSystem.STREAM_NOTIFICATION));
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 1a49f8aa2e6a..ea1e49d8748a 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -43,8 +43,10 @@ import android.hardware.fingerprint.Fingerprint;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.ISidefpsController;
import android.hardware.fingerprint.IUdfpsOverlayController;
+import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
+import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import android.testing.TestableContext;
@@ -128,6 +130,8 @@ public class FingerprintAuthenticationClientTest {
@Captor
private ArgumentCaptor<Consumer<OperationContext>> mContextInjector;
+ private TestLooper mLooper = new TestLooper();
+
@Rule
public final MockitoRule mockito = MockitoJUnit.rule();
@@ -233,6 +237,9 @@ public class FingerprintAuthenticationClientTest {
client.start(mCallback);
client.onAuthenticated(new Fingerprint("name", 2 /* enrollmentId */, SENSOR_ID),
true /* authenticated */, new ArrayList<>());
+
+ mLooper.moveTimeForward(10);
+ mLooper.dispatchAll();
verify(mLuxProbe).destroy();
client.onAcquired(2, 0);
@@ -309,9 +316,58 @@ public class FingerprintAuthenticationClientTest {
client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
true /* authenticated */, new ArrayList<>());
+ mLooper.moveTimeForward(10);
+ mLooper.dispatchAll();
verify(mCancellationSignal).cancel();
}
+ @Test
+ public void fingerprintPowerIgnoresAuthInWindow() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPowerPressed();
+ client.onAuthenticated(new Fingerprint("friendly", 1 /* fingerId */, 2 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void fingerprintAuthIgnoredWaitingForPower() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onAuthenticated(new Fingerprint("friendly", 3 /* fingerId */, 4 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ client.onPowerPressed();
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(false));
+ }
+
+ @Test
+ public void fingerprintAuthSucceedsAfterPowerWindow() throws Exception {
+ when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+ final FingerprintAuthenticationClient client = createClient(1);
+ client.start(mCallback);
+ client.onPowerPressed();
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+ client.onAuthenticated(new Fingerprint("friendly", 4 /* fingerId */, 5 /* deviceId */),
+ true /* authenticated */, new ArrayList<>());
+ mLooper.moveTimeForward(1000);
+ mLooper.dispatchAll();
+
+ verify(mCallback).onClientFinished(any(), eq(true));
+ }
+
private FingerprintAuthenticationClient createClient() throws RemoteException {
return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
}
@@ -336,7 +392,8 @@ public class FingerprintAuthenticationClientTest {
9 /* sensorId */, mBiometricLogger, mBiometricContext,
true /* isStrongBiometric */,
null /* taskStackListener */, mLockoutCache,
- mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps) {
+ mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication, mSensorProps,
+ new Handler(mLooper.getLooper())) {
@Override
protected ActivityTaskManager getActivityTaskManager() {
return mActivityTaskManager;
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index f242fda15f06..c80547ce61c0 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -213,7 +213,7 @@ public class VirtualDeviceManagerServiceTest {
mContext.getSystemService(WindowManager.class), threadVerifier);
mAssociationInfo = new AssociationInfo(1, 0, null,
- MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0);
+ MacAddress.BROADCAST_ADDRESS, "", null, true, false, false, 0, 0);
VirtualDeviceParams params = new VirtualDeviceParams
.Builder()
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
index fe3034dc569d..038cbc032375 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/DeviceStateManagerServiceTest.java
@@ -76,12 +76,15 @@ public final class DeviceStateManagerServiceTest {
private TestDeviceStatePolicy mPolicy;
private TestDeviceStateProvider mProvider;
private DeviceStateManagerService mService;
+ private TestSystemPropertySetter mSysPropSetter;
@Before
public void setup() {
mProvider = new TestDeviceStateProvider();
mPolicy = new TestDeviceStatePolicy(mProvider);
- mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy);
+ mSysPropSetter = new TestSystemPropertySetter();
+ mService = new DeviceStateManagerService(InstrumentationRegistry.getContext(), mPolicy,
+ mSysPropSetter);
// Necessary to allow us to check for top app process id in tests
mService.mActivityTaskManagerInternal = mock(ActivityTaskManagerInternal.class);
@@ -107,6 +110,8 @@ public final class DeviceStateManagerServiceTest {
public void baseStateChanged() {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
@@ -115,6 +120,8 @@ public final class DeviceStateManagerServiceTest {
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
@@ -128,6 +135,8 @@ public final class DeviceStateManagerServiceTest {
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
@@ -136,6 +145,8 @@ public final class DeviceStateManagerServiceTest {
flushHandler();
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
@@ -157,6 +168,8 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
@@ -170,6 +183,8 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
@@ -182,6 +197,8 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE);
@@ -193,6 +210,8 @@ public final class DeviceStateManagerServiceTest {
// supported.
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE);
@@ -211,6 +230,8 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE);
@@ -223,6 +244,8 @@ public final class DeviceStateManagerServiceTest {
// supported.
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertThat(mService.getSupportedStates()).asList().containsExactly(DEFAULT_DEVICE_STATE,
OTHER_DEVICE_STATE);
@@ -315,6 +338,8 @@ public final class DeviceStateManagerServiceTest {
TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
@@ -333,6 +358,8 @@ public final class DeviceStateManagerServiceTest {
TestDeviceStateManagerCallback.STATUS_CANCELED);
// Committed state is set back to the requested state once the override is cleared.
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertFalse(mService.getOverrideState().isPresent());
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
@@ -385,9 +412,10 @@ public final class DeviceStateManagerServiceTest {
// callback.
assertEquals(callback.getLastNotifiedStatus(secondRequestToken),
TestDeviceStateManagerCallback.STATUS_UNKNOWN);
-
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
@@ -397,6 +425,8 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
@@ -412,6 +442,8 @@ public final class DeviceStateManagerServiceTest {
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getPendingState(), Optional.empty());
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
DEFAULT_DEVICE_STATE.getIdentifier());
@@ -458,6 +490,8 @@ public final class DeviceStateManagerServiceTest {
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
@@ -471,6 +505,8 @@ public final class DeviceStateManagerServiceTest {
// Committed state is set back to the requested state once the override is cleared.
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
assertEquals(mService.getBaseState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
assertFalse(mService.getOverrideState().isPresent());
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
OTHER_DEVICE_STATE.getIdentifier());
@@ -495,6 +531,8 @@ public final class DeviceStateManagerServiceTest {
TestDeviceStateManagerCallback.STATUS_ACTIVE);
// Committed state changes as there is a requested override.
assertEquals(mService.getCommittedState(), Optional.of(OTHER_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ OTHER_DEVICE_STATE.getIdentifier() + ":" + OTHER_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertEquals(mService.getOverrideState().get(), OTHER_DEVICE_STATE);
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
@@ -509,6 +547,8 @@ public final class DeviceStateManagerServiceTest {
// Committed state is set back to the requested state as the override state is no longer
// supported.
assertEquals(mService.getCommittedState(), Optional.of(DEFAULT_DEVICE_STATE));
+ assertEquals(mSysPropSetter.getValue(),
+ DEFAULT_DEVICE_STATE.getIdentifier() + ":" + DEFAULT_DEVICE_STATE.getName());
assertEquals(mService.getBaseState(), Optional.of(DEFAULT_DEVICE_STATE));
assertFalse(mService.getOverrideState().isPresent());
assertEquals(mPolicy.getMostRecentRequestedStateToConfigure(),
@@ -673,4 +713,18 @@ public final class DeviceStateManagerServiceTest {
return mLastNotifiedStatus.getOrDefault(requestToken, STATUS_UNKNOWN);
}
}
+
+ private static final class TestSystemPropertySetter implements
+ DeviceStateManagerService.SystemPropertySetter {
+ private String mValue;
+
+ @Override
+ public void setDebugTracingDeviceStateProperty(String value) {
+ mValue = value;
+ }
+
+ public String getValue() {
+ return mValue;
+ }
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
index 0ed90d27db99..6a6cd6c914a2 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessThrottlerTest.java
@@ -16,13 +16,11 @@
package com.android.server.display;
-import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.any;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
@@ -34,17 +32,18 @@ import android.os.IThermalEventListener;
import android.os.IThermalService;
import android.os.Message;
import android.os.PowerManager;
-import android.os.Temperature.ThrottlingStatus;
import android.os.Temperature;
+import android.os.Temperature.ThrottlingStatus;
import android.os.test.TestLooper;
import android.platform.test.annotations.Presubmit;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.internal.os.BackgroundThread;
import com.android.server.display.BrightnessThrottler.Injector;
-import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData;
+import com.android.server.display.DisplayDeviceConfig.BrightnessThrottlingData.ThrottlingLevel;
import org.junit.Before;
import org.junit.Test;
@@ -55,7 +54,6 @@ import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
@SmallTest
@@ -70,6 +68,8 @@ public class BrightnessThrottlerTest {
@Mock IThermalService mThermalServiceMock;
@Mock Injector mInjectorMock;
+ DisplayModeDirectorTest.FakeDeviceConfig mDeviceConfigFake;
+
@Captor ArgumentCaptor<IThermalEventListener> mThermalEventListenerCaptor;
@Before
@@ -83,6 +83,8 @@ public class BrightnessThrottlerTest {
return true;
}
});
+ mDeviceConfigFake = new DisplayModeDirectorTest.FakeDeviceConfig();
+ when(mInjectorMock.getDeviceConfig()).thenReturn(mDeviceConfigFake);
}
@@ -292,6 +294,170 @@ public class BrightnessThrottlerTest {
assertEquals(BrightnessInfo.BRIGHTNESS_MAX_REASON_NONE, throttler.getBrightnessMaxReason());
}
+ @Test public void testUpdateThrottlingData() throws Exception {
+ // Initialise brightness throttling levels
+ // Ensure that they are overridden by setting the data through device config.
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.4");
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.4f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // Update thresholds
+ // This data is equivalent to the string "123,1,critical,0.8", passed below
+ final ThrottlingLevel newLevel = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.8f);
+ // Set new (valid) data from device config
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,0.8");
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(newLevel.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(newLevel.brightness, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ }
+
+ @Test public void testInvalidThrottlingStrings() throws Exception {
+ // Initialise brightness throttling levels
+ // Ensure that they are not overridden by invalid data through device config.
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // None of these are valid so shouldn't override the original data
+ mDeviceConfigFake.setBrightnessThrottlingData("321,1,critical,0.4"); // Not the current id
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,0,critical,0.4"); // Incorrect number
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,2,critical,0.4"); // Incorrect number
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,invalid,0.4"); // Invalid level
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,none"); // Invalid brightness
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("123,1,critical,-3"); // Invalid brightness
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData("invalid string"); // Invalid format
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ mDeviceConfigFake.setBrightnessThrottlingData(""); // Invalid format
+ testThrottling(throttler, listener, PowerManager.BRIGHTNESS_MAX, 0.25f);
+ }
+
+ private void testThrottling(BrightnessThrottler throttler, IThermalEventListener listener,
+ float tooLowCap, float tooHighCap) throws Exception {
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ tooHighCap);
+
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(tooLowCap, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(level.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(tooHighCap, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ }
+
+ @Test public void testMultipleConfigPoints() throws Exception {
+ // Initialise brightness throttling levels
+ final ThrottlingLevel level = new ThrottlingLevel(PowerManager.THERMAL_STATUS_CRITICAL,
+ 0.25f);
+ List<ThrottlingLevel> levels = new ArrayList<>();
+ levels.add(level);
+ final BrightnessThrottlingData data = BrightnessThrottlingData.create(levels);
+
+ // These are identical to the string set below
+ final ThrottlingLevel levelSevere = new ThrottlingLevel(PowerManager.THERMAL_STATUS_SEVERE,
+ 0.9f);
+ final ThrottlingLevel levelCritical = new ThrottlingLevel(
+ PowerManager.THERMAL_STATUS_CRITICAL, 0.5f);
+ final ThrottlingLevel levelEmergency = new ThrottlingLevel(
+ PowerManager.THERMAL_STATUS_EMERGENCY, 0.1f);
+
+ mDeviceConfigFake.setBrightnessThrottlingData(
+ "123,3,severe,0.9,critical,0.5,emergency,0.1");
+ final BrightnessThrottler throttler = createThrottlerSupported(data);
+
+ verify(mThermalServiceMock).registerThermalEventListenerWithType(
+ mThermalEventListenerCaptor.capture(), eq(Temperature.TYPE_SKIN));
+ final IThermalEventListener listener = mThermalEventListenerCaptor.getValue();
+
+ // Ensure that the multiple levels set via the string through the device config correctly
+ // override the original display device config ones.
+
+ // levelSevere
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(PowerManager.BRIGHTNESS_MAX, throttler.getBrightnessCap(), 0f);
+ assertFalse(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelSevere.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // levelCritical
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(0.9f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelCritical.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ //levelEmergency
+ // Set status too low to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus - 1));
+ mTestLooper.dispatchAll();
+ assertEquals(0.5f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+
+ // Set status high enough to trigger throttling
+ listener.notifyThrottling(getSkinTemp(levelEmergency.thermalStatus));
+ mTestLooper.dispatchAll();
+ assertEquals(0.1f, throttler.getBrightnessCap(), 0f);
+ assertTrue(throttler.isThrottled());
+ }
+
private void assertThrottlingLevelsEquals(
List<ThrottlingLevel> expected,
List<ThrottlingLevel> actual) {
@@ -307,12 +473,13 @@ public class BrightnessThrottlerTest {
}
private BrightnessThrottler createThrottlerUnsupported() {
- return new BrightnessThrottler(mInjectorMock, mHandler, null, () -> {});
+ return new BrightnessThrottler(mInjectorMock, mHandler, mHandler, null, () -> {}, null);
}
private BrightnessThrottler createThrottlerSupported(BrightnessThrottlingData data) {
assertNotNull(data);
- return new BrightnessThrottler(mInjectorMock, mHandler, data, () -> {});
+ return new BrightnessThrottler(mInjectorMock, mHandler, BackgroundThread.getHandler(),
+ data, () -> {}, "123");
}
private Temperature getSkinTemp(@ThrottlingStatus int status) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
new file mode 100644
index 000000000000..793930395daa
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public final class DisplayDeviceConfigTest {
+ private DisplayDeviceConfig mDisplayDeviceConfig;
+ @Mock
+ private Context mContext;
+
+ @Mock
+ private Resources mResources;
+
+ @Before
+ public void setUp() throws IOException {
+ MockitoAnnotations.initMocks(this);
+ when(mContext.getResources()).thenReturn(mResources);
+ mockDeviceConfigs();
+ try {
+ Path tempFile = Files.createTempFile("display_config", ".tmp");
+ Files.write(tempFile, getContent().getBytes(StandardCharsets.UTF_8));
+ mDisplayDeviceConfig = new DisplayDeviceConfig(mContext);
+ mDisplayDeviceConfig.initFromFile(tempFile.toFile());
+ } catch (IOException e) {
+ throw new IOException("Failed to setup the display device config.", e);
+ }
+ }
+
+ @Test
+ public void testConfigValues() {
+ assertEquals(mDisplayDeviceConfig.getAmbientHorizonLong(), 5000);
+ assertEquals(mDisplayDeviceConfig.getAmbientHorizonShort(), 50);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampDecreaseMaxMillis(), 3000);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampIncreaseMaxMillis(), 2000);
+ assertEquals(mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), 10.0f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), 2.0f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastDecrease(), 0.01f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampFastIncrease(), 0.02f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowIncrease(), 0.04f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessRampSlowDecrease(), 0.03f, 0.0f);
+ assertEquals(mDisplayDeviceConfig.getBrightnessDefault(), 0.5f, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getBrightness(), new float[]{0.0f, 0.62f, 1.0f},
+ 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getNits(), new float[]{2.0f, 500.0f, 800.0f}, 0.0f);
+ assertArrayEquals(mDisplayDeviceConfig.getBacklight(), new float[]{0.0f, 0.62f, 1.0f},
+ 0.0f);
+ assertEquals(mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), 0.001, 0.000001f);
+ assertEquals(mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), 0.002, 0.000001f);
+
+ // Todo(brup): Add asserts for BrightnessThrottlingData, DensityMapping,
+ // HighBrightnessModeData AmbientLightSensor, RefreshRateLimitations and ProximitySensor.
+ // Also add test for the case where optional display configs are null
+ }
+
+ private String getContent() {
+ return "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>\n"
+ + "<displayConfiguration>\n"
+ + "<screenBrightnessMap>\n"
+ + "<point>\n"
+ + "<value>0.0</value>\n"
+ + "<nits>2.0</nits>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<value>0.62</value>\n"
+ + "<nits>500.0</nits>\n"
+ + "</point>\n"
+ + "<point>\n"
+ + "<value>1.0</value>\n"
+ + "<nits>800.0</nits>\n"
+ + "</point>\n"
+ + "</screenBrightnessMap>\n"
+ + "<highBrightnessMode enabled=\"true\">\n"
+ + "<transitionPoint>0.62</transitionPoint>\n"
+ + "<minimumLux>10000</minimumLux>\n"
+ + "<timing>\n"
+ + "<!-- allow for 5 minutes out of every 30 minutes -->\n"
+ + "<timeWindowSecs>1800</timeWindowSecs>\n"
+ + "<timeMaxSecs>300</timeMaxSecs>\n"
+ + "<timeMinSecs>60</timeMinSecs>\n"
+ + "</timing>\n"
+ + "<refreshRate>\n"
+ + "<minimum>120</minimum>\n"
+ + "<maximum>120</maximum>\n"
+ + "</refreshRate>\n"
+ + "<thermalStatusLimit>light</thermalStatusLimit>\n"
+ + "<allowInLowPowerMode>false</allowInLowPowerMode>\n"
+ + "</highBrightnessMode>\n"
+ + "<ambientBrightnessChangeThresholds>\n"
+ + "<brighteningThresholds>\n"
+ + "<minimum>10</minimum>\n"
+ + "</brighteningThresholds>\n"
+ + "<darkeningThresholds>\n"
+ + "<minimum>2</minimum>\n"
+ + "</darkeningThresholds>\n"
+ + "</ambientBrightnessChangeThresholds>\n"
+ + "<screenBrightnessRampFastDecrease>0.01</screenBrightnessRampFastDecrease> "
+ + "<screenBrightnessRampFastIncrease>0.02</screenBrightnessRampFastIncrease> "
+ + "<screenBrightnessRampSlowDecrease>0.03</screenBrightnessRampSlowDecrease>"
+ + "<screenBrightnessRampSlowIncrease>0.04</screenBrightnessRampSlowIncrease>"
+ + "<screenBrightnessRampIncreaseMaxMillis>"
+ + "2000"
+ + "</screenBrightnessRampIncreaseMaxMillis>"
+ + "<screenBrightnessRampDecreaseMaxMillis>"
+ + "3000"
+ + "</screenBrightnessRampDecreaseMaxMillis>"
+ + "<ambientLightHorizonLong>5000</ambientLightHorizonLong>\n"
+ + "<ambientLightHorizonShort>50</ambientLightHorizonShort>\n"
+ + "<displayBrightnessChangeThresholds>"
+ + "<brighteningThresholds>"
+ + "<minimum>"
+ + "0.001"
+ + "</minimum>"
+ + "</brighteningThresholds>"
+ + "<darkeningThresholds>"
+ + "<minimum>"
+ + "0.002"
+ + "</minimum>"
+ + "</darkeningThresholds>"
+ + "</displayBrightnessChangeThresholds>"
+ + "<screenBrightnessRampIncreaseMaxMillis>"
+ + "2000"
+ + "</screenBrightnessRampIncreaseMaxMillis>\n"
+ + "<thermalThrottling>\n"
+ + "<brightnessThrottlingMap>\n"
+ + "<brightnessThrottlingPoint>\n"
+ + "<thermalStatus>emergency</thermalStatus>\n"
+ + "<!-- Throttling to 250 nits: (250-2.0)/(500-2.0)*(0.62-0.0)+0"
+ + ".0 = 0.30875502 -->\n"
+ + "<brightness>0.30875502</brightness>\n"
+ + "</brightnessThrottlingPoint>\n"
+ + "</brightnessThrottlingMap>\n"
+ + "</thermalThrottling>\n"
+ + "</displayConfiguration>\n";
+ }
+
+ private void mockDeviceConfigs() {
+ when(mResources.getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessSettingDefaultFloat)).thenReturn(0.5f);
+ when(mResources.getFloat(com.android.internal.R.dimen
+ .config_screenBrightnessSettingMaximumFloat)).thenReturn(1.0f);
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
index 5f1ff6be49be..1e97c1c5c5bc 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -287,7 +287,7 @@ public class DisplayManagerServiceTest {
when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
- final int[] displayIds = bs.getDisplayIds(/* includeDisabled= */ false);
+ final int displayIds[] = bs.getDisplayIds();
final int size = displayIds.length;
assertTrue(size > 0);
@@ -297,9 +297,7 @@ public class DisplayManagerServiceTest {
);
for (int i = 0; i < size; i++) {
DisplayInfo info = bs.getDisplayInfo(displayIds[i]);
- if (info != null) {
- assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
- }
+ assertTrue(expectedDisplayTypeToViewPortTypeMapping.keySet().contains(info.type));
}
displayManager.performTraversalInternal(mock(SurfaceControl.Transaction.class));
@@ -1176,8 +1174,7 @@ public class DisplayManagerServiceTest {
DisplayManagerService.BinderService displayManagerBinderService,
FakeDisplayDevice displayDevice) {
- final int[] displayIds = displayManagerBinderService.getDisplayIds(
- /* includeDisabled= */ false);
+ final int[] displayIds = displayManagerBinderService.getDisplayIds();
assertTrue(displayIds.length > 0);
int displayId = Display.INVALID_DISPLAY;
for (int i = 0; i < displayIds.length; i++) {
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 864f31552ae3..968e1d8c546b 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -16,6 +16,7 @@
package com.android.server.display;
+import static android.hardware.display.DisplayManager.DeviceConfig.KEY_BRIGHTNESS_THROTTLING_DATA;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_AMBIENT_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_HIGH_DISPLAY_BRIGHTNESS_THRESHOLDS;
import static android.hardware.display.DisplayManager.DeviceConfig.KEY_FIXED_REFRESH_RATE_LOW_AMBIENT_BRIGHTNESS_THRESHOLDS;
@@ -1902,6 +1903,11 @@ public class DisplayModeDirectorTest {
KEY_REFRESH_RATE_IN_HBM_HDR, String.valueOf(fps));
}
+ void setBrightnessThrottlingData(String brightnessThrottlingData) {
+ putPropertyAndNotify(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
+ KEY_BRIGHTNESS_THROTTLING_DATA, brightnessThrottlingData);
+ }
+
void setLowDisplayBrightnessThresholds(int[] brightnessThresholds) {
String thresholds = toPropertyValue(brightnessThresholds);
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
index 5b131454e4db..cc68ba88f76e 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayMapperTest.java
@@ -33,7 +33,6 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -54,8 +53,6 @@ import android.view.DisplayInfo;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
-import com.android.server.display.layout.Layout;
-
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -82,7 +79,6 @@ public class LogicalDisplayMapperTest {
private TestLooper mLooper;
private Handler mHandler;
private PowerManager mPowerManager;
- private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy;
@Mock LogicalDisplayMapper.Listener mListenerMock;
@Mock Context mContextMock;
@@ -137,11 +133,8 @@ public class LogicalDisplayMapperTest {
mLooper = new TestLooper();
mHandler = new Handler(mLooper.getLooper());
-
- mDeviceStateToLayoutMapSpy = spy(new DeviceStateToLayoutMap());
mLogicalDisplayMapper = new LogicalDisplayMapper(mContextMock, mDisplayDeviceRepo,
- mListenerMock, new DisplayManagerService.SyncRoot(), mHandler,
- mDeviceStateToLayoutMapSpy);
+ mListenerMock, new DisplayManagerService.SyncRoot(), mHandler);
}
@@ -420,86 +413,6 @@ public class LogicalDisplayMapperTest {
/* isBootCompleted= */true));
}
- @Test public void testEnabledAndDisabledDisplays() {
- DisplayAddress displayAddressOne = new TestUtils.TestDisplayAddress();
- DisplayAddress displayAddressTwo = new TestUtils.TestDisplayAddress();
- DisplayAddress displayAddressThree = new TestUtils.TestDisplayAddress();
-
- TestDisplayDevice device1 = createDisplayDevice(displayAddressOne, Display.TYPE_INTERNAL,
- 600, 800,
- DisplayDeviceInfo.FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY);
- TestDisplayDevice device2 = createDisplayDevice(displayAddressTwo, Display.TYPE_INTERNAL,
- 200, 800,
- DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
- TestDisplayDevice device3 = createDisplayDevice(displayAddressThree, Display.TYPE_INTERNAL,
- 600, 900, DisplayDeviceInfo.FLAG_OWN_DISPLAY_GROUP);
-
- Layout threeDevicesEnabledLayout = new Layout();
- threeDevicesEnabledLayout.createDisplayLocked(
- displayAddressOne,
- /* isDefault= */ true,
- /* isEnabled= */ true);
-
- threeDevicesEnabledLayout.createDisplayLocked(
- displayAddressTwo,
- /* isDefault= */ false,
- /* isEnabled= */ true);
- threeDevicesEnabledLayout.createDisplayLocked(
- displayAddressThree,
- /* isDefault= */ false,
- /* isEnabled= */ true);
-
- when(mDeviceStateToLayoutMapSpy.get(DeviceStateToLayoutMap.STATE_DEFAULT))
- .thenReturn(threeDevicesEnabledLayout);
-
- LogicalDisplay display1 = add(device1);
- LogicalDisplay display2 = add(device2);
- LogicalDisplay display3 = add(device3);
-
- // ensure 3 displays are returned
- int [] ids = mLogicalDisplayMapper.getDisplayIdsLocked(Process.SYSTEM_UID);
- assertEquals(3, ids.length);
- Arrays.sort(ids);
- assertEquals(DEFAULT_DISPLAY, ids[0]);
-
- Layout oneDeviceEnabledLayout = new Layout();
- oneDeviceEnabledLayout.createDisplayLocked(
- display1.getDisplayInfoLocked().address,
- /* isDefault= */ true,
- /* isEnabled= */ true);
- oneDeviceEnabledLayout.createDisplayLocked(
- display2.getDisplayInfoLocked().address,
- /* isDefault= */ false,
- /* isEnabled= */ false);
- oneDeviceEnabledLayout.createDisplayLocked(
- display3.getDisplayInfoLocked().address,
- /* isDefault= */ false,
- /* isEnabled= */ false);
-
- when(mDeviceStateToLayoutMapSpy.get(0)).thenReturn(oneDeviceEnabledLayout);
- when(mDeviceStateToLayoutMapSpy.get(1)).thenReturn(threeDevicesEnabledLayout);
-
- mLogicalDisplayMapper
- .setDeviceStateLocked(0, false);
- mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
- final int[] allDisplayIds = mLogicalDisplayMapper.getDisplayIdsLocked(
- Process.SYSTEM_UID, false);
- mLooper.dispatchAll();
-
- // ensure only one display is returned
- assertEquals(1, allDisplayIds.length);
-
- mLogicalDisplayMapper
- .setDeviceStateLocked(1, false);
- mDisplayDeviceRepo.onDisplayDeviceEvent(device3, DISPLAY_DEVICE_EVENT_CHANGED);
- final int[] threeDisplaysEnabled = mLogicalDisplayMapper.getDisplayIdsLocked(
- Process.SYSTEM_UID, false);
- mLooper.dispatchAll();
-
- // ensure all three displays are returned
- assertEquals(3, threeDisplaysEnabled.length);
- }
-
/////////////////
// Helper Methods
/////////////////
diff --git a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
index ece0a627f051..b0738fdb78d0 100644
--- a/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/LogicalDisplayTest.java
@@ -17,7 +17,11 @@
package com.android.server.display;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.PropertyInvalidatedCache;
@@ -45,18 +49,21 @@ public class LogicalDisplayTest {
private LogicalDisplay mLogicalDisplay;
private DisplayDevice mDisplayDevice;
+ private final DisplayDeviceInfo mDisplayDeviceInfo = new DisplayDeviceInfo();
@Before
public void setUp() {
// Share classloader to allow package private access.
System.setProperty("dexmaker.share_classloader", "true");
mDisplayDevice = mock(DisplayDevice.class);
- DisplayDeviceInfo displayDeviceInfo = new DisplayDeviceInfo();
- displayDeviceInfo.width = DISPLAY_WIDTH;
- displayDeviceInfo.height = DISPLAY_HEIGHT;
- displayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
mLogicalDisplay = new LogicalDisplay(DISPLAY_ID, LAYER_STACK, mDisplayDevice);
- when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(displayDeviceInfo);
+
+ mDisplayDeviceInfo.copyFrom(new DisplayDeviceInfo());
+ mDisplayDeviceInfo.width = DISPLAY_WIDTH;
+ mDisplayDeviceInfo.height = DISPLAY_HEIGHT;
+ mDisplayDeviceInfo.flags = DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
+ mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL;
+ when(mDisplayDevice.getDisplayDeviceInfoLocked()).thenReturn(mDisplayDeviceInfo);
// Disable binder caches in this process.
PropertyInvalidatedCache.disableForTestMode();
@@ -103,4 +110,33 @@ public class LogicalDisplayTest {
mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
assertEquals(expectedPosition, mLogicalDisplay.getDisplayPosition());
}
+
+ @Test
+ public void testDisplayInputFlags() {
+ SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+ reset(t);
+
+ mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_NONE;
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ verify(t).setDisplayFlags(any(), eq(0));
+ reset(t);
+
+ mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_VIRTUAL;
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+ reset(t);
+
+ mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_DISABLED);
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ verify(t).setDisplayFlags(any(), eq(0));
+ reset(t);
+
+ mLogicalDisplay.setPhase(LogicalDisplay.DISPLAY_PHASE_ENABLED);
+ mDisplayDeviceInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL;
+ mLogicalDisplay.configureDisplayLocked(t, mDisplayDevice, false);
+ verify(t).setDisplayFlags(any(), eq(SurfaceControl.DISPLAY_RECEIVES_INPUT));
+ reset(t);
+ }
}
diff --git a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
index 363c26b63bae..bbed1b60f8bf 100644
--- a/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/color/ColorDisplayServiceTest.java
@@ -16,11 +16,14 @@
package com.android.server.display.color;
+import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -1130,6 +1133,15 @@ public class ColorDisplayServiceTest {
eq(ColorDisplayManager.COLOR_MODE_BOOSTED), any(), eq(Display.COLOR_MODE_INVALID));
}
+ @Test
+ public void getColorMode_noAvailableModes_returnsNotSet() {
+ when(mResourcesSpy.getIntArray(R.array.config_availableColorModes))
+ .thenReturn(new int[] {});
+ startService();
+ verify(mDisplayTransformManager, never()).setColorMode(anyInt(), any(), anyInt());
+ assertThat(mBinderService.getColorMode()).isEqualTo(-1);
+ }
+
/**
* Configures Night display to use a custom schedule.
*
diff --git a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
index 371861f74871..7746bd6859d6 100644
--- a/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/policy/SideFpsEventHandlerTest.java
@@ -53,33 +53,30 @@ import java.util.List;
/**
* Unit tests for {@link SideFpsEventHandler}.
- * <p/>
- * Run with <code>atest SideFpsEventHandlerTest</code>.
+ *
+ * <p>Run with <code>atest SideFpsEventHandlerTest</code>.
*/
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class SideFpsEventHandlerTest {
- private static final List<Integer> sAllStates = List.of(
- BiometricStateListener.STATE_IDLE,
- BiometricStateListener.STATE_ENROLLING,
- BiometricStateListener.STATE_KEYGUARD_AUTH,
- BiometricStateListener.STATE_BP_AUTH,
- BiometricStateListener.STATE_AUTH_OTHER);
+ private static final List<Integer> sAllStates =
+ List.of(
+ BiometricStateListener.STATE_IDLE,
+ BiometricStateListener.STATE_ENROLLING,
+ BiometricStateListener.STATE_KEYGUARD_AUTH,
+ BiometricStateListener.STATE_BP_AUTH,
+ BiometricStateListener.STATE_AUTH_OTHER);
@Rule
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getContext(), null);
- @Mock
- private PackageManager mPackageManager;
- @Mock
- private FingerprintManager mFingerprintManager;
- @Spy
- private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
- @Mock
- private AlertDialog mAlertDialog;
- @Mock
- private Window mWindow;
+
+ @Mock private PackageManager mPackageManager;
+ @Mock private FingerprintManager mFingerprintManager;
+ @Spy private AlertDialog.Builder mDialogBuilder = new AlertDialog.Builder(mContext);
+ @Mock private AlertDialog mAlertDialog;
+ @Mock private Window mWindow;
private TestLooper mLooper = new TestLooper();
private SideFpsEventHandler mEventHandler;
@@ -95,9 +92,12 @@ public class SideFpsEventHandlerTest {
when(mDialogBuilder.create()).thenReturn(mAlertDialog);
when(mAlertDialog.getWindow()).thenReturn(mWindow);
- mEventHandler = new SideFpsEventHandler(
- mContext, new Handler(mLooper.getLooper()),
- mContext.getSystemService(PowerManager.class), () -> mDialogBuilder);
+ mEventHandler =
+ new SideFpsEventHandler(
+ mContext,
+ new Handler(mLooper.getLooper()),
+ mContext.getSystemService(PowerManager.class),
+ () -> mDialogBuilder);
}
@Test
@@ -105,7 +105,7 @@ public class SideFpsEventHandlerTest {
when(mPackageManager.hasSystemFeature(eq(PackageManager.FEATURE_FINGERPRINT)))
.thenReturn(false);
- assertThat(mEventHandler.onSinglePressDetected(60L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -117,7 +117,7 @@ public class SideFpsEventHandlerTest {
for (int state : sAllStates) {
setBiometricState(state);
- assertThat(mEventHandler.onSinglePressDetected(200L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(200L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -130,7 +130,7 @@ public class SideFpsEventHandlerTest {
for (int state : sAllStates) {
setBiometricState(state);
- assertThat(mEventHandler.onSinglePressDetected(400L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(400L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -139,13 +139,13 @@ public class SideFpsEventHandlerTest {
@Test
public void ignoresWhenIdleOrUnknown() throws Exception {
- setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setupWithSensor(true /* hasSidefps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_IDLE);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
setBiometricState(BiometricStateListener.STATE_AUTH_OTHER);
- assertThat(mEventHandler.onSinglePressDetected(90000L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(90000L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -156,7 +156,7 @@ public class SideFpsEventHandlerTest {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_KEYGUARD_AUTH);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isFalse();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isFalse();
mLooper.dispatchAll();
verify(mAlertDialog, never()).show();
@@ -164,13 +164,13 @@ public class SideFpsEventHandlerTest {
@Test
public void promptsWhenBPisActive() throws Exception {
- setupWithSensor(true /* hasSfps */, true /* initialized */);
+ setupWithSensor(true /* hasSideFps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_BP_AUTH);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
mLooper.dispatchAll();
- verify(mAlertDialog).show();
+ verify(mAlertDialog, never()).show();
}
@Test
@@ -178,7 +178,57 @@ public class SideFpsEventHandlerTest {
setupWithSensor(true /* hasSfps */, true /* initialized */);
setBiometricState(BiometricStateListener.STATE_ENROLLING);
- assertThat(mEventHandler.onSinglePressDetected(80000L)).isTrue();
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+ verify(mAlertDialog, never()).dismiss();
+ }
+
+ @Test
+ public void dismissesDialogOnTouchWhenEnrolling() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mAlertDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ mLooper.moveTimeForward(10000);
+ mLooper.dispatchAll();
+
+ verify(mAlertDialog).dismiss();
+ }
+
+ @Test
+ public void dismissesDialogFailsWhenPowerPressedAndDialogShowing() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mAlertDialog.isShowing()).thenReturn(true);
+ assertThat(mEventHandler.shouldConsumeSinglePress(80000L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog).show();
+
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
+
+ mLooper.dispatchAll();
+ verify(mAlertDialog, never()).dismiss();
+ }
+
+ @Test
+ public void showDialogAfterTap() throws Exception {
+ setupWithSensor(true /* hasSfps */, true /* initialized */);
+
+ setBiometricState(BiometricStateListener.STATE_ENROLLING);
+ when(mAlertDialog.isShowing()).thenReturn(true);
+ mBiometricStateListener.onBiometricAction(BiometricStateListener.ACTION_SENSOR_TOUCH);
+ assertThat(mEventHandler.shouldConsumeSinglePress(60L)).isTrue();
mLooper.dispatchAll();
verify(mAlertDialog).show();
@@ -201,11 +251,13 @@ public class SideFpsEventHandlerTest {
ArgumentCaptor.forClass(IFingerprintAuthenticatorsRegisteredCallback.class);
verify(mFingerprintManager).addAuthenticatorsRegisteredCallback(fpCallbackCaptor.capture());
if (initialized) {
- fpCallbackCaptor.getValue().onAllAuthenticatorsRegistered(
- List.of(mock(FingerprintSensorPropertiesInternal.class)));
+ fpCallbackCaptor
+ .getValue()
+ .onAllAuthenticatorsRegistered(
+ List.of(mock(FingerprintSensorPropertiesInternal.class)));
if (hasSfps) {
- ArgumentCaptor<BiometricStateListener> captor = ArgumentCaptor.forClass(
- BiometricStateListener.class);
+ ArgumentCaptor<BiometricStateListener> captor =
+ ArgumentCaptor.forClass(BiometricStateListener.class);
verify(mFingerprintManager).registerBiometricStateListener(captor.capture());
mBiometricStateListener = captor.getValue();
}
diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
index c016406fc96a..308a4b67de24 100644
--- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
+++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java
@@ -928,6 +928,7 @@ public class AppStandbyControllerTests {
}
@Test
+ @FlakyTest(bugId = 185169504)
public void testNotificationEvent_quotaBump() throws Exception {
mInjector.mSettingsBuilder
.setBoolean("trigger_quota_bump_on_notification_seen", true);
diff --git a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
index 75bd2ccbe635..bc2c57e8bf84 100644
--- a/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
+++ b/services/tests/servicestests/src/com/android/server/usage/UsageStatsDatabaseTest.java
@@ -22,6 +22,7 @@ import static junit.framework.TestCase.fail;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents.Event;
@@ -440,6 +441,7 @@ public class UsageStatsDatabaseTest {
prevDB.readMappingsLocked();
prevDB.init(1);
prevDB.putUsageStats(UsageStatsManager.INTERVAL_DAILY, mIntervalStats);
+ Set<String> prevDBApps = mIntervalStats.packageStats.keySet();
// Create a backup with a specific version
byte[] blob = prevDB.getBackupPayload(KEY_USAGE_STATS, version);
if (version >= 1 && version <= 3) {
@@ -447,6 +449,11 @@ public class UsageStatsDatabaseTest {
"UsageStatsDatabase shouldn't be able to write backups as XML");
return;
}
+ if (version < 1 || version > UsageStatsDatabase.BACKUP_VERSION) {
+ assertFalse(blob != null && blob.length != 0,
+ "UsageStatsDatabase shouldn't be able to write backups for unknown versions");
+ return;
+ }
clearUsageStatsFiles();
@@ -454,9 +461,11 @@ public class UsageStatsDatabaseTest {
newDB.readMappingsLocked();
newDB.init(1);
// Attempt to restore the usage stats from the backup
- newDB.applyRestoredPayload(KEY_USAGE_STATS, blob);
- List<IntervalStats> stats = newDB.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, 0, mEndTime,
- mIntervalStatsVerifier);
+ Set<String> restoredApps = newDB.applyRestoredPayload(KEY_USAGE_STATS, blob);
+ assertTrue(restoredApps.containsAll(prevDBApps),
+ "List of restored apps does not match list backed-up apps list.");
+ List<IntervalStats> stats = newDB.queryUsageStats(
+ UsageStatsManager.INTERVAL_DAILY, 0, mEndTime, mIntervalStatsVerifier);
if (version > UsageStatsDatabase.BACKUP_VERSION || version < 1) {
assertFalse(stats != null && !stats.isEmpty(),
diff --git a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index c735bb7add0a..8a96febcd1e9 100644
--- a/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -778,8 +778,7 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK), HAPTIC_FEEDBACK_ATTRS);
// Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
@@ -793,49 +792,78 @@ public class VibratorManagerServiceTest {
}
@Test
- public void vibrate_withOngoingAlarmVibration_ignoresEffect() throws Exception {
+ public void vibrate_withNewRepeatingVibration_cancelsOngoingEffect() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
VibrationEffect alarmEffect = VibrationEffect.createWaveform(
new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
- vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_ALARM).build());
+ vibrate(service, alarmEffect, ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before checking it started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
+ VibrationEffect repeatingEffect = VibrationEffect.createWaveform(
+ new long[]{10_000, 10_000}, new int[]{128, 255}, 1);
+ vibrate(service, repeatingEffect, NOTIFICATION_ATTRS);
- // Wait before checking it never played a second effect.
- assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
- service, /* timeout= */ 50));
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ // The second vibration should have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
}
@Test
- public void vibrate_withOngoingRingtoneVibration_ignoresEffect() throws Exception {
+ public void vibrate_withOngoingHigherImportanceVibration_ignoresEffect() throws Exception {
mockVibrators(1);
mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
VibratorManagerService service = createSystemReadyService();
- VibrationEffect alarmEffect = VibrationEffect.createWaveform(
+ VibrationEffect effect = VibrationEffect.createWaveform(
new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
- vibrate(service, alarmEffect, new VibrationAttributes.Builder().setUsage(
- VibrationAttributes.USAGE_RINGTONE).build());
+ vibrate(service, effect, ALARM_ATTRS);
// VibrationThread will start this vibration async, so wait before checking it started.
assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
service, TEST_TIMEOUT_MILLIS));
- vibrate(service, VibrationEffect.get(VibrationEffect.EFFECT_CLICK),
- HAPTIC_FEEDBACK_ATTRS);
+ vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
// Wait before checking it never played a second effect.
assertFalse(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
service, /* timeout= */ 50));
+
+ // The second vibration shouldn't have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(1)).noteVibratorOn(anyInt(), anyLong());
+ }
+
+ @Test
+ public void vibrate_withOngoingLowerImportanceVibration_cancelsOngoingEffect()
+ throws Exception {
+ mockVibrators(1);
+ mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
+ VibratorManagerService service = createSystemReadyService();
+
+ VibrationEffect effect = VibrationEffect.createWaveform(
+ new long[]{10_000, 10_000}, new int[]{128, 255}, -1);
+ vibrate(service, effect, HAPTIC_FEEDBACK_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> !mVibratorProviders.get(1).getAllEffectSegments().isEmpty(),
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, effect, RINGTONE_ATTRS);
+
+ // VibrationThread will start this vibration async, so wait before checking it started.
+ assertTrue(waitUntil(s -> mVibratorProviders.get(1).getAllEffectSegments().size() > 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ // The second vibration should have recorded that the vibrators were turned on.
+ verify(mBatteryStatsMock, times(2)).noteVibratorOn(anyInt(), anyLong());
}
@Test
@@ -1052,15 +1080,15 @@ public class VibratorManagerServiceTest {
IVibrator.CAP_COMPOSE_EFFECTS);
VibratorManagerService service = createSystemReadyService();
- vibrate(service, CombinedVibration.startSequential()
- .addNext(1, VibrationEffect.createOneShot(100, 125))
- .combine(), NOTIFICATION_ATTRS);
- assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
- service, TEST_TIMEOUT_MILLIS));
-
vibrate(service, VibrationEffect.startComposition()
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 0.5f)
.compose(), HAPTIC_FEEDBACK_ATTRS);
+ assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 1,
+ service, TEST_TIMEOUT_MILLIS));
+
+ vibrate(service, CombinedVibration.startSequential()
+ .addNext(1, VibrationEffect.createOneShot(100, 125))
+ .combine(), NOTIFICATION_ATTRS);
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 2,
service, TEST_TIMEOUT_MILLIS));
@@ -1070,25 +1098,25 @@ public class VibratorManagerServiceTest {
assertTrue(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() == 3,
service, TEST_TIMEOUT_MILLIS));
+ // Ring vibrations have intensity OFF and are not played.
vibrate(service, VibrationEffect.createOneShot(100, 125), RINGTONE_ATTRS);
assertFalse(waitUntil(s -> fakeVibrator.getAllEffectSegments().size() > 3,
- service, TEST_TIMEOUT_MILLIS));
+ service, /* timeout= */ 50));
+ // Only 3 effects played successfully.
assertEquals(3, fakeVibrator.getAllEffectSegments().size());
+ // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
+ assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
+ 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(0)).getScale());
+
// Notification vibrations will be scaled with SCALE_HIGH or none if default is high.
assertEquals(defaultNotificationIntensity < Vibrator.VIBRATION_INTENSITY_HIGH,
0.6 < fakeVibrator.getAmplitudes().get(0));
- // Haptic feedback vibrations will be scaled with SCALE_LOW or none if default is low.
- assertEquals(defaultTouchIntensity > Vibrator.VIBRATION_INTENSITY_LOW,
- 0.5 > ((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(1)).getScale());
-
// Alarm vibration will be scaled with SCALE_NONE.
assertEquals(1f,
((PrimitiveSegment) fakeVibrator.getAllEffectSegments().get(2)).getScale(), 1e-5);
-
- // Ring vibrations have intensity OFF and are not played.
}
@Test
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
index c64ff9e128e6..3de65c19a1a4 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowOrientationListenerTest.java
@@ -185,7 +185,7 @@ public class WindowOrientationListenerTest {
}
@Override
- public boolean isKeyguardLocked() {
+ public boolean isKeyguardShowingAndNotOccluded() {
return mIsScreenLocked;
}
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
index 2f054b004d42..8ba9af0d85d0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ManagedServicesTest.java
@@ -840,6 +840,156 @@ public class ManagedServicesTest extends UiServiceTestCase {
}
@Test
+ public void reregisterService_checksAppIsApproved_pkg() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+
+ service.addApprovedList("a", 0, true);
+
+ service.reregisterService(cn, 0);
+
+ assertTrue(service.isBound(cn, 0));
+ }
+
+ @Test
+ public void reregisterService_checksAppIsApproved_pkg_secondary() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_PACKAGE);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+
+ service.addApprovedList("a", 0, false);
+
+ service.reregisterService(cn, 0);
+
+ assertTrue(service.isBound(cn, 0));
+ }
+
+ @Test
+ public void reregisterService_checksAppIsApproved_cn() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+
+ service.addApprovedList("a/a", 0, true);
+
+ service.reregisterService(cn, 0);
+
+ assertTrue(service.isBound(cn, 0));
+ }
+
+ @Test
+ public void reregisterService_checksAppIsApproved_cn_secondary() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+
+ service.addApprovedList("a/a", 0, false);
+
+ service.reregisterService(cn, 0);
+
+ assertTrue(service.isBound(cn, 0));
+ }
+
+ @Test
+ public void reregisterService_checksAppIsNotApproved_cn_secondary() throws Exception {
+ Context context = mock(Context.class);
+ PackageManager pm = mock(PackageManager.class);
+ ApplicationInfo ai = new ApplicationInfo();
+ ai.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
+
+ when(context.getPackageName()).thenReturn(mContext.getPackageName());
+ when(context.getUserId()).thenReturn(mContext.getUserId());
+ when(context.getPackageManager()).thenReturn(pm);
+ when(pm.getApplicationInfo(anyString(), anyInt())).thenReturn(ai);
+
+ ManagedServices service = new TestManagedServices(context, mLock, mUserProfiles, mIpm,
+ APPROVAL_BY_COMPONENT);
+ ComponentName cn = ComponentName.unflattenFromString("a/a");
+
+ when(context.bindServiceAsUser(any(), any(), anyInt(), any())).thenAnswer(invocation -> {
+ Object[] args = invocation.getArguments();
+ ServiceConnection sc = (ServiceConnection) args[1];
+ sc.onServiceConnected(cn, mock(IBinder.class));
+ return true;
+ });
+
+ service.addApprovedList("b/b", 0, false);
+
+ service.reregisterService(cn, 0);
+
+ assertFalse(service.isBound(cn, 0));
+ }
+
+ @Test
public void unbindOtherUserServices() throws PackageManager.NameNotFoundException {
Context context = mock(Context.class);
PackageManager pm = mock(PackageManager.class);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
index bd7186e74354..2f922600857a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryDatabaseTest.java
@@ -18,7 +18,6 @@ package com.android.server.notification;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
@@ -26,7 +25,6 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import android.app.AlarmManager;
import android.app.NotificationHistory;
import android.app.NotificationHistory.HistoricalNotification;
import android.content.Context;
@@ -60,8 +58,6 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
Handler mFileWriteHandler;
@Mock
Context mContext;
- @Mock
- AlarmManager mAlarmManager;
NotificationHistoryDatabase mDataBase;
@@ -96,22 +92,16 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
- when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager);
when(mContext.getUser()).thenReturn(getContext().getUser());
when(mContext.getPackageName()).thenReturn(getContext().getPackageName());
mRootDir = new File(mContext.getFilesDir(), "NotificationHistoryDatabaseTest");
- mDataBase = new NotificationHistoryDatabase(mContext, mFileWriteHandler, mRootDir);
+ mDataBase = new NotificationHistoryDatabase(mFileWriteHandler, mRootDir);
mDataBase.init();
}
@Test
- public void testDeletionReceiver() {
- verify(mContext, times(1)).registerReceiver(any(), any(), anyInt());
- }
-
- @Test
public void testPrune() throws Exception {
GregorianCalendar cal = new GregorianCalendar();
cal.setTimeInMillis(10);
@@ -144,8 +134,6 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
mDataBase.prune(retainDays, cal.getTimeInMillis());
assertThat(mDataBase.mHistoryFiles).containsExactlyElementsIn(expectedFiles);
-
- verify(mAlarmManager, times(6)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
}
@Test
@@ -412,15 +400,14 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
when(file.getName()).thenReturn("5");
when(af.getBaseFile()).thenReturn(file);
- wbr.run(5, af);
+ wbr.run(af);
assertThat(mDataBase.mHistoryFiles.size()).isEqualTo(1);
assertThat(mDataBase.mBuffer).isNotEqualTo(nh);
- verify(mAlarmManager, times(1)).setExactAndAllowWhileIdle(anyInt(), anyLong(), any());
}
@Test
- public void testRemoveFilePathFromHistory_hasMatch() throws Exception {
+ public void testRemoveFilePathFromHistory_hasMatch() {
for (int i = 0; i < 5; i++) {
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
@@ -436,7 +423,7 @@ public class NotificationHistoryDatabaseTest extends UiServiceTestCase {
}
@Test
- public void testRemoveFilePathFromHistory_noMatch() throws Exception {
+ public void testRemoveFilePathFromHistory_noMatch() {
for (int i = 0; i < 5; i++) {
AtomicFile af = mock(AtomicFile.class);
when(af.getBaseFile()).thenReturn(new File(mRootDir, "af" + i));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
new file mode 100644
index 000000000000..af10b9dba63f
--- /dev/null
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationHistoryJobServiceTest.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.notification;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
+import static junit.framework.TestCase.assertFalse;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.app.job.JobServiceEngine;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.rule.ServiceTestRule;
+
+import com.android.server.LocalServices;
+import com.android.server.UiServiceTestCase;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+
+import java.lang.reflect.Field;
+
+@RunWith(AndroidTestingRunner.class)
+public class NotificationHistoryJobServiceTest extends UiServiceTestCase {
+ private NotificationHistoryJobService mJobService;
+ private JobParameters mJobParams = new JobParameters(null,
+ NotificationHistoryJobService.BASE_JOB_ID, null, null, null,
+ 0, false, false, null, null, null);
+
+ @Captor
+ ArgumentCaptor<JobInfo> mJobInfoCaptor;
+
+ @Mock
+ private JobScheduler mMockJobScheduler;
+
+ @Mock
+ private NotificationManagerInternal mMockNotificationManagerInternal;
+
+ @Rule
+ public final ServiceTestRule mServiceRule = new ServiceTestRule();
+
+ @Before
+ public void setUp() throws Exception {
+ mJobService = new NotificationHistoryJobService();
+
+ final Field field = JobService.class.getDeclaredField("mEngine");
+ field.setAccessible(true);
+ field.set(mJobService, mock(JobServiceEngine.class));
+ mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler);
+
+ // add NotificationManagerInternal to LocalServices
+ LocalServices.removeServiceForTest(NotificationManagerInternal.class);
+ LocalServices.addService(NotificationManagerInternal.class,
+ mMockNotificationManagerInternal);
+ }
+
+ @Test
+ public void testScheduleJob() {
+ // if asked, the job doesn't currently exist yet
+ when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null);
+
+ // attempt to schedule the job
+ NotificationHistoryJobService.scheduleJob(mContext);
+ verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture());
+
+ // verify various properties of the job that is passed in to the job scheduler
+ JobInfo jobInfo = mJobInfoCaptor.getValue();
+ assertEquals(NotificationHistoryJobService.BASE_JOB_ID, jobInfo.getId());
+ assertFalse(jobInfo.isPersisted());
+ assertTrue(jobInfo.isPeriodic());
+ }
+
+ @Test
+ public void testOnStartJob() {
+ assertTrue(mJobService.onStartJob(mJobParams));
+
+ verify(mMockNotificationManagerInternal, timeout(500).atLeastOnce()).cleanupHistoryFiles();
+ }
+} \ No newline at end of file
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
index 76d4059eb436..c5131c8f8c1d 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenersTest.java
@@ -28,6 +28,7 @@ import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.nullable;
import static org.mockito.Mockito.atLeast;
@@ -40,6 +41,9 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
@@ -49,6 +53,7 @@ import android.os.Bundle;
import android.os.UserHandle;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
+import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
import android.testing.TestableContext;
@@ -60,6 +65,8 @@ import android.util.Xml;
import com.android.server.UiServiceTestCase;
+import com.google.common.collect.ImmutableList;
+
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
@@ -397,6 +404,36 @@ public class NotificationListenersTest extends UiServiceTestCase {
}
@Test
+ public void testImplicitGrant() {
+ String pkg = "pkg";
+ int uid = 9;
+ NotificationChannel channel = new NotificationChannel("id", "name",
+ NotificationManager.IMPORTANCE_HIGH);
+ Notification.Builder nb = new Notification.Builder(mContext, channel.getId())
+ .setContentTitle("foo")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .setTimeoutAfter(1);
+
+ StatusBarNotification sbn = new StatusBarNotification(pkg, pkg, 8, "tag", uid, 0,
+ nb.build(), UserHandle.getUserHandleForUid(uid), null, 0);
+ NotificationRecord r = new NotificationRecord(mContext, sbn, channel);
+
+ ManagedServices.ManagedServiceInfo info = mListeners.new ManagedServiceInfo(
+ null, new ComponentName("a", "a"), sbn.getUserId(), false, null, 33, 33);
+ List<ManagedServices.ManagedServiceInfo> services = ImmutableList.of(info);
+ when(mListeners.getServices()).thenReturn(services);
+
+ when(mNm.isVisibleToListener(any(), anyInt(), any())).thenReturn(true);
+ when(mNm.makeRankingUpdateLocked(info)).thenReturn(mock(NotificationRankingUpdate.class));
+ mNm.mPackageManagerInternal = mPmi;
+
+ mListeners.notifyPostedLocked(r, null);
+
+ verify(mPmi).grantImplicitAccess(sbn.getUserId(), null, UserHandle.getAppId(33),
+ sbn.getUid(), false, false);
+ }
+
+ @Test
public void testNotifyPostedLockedInLockdownMode() {
NotificationRecord r = mock(NotificationRecord.class);
NotificationRecord old = mock(NotificationRecord.class);
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 22721a1bcc92..443ed583bd84 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -3380,39 +3380,98 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
}
@Test
- public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() throws Exception {
+ public void testSnoozeRunnable_tooManySnoozed_singleNotification() {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, null, true);
mService.addNotification(notification);
- when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
+
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+ when(mSnoozeHelper.canSnooze(1)).thenReturn(false);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
- notification.getKey(), 100, null);
+ notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
- NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 =
+
+ verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong());
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testSnoozeRunnable_tooManySnoozed_singleGroupChildNotification() {
+ final NotificationRecord notification = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord notificationChild = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", false);
+ mService.addNotification(notification);
+ mService.addNotification(notificationChild);
+
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+ when(mSnoozeHelper.canSnooze(2)).thenReturn(false);
+
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ notificationChild.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong());
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(2);
+ }
+
+ @Test
+ public void testSnoozeRunnable_tooManySnoozed_summaryNotification() {
+ final NotificationRecord notification = generateNotificationRecord(
+ mTestNotificationChannel, 1, "group", true);
+ final NotificationRecord notificationChild = generateNotificationRecord(
+ mTestNotificationChannel, 12, "group", false);
+ final NotificationRecord notificationChild2 = generateNotificationRecord(
+ mTestNotificationChannel, 13, "group", false);
+ mService.addNotification(notification);
+ mService.addNotification(notificationChild);
+ mService.addNotification(notificationChild2);
+
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+ when(mSnoozeHelper.canSnooze(3)).thenReturn(false);
+
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
+ mService.new SnoozeNotificationRunnable(
+ notification.getKey(), 100, null);
+ snoozeNotificationRunnable.run();
+
+ verify(mSnoozeHelper, never()).snooze(any(NotificationRecord.class), anyLong());
+ assertThat(mService.getNotificationRecordCount()).isEqualTo(3);
+ }
+
+ @Test
+ public void testSnoozeRunnable_reSnoozeASingleSnoozedNotification() {
+ final NotificationRecord notification = generateNotificationRecord(
+ mTestNotificationChannel, 1, null, true);
+ mService.addNotification(notification);
+ when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
+
+ NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
+ snoozeNotificationRunnable.run();
// snooze twice
verify(mSnoozeHelper, times(2)).snooze(any(NotificationRecord.class), anyLong());
}
@Test
- public void testSnoozeRunnable_reSnoozeASnoozedNotificationWithGroupKey() throws Exception {
+ public void testSnoozeRunnable_reSnoozeASnoozedNotificationWithGroupKey() {
final NotificationRecord notification = generateNotificationRecord(
mTestNotificationChannel, 1, "group", true);
mService.addNotification(notification);
when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
- NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 =
- mService.new SnoozeNotificationRunnable(
- notification.getKey(), 100, null);
snoozeNotificationRunnable.run();
// snooze twice
@@ -3430,6 +3489,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(mSnoozeHelper.getNotification(any())).thenReturn(notification);
when(mSnoozeHelper.getNotifications(
anyString(), anyString(), anyInt())).thenReturn(new ArrayList<>());
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3439,8 +3499,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
.thenReturn(new ArrayList<>(Arrays.asList(notification, notification2)));
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable2 =
mService.new SnoozeNotificationRunnable(
- notification.getKey(), 100, null);
- snoozeNotificationRunnable.run();
+ notification2.getKey(), 100, null);
+ snoozeNotificationRunnable2.run();
// snooze twice
verify(mSnoozeHelper, times(4)).snooze(any(NotificationRecord.class), anyLong());
@@ -3454,6 +3514,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel, 2, "group", false);
mService.addNotification(grouped);
mService.addNotification(nonGrouped);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3483,6 +3544,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(parent);
mService.addNotification(child);
mService.addNotification(child2);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3504,6 +3566,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.addNotification(parent);
mService.addNotification(child);
mService.addNotification(child2);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3529,6 +3592,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mTestNotificationChannel, 2, "group", false);
mService.addNotification(parent);
mService.addNotification(child);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -3556,6 +3620,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationRecord child = generateNotificationRecord(
mTestNotificationChannel, 2, "group", false);
mService.addNotification(child);
+ when(mSnoozeHelper.canSnooze(anyInt())).thenReturn(true);
NotificationManagerService.SnoozeNotificationRunnable snoozeNotificationRunnable =
mService.new SnoozeNotificationRunnable(
@@ -8304,7 +8369,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
public void testOnUnlockUser() {
UserInfo ui = new UserInfo();
ui.id = 10;
- mService.onUserUnlocking(new TargetUser(ui));
+ mService.onUserUnlocked(new TargetUser(ui));
waitForIdle();
verify(mHistoryManager, timeout(MAX_POST_DELAY).times(1)).onUserUnlocked(ui.id);
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
index 4c7e8433b15b..f2b1dc9132d5 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/PermissionHelperTest.java
@@ -18,6 +18,7 @@ package com.android.server.notification;
import static android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
import static android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED;
import static android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
@@ -74,6 +75,8 @@ public class PermissionHelperTest extends UiServiceTestCase {
private PermissionHelper mPermissionHelper;
+ private static final int USER_FLAG_MASK = FLAG_PERMISSION_USER_SET | FLAG_PERMISSION_USER_FIXED;
+
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
@@ -183,7 +186,8 @@ public class PermissionHelperTest extends UiServiceTestCase {
verify(mPermManager).grantRuntimePermission(
"pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
+ FLAG_PERMISSION_USER_SET, true, 10);
}
@Test
@@ -201,7 +205,8 @@ public class PermissionHelperTest extends UiServiceTestCase {
verify(mPermManager).grantRuntimePermission(
"pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
+ FLAG_PERMISSION_USER_SET, true, 10);
}
@Test
@@ -214,7 +219,8 @@ public class PermissionHelperTest extends UiServiceTestCase {
verify(mPermManager).revokeRuntimePermission(
eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- FLAG_PERMISSION_USER_SET, FLAG_PERMISSION_USER_SET, true, 10);
+ USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT,
+ FLAG_PERMISSION_USER_SET, true, 10);
}
@Test
@@ -227,7 +233,7 @@ public class PermissionHelperTest extends UiServiceTestCase {
verify(mPermManager).grantRuntimePermission(
"pkg", Manifest.permission.POST_NOTIFICATIONS, 10);
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- 0, FLAG_PERMISSION_USER_SET, true, 10);
+ USER_FLAG_MASK, 0, true, 10);
}
@Test
@@ -240,7 +246,8 @@ public class PermissionHelperTest extends UiServiceTestCase {
verify(mPermManager).revokeRuntimePermission(
eq("pkg"), eq(Manifest.permission.POST_NOTIFICATIONS), eq(10), anyString());
verify(mPermManager).updatePermissionFlags("pkg", Manifest.permission.POST_NOTIFICATIONS,
- 0, FLAG_PERMISSION_USER_SET, true, 10);
+ USER_FLAG_MASK | FLAG_PERMISSION_GRANTED_BY_DEFAULT, 0,
+ true, 10);
}
@Test
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
index 2ae2ef7162a5..7817e8176e76 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/SnoozeHelperTest.java
@@ -15,10 +15,12 @@
*/
package com.android.server.notification;
+import static com.android.server.notification.SnoozeHelper.CONCURRENT_SNOOZE_LIMIT;
import static com.android.server.notification.SnoozeHelper.EXTRA_KEY;
import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertTrue;
@@ -38,7 +40,6 @@ import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
-import android.os.SystemClock;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;
import android.test.suitebuilder.annotation.SmallTest;
@@ -49,7 +50,6 @@ import android.util.Xml;
import androidx.test.runner.AndroidJUnit4;
-import com.android.internal.util.FastXmlSerializer;
import com.android.server.UiServiceTestCase;
import com.android.server.pm.PackageManagerService;
@@ -59,9 +59,7 @@ import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
-import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
@@ -197,18 +195,6 @@ public class SnoozeHelperTest extends UiServiceTestCase {
}
@Test
- public void testCleanupContextShouldRemovePersistedRecord() {
- NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
- mSnoozeHelper.snooze(r, "context");
- mSnoozeHelper.cleanupPersistedContext(r.getSbn().getKey());
- assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
- r.getUser().getIdentifier(),
- r.getSbn().getPackageName(),
- r.getSbn().getKey()
- ));
- }
-
- @Test
public void testReadNoneSnoozedNotification() throws XmlPullParserException,
IOException, InterruptedException {
NotificationRecord r = getNotificationRecord(
@@ -218,8 +204,9 @@ public class SnoozeHelperTest extends UiServiceTestCase {
assertEquals("should see a zero value for unsnoozed notification",
0L,
mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
- UserHandle.SYSTEM.getIdentifier(),
- "not_my_package", r.getKey()).longValue());
+ UserHandle.SYSTEM.getIdentifier(), "not_my_package",
+ getNotificationRecord("not_my_package", 1, "one",
+ UserHandle.SYSTEM).getKey()).longValue());
}
@Test
@@ -281,6 +268,22 @@ public class SnoozeHelperTest extends UiServiceTestCase {
}
@Test
+ public void testSnoozeLimit() {
+ for (int i = 0; i < CONCURRENT_SNOOZE_LIMIT; i++ ) {
+ NotificationRecord r = getNotificationRecord("pkg", i, i+"", UserHandle.SYSTEM);
+
+ assertTrue("cannot snooze record " + i, mSnoozeHelper.canSnooze(1));
+
+ if (i % 2 == 0) {
+ mSnoozeHelper.snooze(r, null);
+ } else {
+ mSnoozeHelper.snooze(r, 9000);
+ }
+ }
+ assertFalse(mSnoozeHelper.canSnooze(1));
+ }
+
+ @Test
public void testCancelByApp() throws Exception {
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
@@ -575,7 +578,7 @@ public class SnoozeHelperTest extends UiServiceTestCase {
}
@Test
- public void testClearData() {
+ public void testClearData_userPackage() {
// snooze 2 from same package
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.SYSTEM);
@@ -599,17 +602,72 @@ public class SnoozeHelperTest extends UiServiceTestCase {
}
@Test
+ public void testClearData_user() {
+ // snooze 2 from same package
+ NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord r2 = getNotificationRecord("pkg2", 2, "two", UserHandle.SYSTEM);
+ NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
+ NotificationRecord r4 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL);
+ mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(r2, 1000);
+ mSnoozeHelper.snooze(r3, "until");
+ mSnoozeHelper.snooze(r4, "until");
+
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_ALL, r4.getSbn().getPackageName(), r4.getKey()));
+
+ // clear data
+ mSnoozeHelper.clearData(UserHandle.USER_SYSTEM);
+
+ // nothing in USER_SYSTEM snoozed; alarms canceled
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r2.getSbn().getPackageName(), r2.getKey()));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, r4.getSbn().getPackageName(), r4.getKey()));
+
+ assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
+ r3.getUser().getIdentifier(), r3.getSbn().getPackageName(),
+ r3.getSbn().getKey()));
+ assertNotNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
+ r4.getUser().getIdentifier(), r4.getSbn().getPackageName(),
+ r4.getSbn().getKey()));
+ assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ r.getUser().getIdentifier(), r.getSbn().getPackageName(),
+ r.getSbn().getKey()).longValue());
+ assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ r2.getUser().getIdentifier(), r2.getSbn().getPackageName(),
+ r2.getSbn().getKey()).longValue());
+
+ // 2 for initial timed-snoozes, once each for canceling the USER_SYSTEM snoozes
+ verify(mAm, times(5)).cancel(any(PendingIntent.class));
+ }
+
+ @Test
public void testClearData_otherRecordsUntouched() {
// 2 packages, 2 users
NotificationRecord r = getNotificationRecord("pkg", 1, "one", UserHandle.SYSTEM);
+ NotificationRecord rb = getNotificationRecord("pkg", 1, "oneb", UserHandle.SYSTEM);
NotificationRecord r2 = getNotificationRecord("pkg", 2, "two", UserHandle.ALL);
NotificationRecord r3 = getNotificationRecord("pkg2", 3, "three", UserHandle.SYSTEM);
mSnoozeHelper.snooze(r, 1000);
+ mSnoozeHelper.snooze(rb, "until");
mSnoozeHelper.snooze(r2, 1000);
mSnoozeHelper.snooze(r3, 1000);
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, rb.getSbn().getPackageName(), rb.getKey()));
+ assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
@@ -619,12 +677,22 @@ public class SnoozeHelperTest extends UiServiceTestCase {
assertFalse(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r.getSbn().getPackageName(), r.getKey()));
+ assertFalse(mSnoozeHelper.isSnoozed(
+ UserHandle.USER_SYSTEM, rb.getSbn().getPackageName(), rb.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_ALL, r2.getSbn().getPackageName(), r2.getKey()));
assertTrue(mSnoozeHelper.isSnoozed(
UserHandle.USER_SYSTEM, r3.getSbn().getPackageName(), r3.getKey()));
+
+ assertNull(mSnoozeHelper.getSnoozeContextForUnpostedNotification(
+ rb.getUser().getIdentifier(), rb.getSbn().getPackageName(),
+ rb.getSbn().getKey()));
+ assertEquals(0L, mSnoozeHelper.getSnoozeTimeForUnpostedNotification(
+ r.getUser().getIdentifier(), r.getSbn().getPackageName(),
+ r.getSbn().getKey()).longValue());
+
// once for each initial snooze, once for canceling one snooze
- verify(mAm, times(4)).cancel(any(PendingIntent.class));
+ verify(mAm, times(5)).cancel(any(PendingIntent.class));
}
private NotificationRecord getNotificationRecord(String pkg, int id, String tag,
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index fd1536c5c0f1..4550b56f6fd0 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -1622,7 +1622,9 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(si),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ // We need the package name to be something that's not "android" so there aren't any
+ // existing rules under that package.
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
assertNotNull(id);
}
try {
@@ -1632,12 +1634,41 @@ public class ZenModeHelperTest extends UiServiceTestCase {
ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
new ZenPolicy.Builder().build(),
NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
- String id = mZenModeHelperSpy.addAutomaticZenRule("android", zenRule, "test");
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
fail("allowed too many rules to be created");
} catch (IllegalArgumentException e) {
// yay
}
+ }
+ @Test
+ public void testAddAutomaticZenRule_beyondSystemLimit_differentComponents() {
+ // Make sure the system limit is enforced per-package even with different component provider
+ // names.
+ for (int i = 0; i < RULE_LIMIT_PER_PACKAGE; i++) {
+ ScheduleInfo si = new ScheduleInfo();
+ si.startHour = i;
+ AutomaticZenRule zenRule = new AutomaticZenRule("name" + i,
+ null,
+ new ComponentName("android", "ScheduleConditionProvider" + i),
+ ZenModeConfig.toScheduleConditionId(si),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ assertNotNull(id);
+ }
+ try {
+ AutomaticZenRule zenRule = new AutomaticZenRule("name",
+ null,
+ new ComponentName("android", "ScheduleConditionProviderFinal"),
+ ZenModeConfig.toScheduleConditionId(new ScheduleInfo()),
+ new ZenPolicy.Builder().build(),
+ NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+ String id = mZenModeHelperSpy.addAutomaticZenRule("pkgname", zenRule, "test");
+ fail("allowed too many rules to be created");
+ } catch (IllegalArgumentException e) {
+ // yay
+ }
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 6fafa491d0ca..3f3d01a14f80 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -29,6 +29,8 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
+import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
+import static android.content.pm.ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_ALWAYS;
import static android.content.pm.ActivityInfo.LOCK_TASK_LAUNCH_MODE_DEFAULT;
@@ -47,7 +49,6 @@ import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
import static android.os.Process.NOBODY_UID;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.InsetsState.ITYPE_IME;
-import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
@@ -182,6 +183,10 @@ public class ActivityRecordTests extends WindowTestsBase {
private final String mPackageName = getInstrumentation().getTargetContext().getPackageName();
+ private static final int ORIENTATION_CONFIG_CHANGES =
+ CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT | CONFIG_SCREEN_SIZE
+ | CONFIG_SMALLEST_SCREEN_SIZE;
+
@Before
public void setUp() throws Exception {
setBooted(mAtm);
@@ -487,7 +492,7 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testSetRequestedOrientationUpdatesConfiguration() throws Exception {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
- .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
.build();
activity.setState(RESUMED, "Testing");
@@ -710,7 +715,7 @@ public class ActivityRecordTests extends WindowTestsBase {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
.setLaunchTaskBehind(true)
- .setConfigChanges(CONFIG_ORIENTATION | CONFIG_SCREEN_LAYOUT)
+ .setConfigChanges(ORIENTATION_CONFIG_CHANGES)
.build();
final Task task = activity.getTask();
activity.setState(STOPPED, "Testing");
@@ -1160,6 +1165,45 @@ public class ActivityRecordTests extends WindowTestsBase {
assertTrue(lastTransition.allReady());
}
+ @Test
+ public void testFinishActivityIfPossible_sendResultImmediately() {
+ // Create activity representing the source of the activity result.
+ final ComponentName sourceComponent = ComponentName.createRelative(
+ DEFAULT_COMPONENT_PACKAGE_NAME, ".SourceActivity");
+ final ComponentName targetComponent = ComponentName.createRelative(
+ sourceComponent.getPackageName(), ".TargetActivity");
+
+ final ActivityRecord sourceActivity = new ActivityBuilder(mWm.mAtmService)
+ .setComponent(sourceComponent)
+ .setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
+ .setCreateTask(true)
+ .build();
+ sourceActivity.finishing = false;
+ sourceActivity.setState(STOPPED, "test");
+
+ final ActivityRecord targetActivity = new ActivityBuilder(mWm.mAtmService)
+ .setComponent(targetComponent)
+ .setTargetActivity(sourceComponent.getClassName())
+ .setLaunchMode(ActivityInfo.LAUNCH_SINGLE_INSTANCE)
+ .setCreateTask(true)
+ .setOnTop(true)
+ .build();
+ targetActivity.finishing = false;
+ targetActivity.setState(RESUMED, "test");
+ targetActivity.resultTo = sourceActivity;
+ targetActivity.setForceSendResultForMediaProjection();
+
+ clearInvocations(mAtm.getLifecycleManager());
+
+ targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
+
+ try {
+ verify(mAtm.getLifecycleManager(), atLeastOnce()).scheduleTransaction(
+ any(ClientTransaction.class));
+ } catch (RemoteException ignored) {
+ }
+ }
+
/**
* Verify that complete finish request for non-finishing activity is invalid.
*/
@@ -1786,13 +1830,16 @@ public class ActivityRecordTests extends WindowTestsBase {
public void testActivityOnCancelFixedRotationTransform() {
final ActivityRecord activity = createActivityWithTask();
final DisplayRotation displayRotation = activity.mDisplayContent.getDisplayRotation();
+ final RemoteDisplayChangeController remoteDisplayChangeController = activity
+ .mDisplayContent.mRemoteDisplayChangeController;
spyOn(displayRotation);
+ spyOn(remoteDisplayChangeController);
final DisplayContent display = activity.mDisplayContent;
final int originalRotation = display.getRotation();
// Make {@link DisplayContent#sendNewConfiguration} not apply rotation immediately.
- doReturn(true).when(displayRotation).isWaitingForRemoteRotation();
+ doReturn(true).when(remoteDisplayChangeController).isWaitingForRemoteDisplayChange();
doReturn((originalRotation + 1) % 4).when(displayRotation).rotationForOrientation(
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Set to visible so the activity can freeze the screen.
@@ -1830,7 +1877,7 @@ public class ActivityRecordTests extends WindowTestsBase {
// Simulate the remote rotation has completed and the configuration doesn't change, then
// the rotated activity should also be restored by clearing the transform.
displayRotation.updateRotationUnchecked(true /* forceUpdate */);
- doReturn(false).when(displayRotation).isWaitingForRemoteRotation();
+ doReturn(false).when(remoteDisplayChangeController).isWaitingForRemoteDisplayChange();
clearInvocations(activity);
display.setFixedRotationLaunchingAppUnchecked(activity);
display.sendNewConfiguration();
@@ -1954,7 +2001,8 @@ public class ActivityRecordTests extends WindowTestsBase {
any() /* window */, any() /* attrs */,
anyInt() /* viewVisibility */, anyInt() /* displayId */,
any() /* requestedVisibilities */, any() /* outInputChannel */,
- any() /* outInsetsState */, any() /* outActiveControls */);
+ any() /* outInsetsState */, any() /* outActiveControls */,
+ any() /* outAttachedFrame */);
mAtm.mWindowManager.mStartingSurfaceController
.createTaskSnapshotSurface(activity, snapshot);
} catch (RemoteException ignored) {
@@ -2165,39 +2213,6 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- public void testSupportsSplitScreenWindowingMode() {
- final ActivityRecord activity = new ActivityBuilder(mAtm)
- .setCreateTask(true)
- .setResizeMode(ActivityInfo.RESIZE_MODE_UNRESIZEABLE)
- .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)
- .build();
-
- // Not allow non-resizable
- mAtm.mForceResizableActivities = false;
- mAtm.mSupportsNonResizableMultiWindow = -1;
- mAtm.mDevEnableNonResizableMultiWindow = false;
- assertFalse(activity.supportsSplitScreenWindowingMode());
-
- // Force resizable
- mAtm.mForceResizableActivities = true;
- mAtm.mSupportsNonResizableMultiWindow = -1;
- mAtm.mDevEnableNonResizableMultiWindow = false;
- assertTrue(activity.supportsSplitScreenWindowingMode());
-
- // Use development option to allow non-resizable
- mAtm.mForceResizableActivities = false;
- mAtm.mSupportsNonResizableMultiWindow = -1;
- mAtm.mDevEnableNonResizableMultiWindow = true;
- assertTrue(activity.supportsSplitScreenWindowingMode());
-
- // Always allow non-resizable
- mAtm.mForceResizableActivities = false;
- mAtm.mSupportsNonResizableMultiWindow = 1;
- mAtm.mDevEnableNonResizableMultiWindow = false;
- assertTrue(activity.supportsSplitScreenWindowingMode());
- }
-
- @Test
public void testSupportsFreeform() {
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setCreateTask(true)
@@ -2523,21 +2538,6 @@ public class ActivityRecordTests extends WindowTestsBase {
}
@Test
- public void testStuckExitingWindow() {
- final WindowState closingWindow = createWindow(null, FIRST_APPLICATION_WINDOW,
- "closingWindow");
- closingWindow.mAnimatingExit = true;
- closingWindow.mRemoveOnExit = true;
- closingWindow.mActivityRecord.commitVisibility(
- false /* visible */, true /* performLayout */);
-
- // We pretended that we were running an exit animation, but that should have been cleared up
- // by changing visibility of ActivityRecord
- closingWindow.removeIfPossible();
- assertTrue(closingWindow.mRemoved);
- }
-
- @Test
public void testSetOrientation() {
final ActivityRecord activity = new ActivityBuilder(mAtm).setCreateTask(true).build();
activity.setVisible(true);
@@ -3133,6 +3133,7 @@ public class ActivityRecordTests extends WindowTestsBase {
mDisplayContent.mOpeningApps.clear();
app.mActivityRecord.commitVisibility(false, false);
app.mActivityRecord.onWindowsGone();
+ mDisplayContent.computeImeTargetIfNeeded(app.mActivityRecord);
assertTrue(app.mActivityRecord.mLastImeShown);
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
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 73e409abf0c1..1575336600b4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java
@@ -192,7 +192,7 @@ public class ActivityStartInterceptorTest {
.thenReturn(PLATFORM_PACKAGE_NAME);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
// THEN the returned intent is the admin support intent
assertEquals(ADMIN_SUPPORT_INTENT, mInterceptor.mIntent);
@@ -203,7 +203,7 @@ public class ActivityStartInterceptorTest {
final String suspendingPackage = "com.test.suspending.package";
final SuspendDialogInfo dialogInfo = suspendPackage(suspendingPackage);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
// Check intent parameters
assertEquals(dialogInfo,
@@ -234,7 +234,7 @@ public class ActivityStartInterceptorTest {
TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT))
.thenReturn(false);
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
assertTrue(BlockedAppActivity.createIntent(TEST_USER_ID, TEST_PACKAGE_NAME)
.filterEquals(mInterceptor.mIntent));
@@ -246,7 +246,7 @@ public class ActivityStartInterceptorTest {
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ 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)
@@ -260,7 +260,7 @@ public class ActivityStartInterceptorTest {
when(mUserManager.isQuietModeEnabled(eq(UserHandle.of(TEST_USER_ID)))).thenReturn(true);
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ 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)
@@ -273,7 +273,7 @@ public class ActivityStartInterceptorTest {
when(mAmInternal.shouldConfirmCredentials(TEST_USER_ID)).thenReturn(true);
// THEN calling intercept returns true
- mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null);
+ mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null);
// THEN the returned intent is the quiet mode intent
assertTrue(CONFIRM_CREDENTIALS_INTENT.filterEquals(mInterceptor.mIntent));
@@ -286,7 +286,7 @@ public class ActivityStartInterceptorTest {
.thenReturn("This app is bad");
// THEN calling intercept returns true
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
// THEN the returned intent is the harmful app warning intent
assertEquals(HarmfulAppWarningActivity.class.getName(),
@@ -298,7 +298,7 @@ public class ActivityStartInterceptorTest {
// GIVEN that none of the interception conditions are met
// THEN calling intercept returns false
- assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
}
public void addMockInterceptorCallback(
@@ -323,7 +323,7 @@ public class ActivityStartInterceptorTest {
new Intent("android.test.foo"),
ActivityOptions.makeBasic().setLaunchDisplayId(3));
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
assertEquals("android.test.foo", mInterceptor.mIntent.getAction());
assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId());
}
@@ -332,7 +332,7 @@ public class ActivityStartInterceptorTest {
public void testInterceptionCallback_singleCallbackReturnsNull() {
addMockInterceptorCallback(null, null);
- assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
}
@Test
@@ -340,7 +340,7 @@ public class ActivityStartInterceptorTest {
addMockInterceptorCallback(null, null);
addMockInterceptorCallback(new Intent("android.test.second"), null);
- assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null));
+ assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, null, 0, 0, null));
assertEquals("android.test.second", mInterceptor.mIntent.getAction());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 4ca14ddbd96f..c78bc59612d4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -802,7 +802,7 @@ public class ActivityStarterTests extends WindowTestsBase {
// Create adjacent tasks and put one activity under it
final Task parent = new TaskBuilder(mSupervisor).build();
final Task adjacentParent = new TaskBuilder(mSupervisor).build();
- parent.setAdjacentTaskFragment(adjacentParent, true);
+ parent.setAdjacentTaskFragment(adjacentParent);
final ActivityRecord activity = new ActivityBuilder(mAtm)
.setParentTask(parent)
.setCreateTask(true).build();
@@ -1128,28 +1128,27 @@ public class ActivityStarterTests extends WindowTestsBase {
}
@Test
- public void testTargetStackInSplitScreen() {
+ public void testTargetTaskInSplitScreen() {
final ActivityStarter starter =
prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */);
final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build();
final ActivityOptions options = ActivityOptions.makeBasic();
final ActivityRecord[] outActivity = new ActivityRecord[1];
- // Activity must not land on split-screen stack if currently not in split-screen mode.
+ // Activity must not land on split-screen task if currently not in split-screen mode.
starter.setActivityOptions(options.toBundle())
- .setReason("testWindowingModeOptionsLaunchAdjacent")
+ .setReason("testTargetTaskInSplitScreen")
.setOutActivity(outActivity).execute();
assertThat(outActivity[0].inMultiWindowMode()).isFalse();
- // Move activity to split-screen-primary stack and make sure it has the focus.
+ // Move activity to split-screen-primary task and make sure it has the focus.
TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent());
top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM);
- top.getRootTask().moveToFront("testWindowingModeOptionsLaunchAdjacent");
+ top.getRootTask().moveToFront("testTargetTaskInSplitScreen");
- // Activity must landed on split-screen-secondary when launch adjacent.
- starter.setActivityOptions(options.toBundle())
- .setReason("testWindowingModeOptionsLaunchAdjacent")
- .setOutActivity(outActivity).execute();
+ // Activity must land on split-screen-secondary when launch adjacent.
+ startActivityInner(starter, outActivity[0], top, options, null /* inTask */,
+ null /* taskFragment*/);
assertThat(outActivity[0].inMultiWindowMode()).isTrue();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
index a8571906bb06..20b1120d7d3e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java
@@ -21,7 +21,6 @@ import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
@@ -42,8 +41,8 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
@@ -60,7 +59,6 @@ import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.LocaleList;
-import android.os.PowerManager;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.Display;
@@ -136,7 +134,7 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
assertNull(transaction.getLifecycleStateRequest());
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void testOnPictureInPictureRequested_cannotEnterPip() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
@@ -146,11 +144,16 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
- // Check enter no transactions with enter pip requests are made.
- verify(lifecycleManager, times(0)).scheduleTransaction(any());
+ verify(lifecycleManager, atLeast(0))
+ .scheduleTransaction(mClientTransactionCaptor.capture());
+ final ClientTransaction transaction = mClientTransactionCaptor.getValue();
+ // Check that none are enter pip request items.
+ transaction.getCallbacks().forEach(clientTransactionItem -> {
+ assertFalse(clientTransactionItem instanceof EnterPipRequestedItem);
+ });
}
- @Test(expected = IllegalStateException.class)
+ @Test
public void testOnPictureInPictureRequested_alreadyInPIPMode() throws RemoteException {
final Task stack = new TaskBuilder(mSupervisor).setCreateActivity(true).build();
final ActivityRecord activity = stack.getBottomMostTask().getTopNonFinishingActivity();
@@ -159,8 +162,13 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
mAtm.mActivityClientController.requestPictureInPictureMode(activity);
- // Check that no transactions with enter pip requests are made.
- verify(lifecycleManager, times(0)).scheduleTransaction(any());
+ verify(lifecycleManager, atLeast(0))
+ .scheduleTransaction(mClientTransactionCaptor.capture());
+ final ClientTransaction transaction = mClientTransactionCaptor.getValue();
+ // Check that none are enter pip request items.
+ transaction.getCallbacks().forEach(clientTransactionItem -> {
+ assertFalse(clientTransactionItem instanceof EnterPipRequestedItem);
+ });
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
index 716612c70aef..75ecfd870eb2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskSupervisorTests.java
@@ -21,6 +21,7 @@ import static android.app.ActivityManager.START_TASK_TO_FRONT;
import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
@@ -34,6 +35,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
@@ -284,10 +286,14 @@ public class ActivityTaskSupervisorTests extends WindowTestsBase {
.setCreateActivity(true).build().getTopMostActivity();
activity2.getTask().setResumedActivity(activity2, "test");
- mAtm.mAmInternal.deletePendingTopUid(activity1.getUid(), Long.MAX_VALUE);
+ final int[] pendingTopUid = new int[1];
+ doAnswer(invocation -> {
+ pendingTopUid[0] = invocation.getArgument(0);
+ return null;
+ }).when(mAtm.mAmInternal).addPendingTopUid(anyInt(), anyInt(), any());
clearInvocations(mAtm);
activity1.moveFocusableActivityToTop("test");
- assertTrue(mAtm.mAmInternal.isPendingTopUid(activity1.getUid()));
+ assertEquals(activity1.getUid(), pendingTopUid[0]);
verify(mAtm).updateOomAdj();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 890a5478602a..513791d2b8a5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -16,6 +16,7 @@
package com.android.server.wm;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
@@ -25,6 +26,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CHANGE;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OLD_ACTIVITY_OPEN;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_DREAM_ACTIVITY_OPEN;
import static android.view.WindowManager.TRANSIT_OLD_KEYGUARD_UNOCCLUDE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_CHANGE_WINDOWING_MODE;
import static android.view.WindowManager.TRANSIT_OLD_TASK_FRAGMENT_CHANGE;
@@ -44,6 +47,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.clearInvocations;
@@ -86,6 +90,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
@Before
public void setUp() throws Exception {
+ assumeFalse(WindowManagerService.sEnableShellTransitions);
mAppTransitionController = new AppTransitionController(mWm, mDisplayContent);
}
@@ -155,6 +160,32 @@ public class AppTransitionControllerTest extends WindowTestsBase {
}
@Test
+ public void testDreamActivityOpenTransition() {
+ final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
+ mDisplayContent.prepareAppTransition(TRANSIT_OPEN);
+ mDisplayContent.mOpeningApps.add(dreamActivity);
+
+ assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_OPEN,
+ AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null, null, false));
+ }
+
+ @Test
+ public void testDreamActivityCloseTransition() {
+ final ActivityRecord dreamActivity = createActivityRecord(mDisplayContent,
+ WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_DREAM);
+ mDisplayContent.prepareAppTransition(TRANSIT_CLOSE);
+ mDisplayContent.mClosingApps.add(dreamActivity);
+
+ assertEquals(TRANSIT_OLD_DREAM_ACTIVITY_CLOSE,
+ AppTransitionController.getTransitCompatType(mDisplayContent.mAppTransition,
+ mDisplayContent.mOpeningApps, mDisplayContent.mClosingApps,
+ mDisplayContent.mChangingContainers, null, null, false));
+ }
+
+ @Test
public void testChangeIsNotOverwritten() {
final ActivityRecord behind = createActivityRecord(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
@@ -564,7 +595,7 @@ public class AppTransitionControllerTest extends WindowTestsBase {
.setCreatedByOrganizer(true);
final Task splitRoot1 = builder.build();
final Task splitRoot2 = builder.build();
- splitRoot1.setAdjacentTaskFragment(splitRoot2, false /* moveTogether */);
+ splitRoot1.setAdjacentTaskFragment(splitRoot2);
final ActivityRecord activity1 = createActivityRecordWithParentTask(splitRoot1);
activity1.setVisible(false);
activity1.mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 07923005c45e..d6b807fc223e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
import static android.content.pm.ActivityInfo.FLAG_SHOW_WHEN_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -46,8 +47,10 @@ import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
@@ -77,10 +80,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING;
-import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_TOKEN_TRANSFORM;
import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS;
-import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION;
import static com.android.server.wm.WindowContainer.POSITION_TOP;
import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
@@ -123,8 +124,8 @@ import android.view.ContentRecordingSession;
import android.view.DisplayCutout;
import android.view.DisplayInfo;
import android.view.Gravity;
-import android.view.IDisplayWindowRotationCallback;
-import android.view.IDisplayWindowRotationController;
+import android.view.IDisplayChangeWindowCallback;
+import android.view.IDisplayChangeWindowController;
import android.view.ISystemGestureExclusionListener;
import android.view.IWindowManager;
import android.view.InsetsState;
@@ -135,6 +136,7 @@ import android.view.SurfaceControl;
import android.view.SurfaceControl.Transaction;
import android.view.View;
import android.view.WindowManager;
+import android.window.DisplayAreaInfo;
import android.window.IDisplayAreaOrganizer;
import android.window.WindowContainerToken;
@@ -641,6 +643,7 @@ public class DisplayContentTests extends WindowTestsBase {
final DisplayContent dc = mDisplayContent;
final WindowState ws = createWindow(null, TYPE_APPLICATION, dc, "app window");
dc.setImeLayeringTarget(ws);
+ dc.setImeInputTarget(ws);
// Adjust bounds so that matchesRootDisplayAreaBounds() returns false.
final Rect bounds = new Rect(dc.getBounds());
@@ -1097,6 +1100,25 @@ public class DisplayContentTests extends WindowTestsBase {
}
@Test
+ public void testOrientationBehind() {
+ final ActivityRecord prev = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setScreenOrientation(getRotatedOrientation(mDisplayContent)).build();
+ prev.mVisibleRequested = false;
+ final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true)
+ .setScreenOrientation(SCREEN_ORIENTATION_BEHIND).build();
+ assertNotEquals(WindowConfiguration.ROTATION_UNDEFINED,
+ mDisplayContent.rotationForActivityInDifferentOrientation(top));
+
+ mDisplayContent.requestTransitionAndLegacyPrepare(WindowManager.TRANSIT_OPEN, 0);
+ top.setVisibility(true);
+ mDisplayContent.updateOrientation();
+ // The top uses "behind", so the orientation is decided by the previous.
+ assertEquals(prev, mDisplayContent.getLastOrientationSource());
+ // The top will use the rotation from "prev" with fixed rotation.
+ assertTrue(top.hasFixedRotationTransform());
+ }
+
+ @Test
public void testFixedToUserRotationChanged() {
final DisplayContent dc = createNewDisplay();
dc.getDisplayRotation().setFixedToUserRotation(
@@ -1133,6 +1155,7 @@ public class DisplayContentTests extends WindowTestsBase {
dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "app"));
dc.getImeTarget(IME_TARGET_LAYERING).getWindow().setWindowingMode(
WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
+ dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow());
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
@@ -1151,6 +1174,7 @@ public class DisplayContentTests extends WindowTestsBase {
public void testComputeImeParent_noApp() throws Exception {
final DisplayContent dc = createNewDisplay();
dc.setImeLayeringTarget(createWindow(null, TYPE_STATUS_BAR, "statusBar"));
+ dc.setImeInputTarget(dc.getImeTarget(IME_TARGET_LAYERING).getWindow());
assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
}
@@ -1380,6 +1404,28 @@ public class DisplayContentTests extends WindowTestsBase {
win.setHasSurface(false);
}
+ @Test
+ public void testCalculateSystemGestureExclusion_unrestricted() throws Exception {
+ mWm.mConstants.mSystemGestureExcludedByPreQStickyImmersive = true;
+
+ final DisplayContent dc = createNewDisplay();
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, dc, "win");
+ win.getAttrs().flags |= FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR;
+ win.getAttrs().layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
+ win.getAttrs().privateFlags |= PRIVATE_FLAG_UNRESTRICTED_GESTURE_EXCLUSION;
+ win.setSystemGestureExclusion(Collections.singletonList(dc.getBounds()));
+
+ performLayout(dc);
+
+ win.setHasSurface(true);
+
+ final Region expected = Region.obtain();
+ expected.set(dc.getBounds());
+ assertEquals(expected, calculateSystemGestureExclusion(dc));
+
+ win.setHasSurface(false);
+ }
+
@UseTestDisplay(addWindows = { W_ABOVE_ACTIVITY, W_ACTIVITY})
@Test
public void testRequestResizeForEmptyFrames() {
@@ -1593,14 +1639,14 @@ public class DisplayContentTests extends WindowTestsBase {
final Task task = app.getTask();
final ActivityRecord app2 = new ActivityBuilder(mWm.mAtmService).setTask(task).build();
mDisplayContent.setFixedRotationLaunchingApp(app2, (mDisplayContent.getRotation() + 1) % 4);
- doReturn(true).when(task).isAppTransitioning();
- // If the task is animating transition, this should be no-op.
+ doReturn(true).when(app).isInTransition();
+ // If the task contains a transition, this should be no-op.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
assertTrue(app2.hasFixedRotationTransform());
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
- doReturn(false).when(task).isAppTransitioning();
+ doReturn(false).when(app).isInTransition();
// Although this notifies app instead of app2 that uses the fixed rotation, app2 should
// still finish the transform because there is no more transition event.
mDisplayContent.mFixedRotationTransitionListener.onAppTransitionFinishedLocked(app.token);
@@ -1677,6 +1723,13 @@ public class DisplayContentTests extends WindowTestsBase {
assertFalse(displayContent.mPinnedTaskController.isFreezingTaskConfig(pinnedTask));
assertEquals(pinnedActivity.getConfiguration().orientation,
displayContent.getConfiguration().orientation);
+
+ // No need to apply rotation if the display ignores orientation request.
+ doCallRealMethod().when(displayContent).rotationForActivityInDifferentOrientation(any());
+ pinnedActivity.mOrientation = SCREEN_ORIENTATION_LANDSCAPE;
+ displayContent.setIgnoreOrientationRequest(true);
+ assertEquals(WindowConfiguration.ROTATION_UNDEFINED,
+ displayContent.rotationForActivityInDifferentOrientation(pinnedActivity));
}
@Test
@@ -1693,8 +1746,7 @@ public class DisplayContentTests extends WindowTestsBase {
// The condition should reject using fixed rotation because the resumed client in real case
// might get display info immediately. And the fixed rotation adjustments haven't arrived
// client side so the info may be inconsistent with the requested orientation.
- verify(mDisplayContent).handleTopActivityLaunchingInDifferentOrientation(eq(app),
- eq(true) /* checkOpening */);
+ verify(mDisplayContent).updateOrientation(eq(app), anyBoolean());
assertFalse(app.isFixedRotationTransforming());
assertFalse(mDisplayContent.hasTopFixedRotationLaunchingApp());
}
@@ -1782,15 +1834,16 @@ public class DisplayContentTests extends WindowTestsBase {
return true;
}).when(dc).updateDisplayOverrideConfigurationLocked();
final boolean[] called = new boolean[1];
- mWm.mDisplayRotationController =
- new IDisplayWindowRotationController.Stub() {
+ mWm.mDisplayChangeController =
+ new IDisplayChangeWindowController.Stub() {
@Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- IDisplayWindowRotationCallback callback) {
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo,
+ IDisplayChangeWindowCallback callback) throws RemoteException {
called[0] = true;
try {
- callback.continueRotateDisplay(toRotation, null);
+ callback.continueDisplayChange(null);
} catch (RemoteException e) {
assertTrue(false);
}
@@ -1824,13 +1877,14 @@ public class DisplayContentTests extends WindowTestsBase {
// Rotate 180 degree so the display doesn't have configuration change. This condition is
// used for the later verification of stop-freezing (without setting mWaitingForConfig).
doReturn((dr.getRotation() + 2) % 4).when(dr).rotationForOrientation(anyInt(), anyInt());
- mWm.mDisplayRotationController =
- new IDisplayWindowRotationController.Stub() {
+ mWm.mDisplayChangeController =
+ new IDisplayChangeWindowController.Stub() {
@Override
- public void onRotateDisplay(int displayId, int fromRotation, int toRotation,
- IDisplayWindowRotationCallback callback) {
+ public void onDisplayChange(int displayId, int fromRotation, int toRotation,
+ DisplayAreaInfo newDisplayAreaInfo,
+ IDisplayChangeWindowCallback callback) throws RemoteException {
try {
- callback.continueRotateDisplay(toRotation, null);
+ callback.continueDisplayChange(null);
} catch (RemoteException e) {
assertTrue(false);
}
@@ -2088,7 +2142,6 @@ public class DisplayContentTests extends WindowTestsBase {
final WindowState appWin2 = createWindow(null, TYPE_BASE_APPLICATION, act2, "appWin2");
appWin2.setHasSurface(true);
assertTrue(appWin2.canBeImeTarget());
- doReturn(true).when(appWin1).isClosing();
doReturn(true).when(appWin1).inTransitionSelfOrParent();
// Test step 3: Verify appWin2 will be the next IME target and the IME snapshot surface will
@@ -2154,6 +2207,52 @@ public class DisplayContentTests extends WindowTestsBase {
assertNotEquals(curSnapshot, mDisplayContent.mImeScreenshot);
}
+ @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+ @Test
+ public void testRemoveImeScreenshot_whenTargetSurfaceWasInvisible() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ win.onSurfaceShownChanged(true);
+ makeWindowVisible(win, mDisplayContent.mInputMethodWindow);
+ task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE);
+ doReturn(true).when(task).okToAnimate();
+ ArrayList<WindowContainer> sources = new ArrayList<>();
+ sources.add(activity);
+
+ mDisplayContent.setImeLayeringTarget(win);
+ mDisplayContent.setImeInputTarget(win);
+ mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
+ task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */,
+ false /* isVoiceInteraction */, sources);
+ assertNotNull(mDisplayContent.mImeScreenshot);
+
+ win.onSurfaceShownChanged(false);
+ assertNull(mDisplayContent.mImeScreenshot);
+ }
+
+ @UseTestDisplay(addWindows = {W_INPUT_METHOD})
+ @Test
+ public void testRemoveImeScreenshot_whenWindowRemoveImmediately() {
+ final Task rootTask = createTask(mDisplayContent);
+ final Task task = createTaskInRootTask(rootTask, 0 /* userId */);
+ final ActivityRecord activity = createActivityRecord(mDisplayContent, task);
+ final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win");
+ makeWindowVisible(mDisplayContent.mInputMethodWindow);
+
+ mDisplayContent.setImeLayeringTarget(win);
+ mDisplayContent.setImeInputTarget(win);
+ mDisplayContent.getInsetsStateController().getImeSourceProvider().setImeShowing(true);
+ mDisplayContent.showImeScreenshot();
+ assertNotNull(mDisplayContent.mImeScreenshot);
+
+ // Expect IME snapshot will be removed when the win is IME layering target and invoked
+ // removeImeSurfaceByTarget.
+ win.removeImmediately();
+ assertNull(mDisplayContent.mImeScreenshot);
+ }
+
@Test
public void testRotateBounds_keepSamePhysicalPosition() {
final DisplayContent dc =
@@ -2193,23 +2292,28 @@ public class DisplayContentTests extends WindowTestsBase {
*/
@Test
public void testCreateTestDisplayContentFromDimensions() {
- final int displayWidth = 1000;
- final int displayHeight = 2000;
+ final int displayWidth = 540;
+ final int displayHeight = 960;
+ final int density = 192;
+ final int expectedWidthDp = 450; // = 540/(192/160)
+ final int expectedHeightDp = 800; // = 960/(192/160)
final int windowingMode = WINDOWING_MODE_FULLSCREEN;
final boolean ignoreOrientationRequests = false;
final float fixedOrientationLetterboxRatio = 0;
final DisplayContent testDisplayContent = new TestDisplayContent.Builder(mAtm, displayWidth,
- displayHeight).build();
+ displayHeight).setDensityDpi(density).build();
// test display info
final DisplayInfo di = testDisplayContent.getDisplayInfo();
assertEquals(displayWidth, di.logicalWidth);
assertEquals(displayHeight, di.logicalHeight);
- assertEquals(TestDisplayContent.DEFAULT_LOGICAL_DISPLAY_DENSITY, di.logicalDensityDpi);
+ assertEquals(density, di.logicalDensityDpi);
// test configuration
- final WindowConfiguration windowConfig = testDisplayContent.getConfiguration()
- .windowConfiguration;
+ final Configuration config = testDisplayContent.getConfiguration();
+ assertEquals(expectedWidthDp, config.screenWidthDp);
+ assertEquals(expectedHeightDp, config.screenHeightDp);
+ final WindowConfiguration windowConfig = config.windowConfiguration;
assertEquals(displayWidth, windowConfig.getBounds().width());
assertEquals(displayHeight, windowConfig.getBounds().height());
assertEquals(windowingMode, windowConfig.getWindowingMode());
@@ -2396,7 +2500,7 @@ public class DisplayContentTests extends WindowTestsBase {
@Test
public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() {
final WindowState imeMenuDialog =
- createWindow(mImeWindow, TYPE_INPUT_METHOD_DIALOG, "imeMenuDialog");
+ createWindow(null, TYPE_INPUT_METHOD_DIALOG, "imeMenuDialog");
makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow);
assertTrue(imeMenuDialog.canReceiveKeys());
mDisplayContent.setInputMethodWindowLocked(mImeWindow);
@@ -2409,13 +2513,11 @@ public class DisplayContentTests extends WindowTestsBase {
doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME);
assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
- // Verify imeMenuDialog doesn't be focused window if the next IME target does not
- // request IME visible.
+ // Verify imeMenuDialog doesn't be focused window if the next IME target is closing.
final WindowState nextImeAppTarget =
createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget");
- spyOn(nextImeAppTarget);
- doReturn(true).when(nextImeAppTarget).isAnimating(PARENTS | TRANSITION,
- ANIMATION_TYPE_APP_TRANSITION);
+ makeWindowVisibleAndDrawn(nextImeAppTarget);
+ nextImeAppTarget.mActivityRecord.commitVisibility(false, false);
mDisplayContent.setImeLayeringTarget(nextImeAppTarget);
assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow());
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 45ae81a71c44..34575ae2fe46 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -43,6 +43,7 @@ import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;
import android.view.DisplayInfo;
+import android.view.InsetsFrameProvider;
import android.view.InsetsState;
import android.view.PrivacyIndicatorBounds;
import android.view.RoundedCorners;
@@ -153,7 +154,10 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one.
WindowState win = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
- win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_TOP_GESTURES};
+ win.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR),
+ new InsetsFrameProvider(ITYPE_TOP_GESTURES)
+ };
win.getFrame().set(0, 0, 500, 100);
addWindow(win);
@@ -182,7 +186,9 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
public void addingWindow_InWindowTypeWithPredefinedInsets() {
mDisplayPolicy.removeWindowLw(mStatusBarWindow); // Removes the existing one.
WindowState win = createWindow(null, TYPE_STATUS_BAR, "StatusBar");
- win.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR};
+ win.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR)
+ };
win.getFrame().set(0, 0, 500, 100);
addWindow(win);
@@ -199,12 +205,19 @@ public class DisplayPolicyLayoutTests extends DisplayPolicyTestsBase {
@Test
public void addingWindow_throwsException_WithMultipleInsetTypes() {
WindowState win1 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
- win1.mAttrs.providesInsetsTypes = new int[]{ITYPE_STATUS_BAR, ITYPE_NAVIGATION_BAR};
+ win1.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_STATUS_BAR),
+ new InsetsFrameProvider(ITYPE_NAVIGATION_BAR)
+ };
expectThrows(IllegalArgumentException.class, () -> addWindow(win1));
WindowState win2 = createWindow(null, TYPE_STATUS_BAR_SUB_PANEL, "StatusBarSubPanel");
- win2.mAttrs.providesInsetsTypes = new int[]{ITYPE_CLIMATE_BAR, ITYPE_EXTRA_NAVIGATION_BAR};
+
+ win2.mAttrs.providedInsets = new InsetsFrameProvider[] {
+ new InsetsFrameProvider(ITYPE_CLIMATE_BAR),
+ new InsetsFrameProvider(ITYPE_EXTRA_NAVIGATION_BAR)
+ };
expectThrows(IllegalArgumentException.class, () -> addWindow(win2));
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
index dbb7fae548b7..2956c14155b9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DualDisplayAreaGroupPolicyTest.java
@@ -168,7 +168,8 @@ public class DualDisplayAreaGroupPolicyTest extends WindowTestsBase {
mSecondRoot.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
mDisplay.onLastFocusedTaskDisplayAreaChanged(mFirstTda);
- prepareUnresizable(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT);
+ prepareLimitedBounds(mFirstActivity, SCREEN_ORIENTATION_PORTRAIT,
+ false /* isUnresizable */);
final Rect dagBounds = new Rect(mFirstRoot.getBounds());
final Rect taskBounds = new Rect(mFirstTask.getBounds());
final Rect activityBounds = new Rect(mFirstActivity.getBounds());
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index ffa21fadff6b..6c161cf088f7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -27,6 +27,7 @@ import static android.view.InsetsState.ITYPE_STATUS_BAR;
import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -44,12 +45,15 @@ import static org.mockito.Mockito.verify;
import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
+import android.util.SparseArray;
import android.view.InsetsSourceControl;
import android.view.InsetsState;
import android.view.InsetsVisibilities;
import androidx.test.filters.SmallTest;
+import com.android.internal.util.function.TriConsumer;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -269,15 +273,18 @@ public class InsetsStateControllerTest extends WindowTestsBase {
@Test
public void testImeForDispatch() {
final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
- final WindowState ime = createWindow(null, TYPE_APPLICATION, "ime");
+ final WindowState ime = createWindow(null, TYPE_INPUT_METHOD, "ime");
// IME cannot be the IME target.
ime.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
WindowContainerInsetsSourceProvider statusBarProvider =
getController().getSourceProvider(ITYPE_STATUS_BAR);
- statusBarProvider.setWindowContainer(statusBar, null, ((displayFrames, windowState, rect) ->
+ final SparseArray<TriConsumer<DisplayFrames, WindowContainer, Rect>> imeOverrideProviders =
+ new SparseArray<>();
+ imeOverrideProviders.put(TYPE_INPUT_METHOD, ((displayFrames, windowState, rect) ->
rect.set(0, 1, 2, 3)));
+ statusBarProvider.setWindowContainer(statusBar, null, imeOverrideProviders);
getController().getSourceProvider(ITYPE_IME).setWindowContainer(ime, null, null);
statusBar.setControllableInsetProvider(statusBarProvider);
statusBar.updateSourceFrame(statusBar.getFrame());
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
index 1e86522a2307..e502f2fbd173 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxTest.java
@@ -63,7 +63,7 @@ public class LetterboxTest {
mLetterbox = new Letterbox(mSurfaces, StubTransaction::new,
() -> mAreCornersRounded, () -> Color.valueOf(mColor),
() -> mHasWallpaperBackground, () -> mBlurRadius, () -> mDarkScrimAlpha,
- /* doubleTapCallback= */ x -> {});
+ /* doubleTapCallbackX= */ x -> {}, /* doubleTapCallbackY= */ y -> {});
mTransaction = spy(StubTransaction.class);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
index feb1c6fa6b5c..83f375f85fa3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentTasksTest.java
@@ -1229,7 +1229,6 @@ public class RecentTasksTest extends WindowTestsBase {
RecentTaskInfo info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
- assertTrue(info.supportsSplitScreenMultiWindow);
// The task can be put in split screen even if it is not attached now.
task.removeImmediately();
@@ -1237,7 +1236,6 @@ public class RecentTasksTest extends WindowTestsBase {
info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
- assertTrue(info.supportsSplitScreenMultiWindow);
// Test non-resizable.
// The non-resizable task cannot be put in split screen because of the config.
@@ -1247,7 +1245,6 @@ public class RecentTasksTest extends WindowTestsBase {
info = mRecentTasks.createRecentTaskInfo(task, true);
assertFalse(info.supportsMultiWindow);
- assertFalse(info.supportsSplitScreenMultiWindow);
// Even if it is not attached, the non-resizable task can be put in split screen as long as
// the device supports it.
@@ -1256,8 +1253,6 @@ public class RecentTasksTest extends WindowTestsBase {
info = mRecentTasks.createRecentTaskInfo(task, true);
assertTrue(info.supportsMultiWindow);
- assertTrue(info.supportsSplitScreenMultiWindow);
-
}
private TaskSnapshot createSnapshot(Point taskSize, Point bufferSize) {
@@ -1337,7 +1332,7 @@ public class RecentTasksTest extends WindowTestsBase {
});
assertSecurityException(expectCallable,
() -> mAtm.startActivityFromRecents(0, new Bundle()));
- assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true));
+ assertSecurityException(expectCallable, () -> mAtm.getTaskSnapshot(0, true, false));
assertSecurityException(expectCallable, () -> mAtm.registerTaskStackListener(null));
assertSecurityException(expectCallable,
() -> mAtm.unregisterTaskStackListener(null));
diff --git a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
index 1b19a28a9790..a1d6a5006fef 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RecentsAnimationTest.java
@@ -37,13 +37,13 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
-import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -91,9 +91,15 @@ public class RecentsAnimationTest extends WindowTestsBase {
TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea();
Task recentsStack = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,
ACTIVITY_TYPE_RECENTS, true /* onTop */);
+ final WindowProcessController wpc = mSystemServicesTestRule.addProcess(
+ mRecentsComponent.getPackageName(), mRecentsComponent.getPackageName(),
+ // Use real pid/uid of the test so the corresponding process can be mapped by
+ // Binder.getCallingPid/Uid.
+ WindowManagerService.MY_PID, WindowManagerService.MY_UID);
ActivityRecord recentActivity = new ActivityBuilder(mAtm)
.setComponent(mRecentsComponent)
.setTask(recentsStack)
+ .setUseProcess(wpc)
.build();
ActivityRecord topActivity = new ActivityBuilder(mAtm).setCreateTask(true).build();
topActivity.getRootTask().moveToFront("testRecentsActivityVisiblility");
@@ -106,11 +112,14 @@ public class RecentsAnimationTest extends WindowTestsBase {
mRecentsComponent, true /* getRecentsAnimation */);
// The launch-behind state should make the recents activity visible.
assertTrue(recentActivity.mVisibleRequested);
+ assertEquals(ActivityTaskManagerService.DEMOTE_TOP_REASON_ANIMATING_RECENTS,
+ mAtm.mDemoteTopAppReasons);
// Simulate the animation is cancelled without changing the stack order.
recentsAnimation.onAnimationFinished(REORDER_KEEP_IN_PLACE, false /* sendUserLeaveHint */);
// The non-top recents activity should be invisible by the restored launch-behind state.
assertFalse(recentActivity.mVisibleRequested);
+ assertEquals(0, mAtm.mDemoteTopAppReasons);
}
@Test
@@ -138,11 +147,8 @@ public class RecentsAnimationTest extends WindowTestsBase {
anyInt() /* startFlags */, any() /* profilerInfo */);
// Assume its process is alive because the caller should be the recents service.
- WindowProcessController wpc = new WindowProcessController(mAtm, aInfo.applicationInfo,
- aInfo.processName, aInfo.applicationInfo.uid, 0 /* userId */,
- mock(Object.class) /* owner */, mock(WindowProcessListener.class));
- wpc.setThread(mock(IApplicationThread.class));
- doReturn(wpc).when(mAtm).getProcessController(eq(wpc.mName), eq(wpc.mUid));
+ mSystemServicesTestRule.addProcess(aInfo.packageName, aInfo.processName, 12345 /* pid */,
+ aInfo.applicationInfo.uid);
Intent recentsIntent = new Intent().setComponent(mRecentsComponent);
// Null animation indicates to preload.
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index e6910c2c0eca..027f5218f820 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -79,19 +79,25 @@ import org.mockito.MockitoAnnotations;
/**
* Build/Install/Run:
- * atest WmTests:RemoteAnimationControllerTest
+ * atest WmTests:RemoteAnimationControllerTest
*/
@SmallTest
@Presubmit
@RunWith(WindowTestRunner.class)
public class RemoteAnimationControllerTest extends WindowTestsBase {
- @Mock SurfaceControl mMockLeash;
- @Mock SurfaceControl mMockThumbnailLeash;
- @Mock Transaction mMockTransaction;
- @Mock OnAnimationFinishedCallback mFinishedCallback;
- @Mock OnAnimationFinishedCallback mThumbnailFinishedCallback;
- @Mock IRemoteAnimationRunner mMockRunner;
+ @Mock
+ SurfaceControl mMockLeash;
+ @Mock
+ SurfaceControl mMockThumbnailLeash;
+ @Mock
+ Transaction mMockTransaction;
+ @Mock
+ OnAnimationFinishedCallback mFinishedCallback;
+ @Mock
+ OnAnimationFinishedCallback mThumbnailFinishedCallback;
+ @Mock
+ IRemoteAnimationRunner mMockRunner;
private RemoteAnimationAdapter mAdapter;
private RemoteAnimationController mController;
private final OffsettableClock mClock = new OffsettableClock.Stopped();
@@ -105,7 +111,8 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
mAdapter = new RemoteAnimationAdapter(mMockRunner, 100, 50, true /* changeNeedsSnapshot */);
mAdapter.setCallingPidUid(123, 456);
runWithScissors(mWm.mH, () -> mHandler = new TestHandler(null, mClock), 0);
- mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter, mHandler);
+ mController = new RemoteAnimationController(mWm, mDisplayContent, mAdapter,
+ mHandler, false /*isActivityEmbedding*/);
}
private WindowState createAppOverlayWindow() {
@@ -117,13 +124,47 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
}
@Test
+ public void testForwardsShowBackdrop() throws Exception {
+ final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
+ mDisplayContent.mOpeningApps.add(win.mActivityRecord);
+ final WindowState overlayWin = createAppOverlayWindow();
+ try {
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null,
+ true /* showBackdrop */).mAdapter;
+ adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
+ mFinishedCallback);
+ mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
+ mWm.mAnimator.executeAfterPrepareSurfacesRunnables();
+ final ArgumentCaptor<RemoteAnimationTarget[]> appsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> wallpapersCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<RemoteAnimationTarget[]> nonAppsCaptor =
+ ArgumentCaptor.forClass(RemoteAnimationTarget[].class);
+ final ArgumentCaptor<IRemoteAnimationFinishedCallback> finishedCaptor =
+ ArgumentCaptor.forClass(IRemoteAnimationFinishedCallback.class);
+ verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_ACTIVITY_OPEN),
+ appsCaptor.capture(), wallpapersCaptor.capture(), nonAppsCaptor.capture(),
+ finishedCaptor.capture());
+ assertEquals(1, appsCaptor.getValue().length);
+ final RemoteAnimationTarget app = appsCaptor.getValue()[0];
+ assertTrue(app.showBackdrop);
+ } finally {
+ mDisplayContent.mOpeningApps.clear();
+ }
+ }
+
+ @Test
public void testRun() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
final WindowState overlayWin = createAppOverlayWindow();
try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -162,8 +203,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testCancel() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -175,8 +217,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testTimeout() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -197,7 +240,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
"testWin");
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150),
- null).mAdapter;
+ null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -229,7 +272,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
public void testNotReallyStarted() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null);
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
verify(mMockRunner, never()).onAnimationStart(anyInt(), any(), any(), any(), any());
verify(mMockRunner).onAnimationCancelled(anyBoolean());
@@ -240,9 +283,10 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1");
final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2");
mController.createRemoteAnimationRecord(win1.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null);
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win2.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false);
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win2.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -265,8 +309,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
@Test
public void testRemovedBeforeStarted() throws Exception {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
win.mActivityRecord.removeImmediately();
@@ -310,7 +355,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
try {
final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(50, 100), null, new Rect(50, 100, 150, 150),
- new Rect(0, 0, 200, 200));
+ new Rect(0, 0, 200, 200), false);
assertNotNull(record.mThumbnailAdapter);
((AnimationAdapter) record.mAdapter)
.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
@@ -364,7 +409,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
try {
final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(0, 0), null, new Rect(0, 0, 200, 200),
- new Rect(50, 100, 150, 150));
+ new Rect(50, 100, 150, 150), false);
assertNotNull(record.mThumbnailAdapter);
((AnimationAdapter) record.mAdapter)
.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
@@ -418,7 +463,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
try {
final RemoteAnimationRecord record = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(100, 100), null, new Rect(150, 150, 400, 400),
- new Rect(50, 100, 150, 150));
+ new Rect(50, 100, 150, 150), false);
assertNotNull(record.mThumbnailAdapter);
((AnimationAdapter) record.mAdapter)
.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_WINDOW_ANIMATION,
@@ -475,8 +520,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -507,8 +553,9 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
mDisplayContent.mOpeningApps.add(win.mActivityRecord);
try {
- final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
- new Point(50, 100), null, new Rect(50, 100, 150, 150), null).mAdapter;
+ final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
+ win.mActivityRecord,
+ new Point(50, 100), null, new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_ACTIVITY_OPEN);
@@ -544,7 +591,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
try {
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null).mAdapter;
+ new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY);
@@ -594,7 +641,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
try {
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null).mAdapter;
+ new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(TRANSIT_OLD_KEYGUARD_GOING_AWAY_ON_WALLPAPER);
@@ -728,7 +775,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
mDisplayContent.mOpeningApps.add(win2.mActivityRecord);
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win2.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null).mAdapter;
+ new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
@@ -758,7 +805,7 @@ public class RemoteAnimationControllerTest extends WindowTestsBase {
final AnimationAdapter adapter = mController.createRemoteAnimationRecord(
win.mActivityRecord, new Point(50, 100), null,
- new Rect(50, 100, 150, 150), null).mAdapter;
+ new Rect(50, 100, 150, 150), null, false).mAdapter;
adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION,
mFinishedCallback);
mController.goodToGo(transit);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
index 700fadd61c9a..68079f4a9ac5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -657,7 +657,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
- mAtm.setBooted(true);
+ setBooted(mAtm);
// Trigger resume on all displays
mRootWindowContainer.resumeFocusedTasksTopActivities();
@@ -685,7 +685,7 @@ public class RootWindowContainerTests extends WindowTestsBase {
doReturn(true).when(mRootWindowContainer).resumeHomeActivity(any(), any(), any());
- mAtm.setBooted(true);
+ setBooted(mAtm);
// Trigger resume on all displays
mRootWindowContainer.resumeFocusedTasksTopActivities();
@@ -771,17 +771,10 @@ public class RootWindowContainerTests extends WindowTestsBase {
@Test
public void testNotStartHomeBeforeBoot() {
final int displayId = 1;
- final boolean isBooting = mAtm.mAmInternal.isBooting();
- final boolean isBooted = mAtm.mAmInternal.isBooted();
- try {
- mAtm.mAmInternal.setBooting(false);
- mAtm.mAmInternal.setBooted(false);
- mRootWindowContainer.onDisplayAdded(displayId);
- verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt());
- } finally {
- mAtm.mAmInternal.setBooting(isBooting);
- mAtm.mAmInternal.setBooted(isBooted);
- }
+ doReturn(false).when(mAtm).isBooting();
+ doReturn(false).when(mAtm).isBooted();
+ mRootWindowContainer.onDisplayAdded(displayId);
+ verify(mRootWindowContainer, never()).startHomeOnDisplay(anyInt(), any(), anyInt());
}
/**
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 891b33baf2f1..324e244c46f5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -1259,6 +1259,36 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+ ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+ public void testOverrideMinAspectRatioLargeForResizableAppInSplitScreen() {
+ setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800);
+
+ // Create a size compat activity on the same task.
+ final ActivityRecord activity = new ActivityBuilder(mAtm)
+ .setTask(mTask)
+ .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT)
+ .setComponent(ComponentName.createRelative(mContext,
+ SizeCompatTests.class.getName()))
+ .setUid(android.os.Process.myUid())
+ .build();
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, activity.getDisplayContent());
+
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, 1000, 1400);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, activity.getWindowingMode());
+
+ // The per-package override forces the activity into a 16:9 aspect ratio
+ assertEquals(1400, activity.getBounds().height());
+ assertEquals(1400 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+ activity.getBounds().width(), 0.5);
+ }
+
+ @Test
public void testLaunchWithFixedRotationTransform() {
final int dw = 1000;
final int dh = 2500;
@@ -1409,12 +1439,10 @@ public class SizeCompatTests extends WindowTestsBase {
setUpDisplaySizeWithApp(2800, 1400);
mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
- // Portrait fixed app with min aspect ratio higher that aspect ratio override for fixed
- // orientation letterbox.
final float fixedOrientationLetterboxAspectRatio = 1.1f;
mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
fixedOrientationLetterboxAspectRatio);
- prepareUnresizable(mActivity, 0, SCREEN_ORIENTATION_PORTRAIT);
+ prepareLimitedBounds(mActivity, SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable= */ false);
final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
final Rect activityBounds = new Rect(mActivity.getBounds());
@@ -1435,6 +1463,111 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testDisplayIgnoreOrientationRequest_unresizableWithCorrespondingMinAspectRatio() {
+ // Set up a display in landscape and ignoring orientation request.
+ setUpDisplaySizeWithApp(2800, 1400);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ final float fixedOrientationLetterboxAspectRatio = 1.1f;
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(
+ fixedOrientationLetterboxAspectRatio);
+ mActivity.mWmService.mLetterboxConfiguration.setDefaultMinAspectRatioForUnresizableApps(
+ 1.5f);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
+ final Rect activityBounds = new Rect(mActivity.getBounds());
+
+ // Display shouldn't be rotated.
+ assertEquals(SCREEN_ORIENTATION_UNSPECIFIED,
+ mActivity.mDisplayContent.getLastOrientation());
+ assertTrue(displayBounds.width() > displayBounds.height());
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ assertFalse(mActivity.inSizeCompatMode());
+
+ // Letterbox logic should use config_letterboxDefaultMinAspectRatioForUnresizableApps over
+ // config_fixedOrientationLetterboxAspectRatio.
+ assertEquals(displayBounds.height(), activityBounds.height());
+ final float defaultAspectRatio = mActivity.mWmService.mLetterboxConfiguration
+ .getDefaultMinAspectRatioForUnresizableApps();
+ assertEquals(displayBounds.height() / defaultAspectRatio, activityBounds.width(), 0.5);
+ }
+
+ @Test
+ public void testSplitAspectRatioForUnresizablePortraitApps() {
+ // Set up a display in landscape and ignoring orientation request.
+ int screenWidth = 1600;
+ int screenHeight = 1400;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration
+ .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
+
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
+
+ final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
+ final Rect activityBounds = new Rect(mActivity.getBounds());
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Checking that there is no size compat mode.
+ assertFitted();
+
+ assertEquals(displayBounds.height(), activityBounds.height());
+ assertTrue(activityBounds.width() < displayBounds.width() / 2);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, getExpectedSplitSize(screenWidth), screenHeight);
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+ // Checking that there is no size compat mode.
+ assertFitted();
+ }
+
+ @Test
+ public void testSplitAspectRatioForUnresizableLandscapeApps() {
+ // Set up a display in portrait and ignoring orientation request.
+ int screenWidth = 1400;
+ int screenHeight = 1600;
+ setUpDisplaySizeWithApp(screenWidth, screenHeight);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+ mActivity.mWmService.mLetterboxConfiguration
+ .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(true);
+
+ mActivity.mWmService.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(1.1f);
+
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ final Rect displayBounds = new Rect(mActivity.mDisplayContent.getBounds());
+ final Rect activityBounds = new Rect(mActivity.getBounds());
+
+ // App should launch in fixed orientation letterbox.
+ assertTrue(mActivity.isLetterboxedForFixedOrientationAndAspectRatio());
+ // Checking that there is no size compat mode.
+ assertFitted();
+
+ assertEquals(displayBounds.width(), activityBounds.width());
+ assertTrue(activityBounds.height() < displayBounds.height() / 2);
+
+ final TestSplitOrganizer organizer =
+ new TestSplitOrganizer(mAtm, mActivity.getDisplayContent());
+ // Move activity to split screen which takes half of the screen.
+ mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents= */ false , "test");
+ organizer.mPrimary.setBounds(0, 0, screenWidth, getExpectedSplitSize(screenHeight));
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mTask.getWindowingMode());
+ assertEquals(WINDOWING_MODE_MULTI_WINDOW, mActivity.getWindowingMode());
+ // Checking that there is no size compat mode.
+ assertFitted();
+ }
+
+ @Test
public void
testDisplayIgnoreOrientationRequest_orientationLetterboxBecameSizeCompatAfterRotate() {
// Set up a display in landscape and ignoring orientation request.
@@ -1908,7 +2041,7 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
- public void testSupportsNonResizableInSplitScreen_fillTaskForSameOrientation() {
+ public void testSupportsNonResizableInSplitScreen_aspectRatioLetterboxInSameOrientation() {
// Support non resizable in multi window
mAtm.mDevEnableNonResizableMultiWindow = true;
setUpDisplaySizeWithApp(1000, 2800);
@@ -1960,7 +2093,7 @@ public class SizeCompatTests extends WindowTestsBase {
prepareUnresizable(mActivity, /* maxAspect */ 1.1f, SCREEN_ORIENTATION_UNSPECIFIED);
// Bounds are letterboxed to respect the provided max aspect ratio.
- assertEquals(mActivity.getBounds(), new Rect(0, 0, 1000, 1100));
+ assertEquals(mActivity.getBounds(), new Rect(0, 850, 1000, 1950));
// Move activity to split screen which has landscape size.
mTask.reparent(organizer.mPrimary, POSITION_TOP, /* moveParents */ false, "test");
@@ -2077,19 +2210,14 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
letterboxHorizontalPositionMultiplier);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
-
assertEquals(fixedOrientationLetterbox, mActivity.getBounds());
-
// Rotate to put activity in size compat mode.
rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
-
assertTrue(mActivity.inSizeCompatMode());
// Activity is in size compat mode but not scaled.
assertEquals(sizeCompatUnscaled, mActivity.getBounds());
-
// Force activity to scaled down for size compat mode.
resizeDisplay(mTask.mDisplayContent, 700, 1400);
-
assertTrue(mActivity.inSizeCompatMode());
assertScaled();
assertEquals(sizeCompatScaled, mActivity.getBounds());
@@ -2107,6 +2235,109 @@ public class SizeCompatTests extends WindowTestsBase {
}
@Test
+ public void testUpdateResolvedBoundsVerticalPosition_top() {
+ // Display configured as (1400, 2800).
+ assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxVerticalPositionMultiplier */ 0.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(0, 0, 1400, 700),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(700, 0, 2100, 700),
+ // After the display is resized to (1400, 700).
+ /* sizeCompatScaled */ new Rect(0, 0, 700, 350));
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsVerticalPosition_center() {
+ // Display configured as (1400, 2800).
+ assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxVerticalPositionMultiplier */ 0.5f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
+ // After the display is resized to (1400, 700).
+ /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsVerticalPosition_invalidMultiplier_defaultToCenter() {
+ // Display configured as (1400, 2800).
+
+ // Below 0.0.
+ assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxVerticalPositionMultiplier */ -1.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
+ // After the display is resized to (1400, 700).
+ /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
+
+ // Above 1.0
+ assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxVerticalPositionMultiplier */ 2.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(0, 1050, 1400, 1750),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(700, 350, 2100, 1050),
+ // After the display is resized to (1400, 700).
+ /* sizeCompatScaled */ new Rect(0, 525, 700, 875));
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsVerticalPosition_bottom() {
+ // Display configured as (1400, 2800).
+ assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ /* letterboxVerticalPositionMultiplier */ 1.0f,
+ // At launch.
+ /* fixedOrientationLetterbox */ new Rect(0, 2100, 1400, 2800),
+ // After 90 degree rotation.
+ /* sizeCompatUnscaled */ new Rect(700, 700, 2100, 1400),
+ // After the display is resized to (1400, 700).
+ /* sizeCompatScaled */ new Rect(0, 1050, 700, 1400));
+ }
+
+ private void assertVerticalPositionForDifferentDisplayConfigsForLandscapeActivity(
+ float letterboxVerticalPositionMultiplier, Rect fixedOrientationLetterbox,
+ Rect sizeCompatUnscaled, Rect sizeCompatScaled) {
+ // Set up a display in portrait and ignoring orientation request.
+ setUpDisplaySizeWithApp(1400, 2800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(
+ letterboxVerticalPositionMultiplier);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+
+ assertEquals(fixedOrientationLetterbox, mActivity.getBounds());
+
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+
+ assertTrue(mActivity.inSizeCompatMode());
+ // Activity is in size compat mode but not scaled.
+ assertEquals(sizeCompatUnscaled, mActivity.getBounds());
+
+ // Force activity to scaled down for size compat mode.
+ resizeDisplay(mTask.mDisplayContent, 1400, 700);
+
+ assertTrue(mActivity.inSizeCompatMode());
+ assertScaled();
+ assertEquals(sizeCompatScaled, mActivity.getBounds());
+ }
+
+ @Test
+ public void testUpdateResolvedBoundsVerticalPosition_activityFillParentHeight() {
+ // When activity height equals parent height, multiplier shouldn't have any effect.
+ assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxVerticalPositionMultiplier */ 0.0f);
+ assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxVerticalPositionMultiplier */ 0.5f);
+ assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
+ /* letterboxVerticalPositionMultiplier */ 1.0f);
+ }
+
+ @Test
public void testAreBoundsLetterboxed_letterboxedForAspectRatio_returnsTrue() {
setUpDisplaySizeWithApp(1000, 2500);
@@ -2390,6 +2621,16 @@ public class SizeCompatTests extends WindowTestsBase {
assertEquals(newDensity, mActivity.getConfiguration().densityDpi);
}
+ private int getExpectedSplitSize(int dimensionToSplit) {
+ int dividerWindowWidth =
+ mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_thickness);
+ int dividerInsets =
+ mActivity.mWmService.mContext.getResources().getDimensionPixelSize(
+ com.android.internal.R.dimen.docked_stack_divider_insets);
+ return (dimensionToSplit - (dividerWindowWidth - dividerInsets * 2)) / 2;
+ }
+
private void assertHorizontalPositionForDifferentDisplayConfigsForLandscapeActivity(
float letterboxHorizontalPositionMultiplier) {
// Set up a display in landscape and ignoring orientation request.
@@ -2399,6 +2640,23 @@ public class SizeCompatTests extends WindowTestsBase {
mActivity.mWmService.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(
letterboxHorizontalPositionMultiplier);
prepareUnresizable(mActivity, SCREEN_ORIENTATION_LANDSCAPE);
+ assertFitted();
+ // Rotate to put activity in size compat mode.
+ rotateDisplay(mActivity.mDisplayContent, ROTATION_90);
+ assertTrue(mActivity.inSizeCompatMode());
+ // Activity is in size compat mode but not scaled.
+ assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds());
+ }
+
+ private void assertVerticalPositionForDifferentDisplayConfigsForPortraitActivity(
+ float letterboxVerticalPositionMultiplier) {
+ // Set up a display in portrait and ignoring orientation request.
+ setUpDisplaySizeWithApp(1400, 2800);
+ mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+
+ mActivity.mWmService.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(
+ letterboxVerticalPositionMultiplier);
+ prepareUnresizable(mActivity, SCREEN_ORIENTATION_PORTRAIT);
assertFitted();
@@ -2407,7 +2665,7 @@ public class SizeCompatTests extends WindowTestsBase {
assertTrue(mActivity.inSizeCompatMode());
// Activity is in size compat mode but not scaled.
- assertEquals(new Rect(0, 1050, 1400, 1750), mActivity.getBounds());
+ assertEquals(new Rect(1050, 0, 1750, 1400), mActivity.getBounds());
}
private static WindowState addWindowToActivity(ActivityRecord activity) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index dc9a62554fdb..6c100d76f5fb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -43,12 +43,14 @@ import static org.mockito.Mockito.withSettings;
import android.app.ActivityManagerInternal;
import android.app.AppOpsManager;
+import android.app.IApplicationThread;
import android.app.usage.UsageStatsManagerInternal;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManagerInternal;
import android.database.ContentObserver;
@@ -66,7 +68,6 @@ import android.os.UserHandle;
import android.provider.DeviceConfig;
import android.util.Log;
import android.view.InputChannel;
-import android.view.Surface;
import android.view.SurfaceControl;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
@@ -77,7 +78,6 @@ import com.android.server.LockGuard;
import com.android.server.UiThread;
import com.android.server.Watchdog;
import com.android.server.am.ActivityManagerService;
-import com.android.server.appop.AppOpsService;
import com.android.server.display.color.ColorDisplayService;
import com.android.server.firewall.IntentFirewall;
import com.android.server.input.InputManagerService;
@@ -94,10 +94,8 @@ import org.mockito.MockSettings;
import org.mockito.Mockito;
import org.mockito.quality.Strictness;
-import java.io.File;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.Supplier;
/**
* JUnit test rule to correctly setting up system services like {@link WindowManagerService}
@@ -125,13 +123,11 @@ public class SystemServicesTestRule implements TestRule {
private Description mDescription;
private Context mContext;
private StaticMockitoSession mMockitoSession;
- private ActivityManagerService mAmService;
private ActivityTaskManagerService mAtmService;
private WindowManagerService mWmService;
private WindowState.PowerManagerWrapper mPowerManagerWrapper;
private InputManagerService mImService;
private InputChannel mInputChannel;
- private Supplier<Surface> mSurfaceFactory = () -> mock(Surface.class);
/**
* Spied {@link SurfaceControl.Transaction} class than can be used to verify calls.
*/
@@ -289,29 +285,9 @@ public class SystemServicesTestRule implements TestRule {
}
private void setUpActivityTaskManagerService() {
- // ActivityManagerService
- mAmService = new ActivityManagerService(new AMTestInjector(mContext), null /* thread */);
- spyOn(mAmService);
- doReturn(mock(IPackageManager.class)).when(mAmService).getPackageManager();
- doNothing().when(mAmService).grantImplicitAccess(
- anyInt(), any(), anyInt(), anyInt());
-
// ActivityManagerInternal
- final ActivityManagerInternal amInternal = mAmService.mInternal;
- spyOn(amInternal);
- doNothing().when(amInternal).trimApplications();
- doNothing().when(amInternal).scheduleAppGcs();
- doNothing().when(amInternal).updateCpuStats();
- doNothing().when(amInternal).updateOomAdj();
- doNothing().when(amInternal).updateBatteryStats(any(), anyInt(), anyInt(), anyBoolean());
- doNothing().when(amInternal).updateActivityUsageStats(
- any(), anyInt(), anyInt(), any(), any());
- doNothing().when(amInternal).startProcess(
- any(), any(), anyBoolean(), anyBoolean(), any(), any());
- doNothing().when(amInternal).updateOomLevelsForDisplay(anyInt());
- doNothing().when(amInternal).broadcastGlobalConfigurationChanged(anyInt(), anyBoolean());
- doNothing().when(amInternal).cleanUpServices(anyInt(), any(), any());
- doNothing().when(amInternal).reportCurKeyguardUsageEvent(anyBoolean());
+ final ActivityManagerInternal amInternal =
+ mock(ActivityManagerInternal.class, withSettings().stubOnly());
doReturn(UserHandle.USER_SYSTEM).when(amInternal).getCurrentUserId();
doReturn(TEST_USER_PROFILE_IDS).when(amInternal).getCurrentProfileIds();
doReturn(true).when(amInternal).isUserRunning(anyInt(), anyInt());
@@ -320,7 +296,9 @@ public class SystemServicesTestRule implements TestRule {
doReturn(false).when(amInternal).isActivityStartsLoggingEnabled();
LocalServices.addService(ActivityManagerInternal.class, amInternal);
- mAtmService = new TestActivityTaskManagerService(mContext, mAmService);
+ final ActivityManagerService amService =
+ mock(ActivityManagerService.class, withSettings().stubOnly());
+ mAtmService = new TestActivityTaskManagerService(mContext, amService);
LocalServices.addService(ActivityTaskManagerInternal.class, mAtmService.getAtmInternal());
}
@@ -334,7 +312,7 @@ public class SystemServicesTestRule implements TestRule {
mWmService = WindowManagerService.main(
mContext, mImService, false, false, wmPolicy, mAtmService,
testDisplayWindowSettingsProvider, StubTransaction::new,
- () -> mSurfaceFactory.get(), (unused) -> new MockSurfaceControlBuilder());
+ (unused) -> new MockSurfaceControlBuilder());
spyOn(mWmService);
spyOn(mWmService.mRoot);
// Invoked during {@link ActivityStack} creation.
@@ -355,7 +333,7 @@ public class SystemServicesTestRule implements TestRule {
null, null, mTransaction, mWmService.mPowerManagerInternal);
mWmService.onInitReady();
- mAmService.setWindowManager(mWmService);
+ mAtmService.setWindowManager(mWmService);
mWmService.mDisplayEnabled = true;
mWmService.mDisplayReady = true;
// Set configuration for default display
@@ -454,8 +432,32 @@ public class SystemServicesTestRule implements TestRule {
.spiedInstance(sWakeLock).stubOnly());
}
- void setSurfaceFactory(Supplier<Surface> factory) {
- mSurfaceFactory = factory;
+ WindowProcessController addProcess(String pkgName, String procName, int pid, int uid) {
+ return addProcess(mAtmService, pkgName, procName, pid, uid);
+ }
+
+ static WindowProcessController addProcess(ActivityTaskManagerService atmService, String pkgName,
+ String procName, int pid, int uid) {
+ final ApplicationInfo info = new ApplicationInfo();
+ info.uid = uid;
+ info.packageName = pkgName;
+ return addProcess(atmService, info, procName, pid);
+ }
+
+ static WindowProcessController addProcess(ActivityTaskManagerService atmService,
+ ApplicationInfo info, String procName, int pid) {
+ final WindowProcessListener mockListener = mock(WindowProcessListener.class,
+ withSettings().stubOnly());
+ final int uid = info.uid;
+ final WindowProcessController proc = new WindowProcessController(atmService,
+ info, procName, uid, UserHandle.getUserId(uid), mockListener, mockListener);
+ proc.setThread(mock(IApplicationThread.class, withSettings().stubOnly()));
+ atmService.mProcessNames.put(procName, uid, proc);
+ if (pid > 0) {
+ proc.setPid(pid);
+ atmService.mProcessMap.put(pid, proc);
+ }
+ return proc;
}
void cleanupWindowManagerHandlers() {
@@ -618,32 +620,4 @@ public class SystemServicesTestRule implements TestRule {
doReturn(true).when(controller).checkKeyguardVisibility(any());
}
}
-
- // TODO: Can we just mock this?
- private static class AMTestInjector extends ActivityManagerService.Injector {
-
- AMTestInjector(Context context) {
- super(context);
- }
-
- @Override
- public Context getContext() {
- return getInstrumentation().getTargetContext();
- }
-
- @Override
- public AppOpsService getAppOpsService(File file, Handler handler) {
- return null;
- }
-
- @Override
- public Handler getUiHandler(ActivityManagerService service) {
- return UiThread.getHandler();
- }
-
- @Override
- public boolean isNetworkRestrictedForUid(int uid) {
- return false;
- }
- }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
index e8f1d2390c34..7f09606d1c3a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskDisplayAreaTests.java
@@ -82,7 +82,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask);
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -108,7 +108,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
final Task adjacentRootTask = createTask(
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
- adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask);
taskDisplayArea.setLaunchRootTask(rootTask,
new int[]{WINDOWING_MODE_MULTI_WINDOW}, new int[]{ACTIVITY_TYPE_STANDARD});
@@ -129,7 +129,7 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
adjacentRootTask.mCreatedByOrganizer = true;
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask);
taskDisplayArea.setLaunchAdjacentFlagRootTask(adjacentRootTask);
final Task actualRootTask = taskDisplayArea.getLaunchRootTask(
@@ -756,18 +756,26 @@ public class TaskDisplayAreaTests extends WindowTestsBase {
adjacentRootTask.mCreatedByOrganizer = true;
final Task candidateTask = createTaskInRootTask(rootTask, 0 /* userId*/);
final TaskDisplayArea taskDisplayArea = rootTask.getDisplayArea();
- adjacentRootTask.setAdjacentTaskFragment(rootTask, false /* moveTogether */);
+ adjacentRootTask.setAdjacentTaskFragment(rootTask);
// Verify the launch root with candidate task
Task actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
0 /* launchFlags */, candidateTask);
- assertSame(rootTask, actualRootTask.getRootTask());
+ assertSame(rootTask, actualRootTask);
// Verify the launch root task without candidate task
actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
0 /* launchFlags */);
- assertSame(adjacentRootTask, actualRootTask.getRootTask());
+ assertSame(adjacentRootTask, actualRootTask);
+
+ final Task pinnedTask = createTask(
+ mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+ // Verify not adjusting launch target for pinned candidate task
+ actualRootTask = taskDisplayArea.getLaunchRootTask(WINDOWING_MODE_UNDEFINED,
+ ACTIVITY_TYPE_STANDARD, null /* options */, adjacentRootTask /* sourceTask */,
+ 0 /* launchFlags */, pinnedTask /* candidateTask */);
+ assertNull(actualRootTask);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index ed8440027bdc..97f0918e8136 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -18,6 +18,12 @@ package com.android.server.wm;
import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_CHILDREN;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
+import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -35,7 +41,6 @@ import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
@@ -238,10 +243,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mController.registerOrganizer(mIOrganizer);
mController.onTaskFragmentError(mTaskFragment.getTaskFragmentOrganizer(),
- mErrorToken, exception);
+ mErrorToken, null /* taskFragment */, -1 /* opType */, exception);
mController.dispatchPendingEvents();
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(exception));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), eq(null), eq(-1), eq(exception));
}
@Test
@@ -422,7 +427,7 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
// Throw exception if the transaction is trying to change a window that is not organized by
// the organizer.
- mTransaction.setAdjacentRoots(mFragmentWindowToken, token2, false /* moveTogether */);
+ mTransaction.setAdjacentRoots(mFragmentWindowToken, token2);
assertApplyTransactionDisallowed(mTransaction);
@@ -605,7 +610,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
verify(mAtm.getActivityStartController(), never()).startActivityInTaskFragment(any(), any(),
any(), any(), anyInt(), anyInt(), any());
verify(mAtm.mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment),
+ eq(HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
}
@Test
@@ -620,7 +627,9 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment),
+ eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
assertNull(activity.getOrganizedTaskFragment());
}
@@ -636,8 +645,10 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
- verify(mTaskFragment, never()).setAdjacentTaskFragment(any(), anyBoolean());
+ eq(mErrorToken), eq(mTaskFragment),
+ eq(HIERARCHY_OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS),
+ any(IllegalArgumentException.class));
+ verify(mTaskFragment, never()).setAdjacentTaskFragment(any());
}
@Test
@@ -655,7 +666,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(null), eq(HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
assertNull(mWindowOrganizerController.getTaskFragment(fragmentToken));
}
@@ -670,7 +682,8 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mWindowOrganizerController.applyTransaction(mTransaction);
verify(mWindowOrganizerController).sendTaskFragmentOperationFailure(eq(mIOrganizer),
- eq(mErrorToken), any(IllegalArgumentException.class));
+ eq(mErrorToken), eq(mTaskFragment), eq(HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT),
+ any(IllegalArgumentException.class));
assertNotNull(mWindowOrganizerController.getTaskFragment(mFragmentToken));
// Allow organizer to delete empty TaskFragment for cleanup.
@@ -923,15 +936,18 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.setBounds(mTaskFragBounds)
.build();
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
- clearInvocations(mAtm.mRootWindowContainer);
// Reparent activity to mTaskFragment, which is smaller than activity's
// minimum dimensions.
mTransaction.reparentActivityToTaskFragment(mFragmentToken, activity.token)
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
+ // The pending event will be dispatched on the handler (from requestTraversal).
+ waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(SecurityException.class));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ eq(HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT),
+ any(SecurityException.class));
}
@Test
@@ -958,7 +974,6 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
.build();
mWindowOrganizerController.mLaunchTaskFragments.put(oldFragToken, oldTaskFrag);
mWindowOrganizerController.mLaunchTaskFragments.put(mFragmentToken, mTaskFragment);
- clearInvocations(mAtm.mRootWindowContainer);
// Reparent oldTaskFrag's children to mTaskFragment, which is smaller than activity's
// minimum dimensions.
@@ -966,8 +981,11 @@ public class TaskFragmentOrganizerControllerTest extends WindowTestsBase {
mTaskFragment.mRemoteToken.toWindowContainerToken())
.setErrorCallbackToken(mErrorToken);
mWindowOrganizerController.applyTransaction(mTransaction);
+ // The pending event will be dispatched on the handler (from requestTraversal).
+ waitHandlerIdle(mWm.mAnimationHandler);
- verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(SecurityException.class));
+ verify(mOrganizer).onTaskFragmentError(eq(mErrorToken), any(),
+ eq(HIERARCHY_OP_TYPE_REPARENT_CHILDREN), any(SecurityException.class));
}
@Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 3ed484ac7391..5f3096356bc5 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -202,7 +202,7 @@ public class TaskFragmentTest extends WindowTestsBase {
doReturn(true).when(primaryActivity).supportsPictureInPicture();
doReturn(false).when(secondaryActivity).supportsPictureInPicture();
- primaryTf.setAdjacentTaskFragment(secondaryTf, false /* moveAdjacentTogether */);
+ primaryTf.setAdjacentTaskFragment(secondaryTf);
primaryActivity.setState(RESUMED, "test");
secondaryActivity.setState(RESUMED, "test");
@@ -448,7 +448,7 @@ public class TaskFragmentTest extends WindowTestsBase {
.setOrganizer(mOrganizer)
.setFragmentToken(new Binder())
.build();
- tf0.setAdjacentTaskFragment(tf1, false /* moveAdjacentTogether */);
+ tf0.setAdjacentTaskFragment(tf1);
tf0.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
tf1.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
task.setBounds(0, 0, 1200, 1000);
@@ -475,5 +475,13 @@ public class TaskFragmentTest extends WindowTestsBase {
assertFalse(activity0.isLetterboxedForFixedOrientationAndAspectRatio());
assertFalse(activity1.isLetterboxedForFixedOrientationAndAspectRatio());
assertEquals(SCREEN_ORIENTATION_UNSET, task.getOrientation());
+
+ tf0.setResumedActivity(activity0, "test");
+ tf1.setResumedActivity(activity1, "test");
+ mDisplayContent.mFocusedApp = activity1;
+
+ // Making the activity0 be the focused activity and ensure the focused app is updated.
+ activity0.moveFocusableActivityToTop("test");
+ assertEquals(activity0, mDisplayContent.mFocusedApp);
}
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
index 202168bae869..f4323db2ceec 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java
@@ -31,7 +31,6 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.util.DisplayMetrics.DENSITY_DEFAULT;
import static android.view.IWindowManager.FIXED_TO_USER_ROTATION_ENABLED;
import static android.view.Surface.ROTATION_0;
import static android.view.Surface.ROTATION_90;
@@ -60,7 +59,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.ArgumentMatchers.same;
import static org.mockito.Mockito.atLeast;
import static org.mockito.Mockito.clearInvocations;
@@ -270,6 +268,20 @@ public class TaskTests extends WindowTestsBase {
assertFalse(task.hasChild());
// In real case, the task should be preserved for adding new activity.
assertTrue(task.isAttached());
+
+ final ActivityRecord activityA = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord activityB = new ActivityBuilder(mAtm).setTask(task).build();
+ final ActivityRecord activityC = new ActivityBuilder(mAtm).setTask(task).build();
+ activityA.setState(ActivityRecord.State.STOPPED, "test");
+ activityB.setState(ActivityRecord.State.PAUSED, "test");
+ activityC.setState(ActivityRecord.State.RESUMED, "test");
+ doReturn(true).when(activityB).shouldBeVisibleUnchecked();
+ doReturn(true).when(activityC).shouldBeVisibleUnchecked();
+ activityA.getConfiguration().densityDpi += 100;
+ assertTrue(task.performClearTop(activityA, 0 /* launchFlags */).finishing);
+ // The bottom activity should destroy directly without relaunch for config change.
+ assertEquals(ActivityRecord.State.DESTROYING, activityA.getState());
+ verify(activityA, never()).startRelaunching();
}
@Test
@@ -497,34 +509,6 @@ public class TaskTests extends WindowTestsBase {
assertEquals(reqBounds.height(), task.getBounds().height());
}
- /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
- @Test
- public void testBoundsOnModeChangeFreeformToFullscreen() {
- DisplayContent display = mAtm.mRootWindowContainer.getDefaultDisplay();
- Task rootTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true)
- .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
- Task task = rootTask.getBottomMostTask();
- task.getRootActivity().setOrientation(SCREEN_ORIENTATION_UNSPECIFIED);
- DisplayInfo info = new DisplayInfo();
- display.mDisplay.getDisplayInfo(info);
- final Rect fullScreenBounds = new Rect(0, 0, info.logicalWidth, info.logicalHeight);
- final Rect freeformBounds = new Rect(fullScreenBounds);
- freeformBounds.inset((int) (freeformBounds.width() * 0.2),
- (int) (freeformBounds.height() * 0.2));
- task.setBounds(freeformBounds);
-
- assertEquals(freeformBounds, task.getBounds());
-
- // FULLSCREEN inherits bounds
- rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
- assertEquals(fullScreenBounds, task.getBounds());
- assertEquals(freeformBounds, task.mLastNonFullscreenBounds);
-
- // FREEFORM restores bounds
- rootTask.setWindowingMode(WINDOWING_MODE_FREEFORM);
- assertEquals(freeformBounds, task.getBounds());
- }
-
/**
* Tests that a task with forced orientation has orientation-consistent bounds within the
* parent.
@@ -590,6 +574,7 @@ public class TaskTests extends WindowTestsBase {
// FULLSCREEN letterboxes bounds on activity level, no constraint on task level.
rootTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+ rootTask.setBounds(null);
assertThat(task.getBounds().height()).isGreaterThan(task.getBounds().width());
assertThat(top.getBounds().width()).isGreaterThan(top.getBounds().height());
assertEquals(fullScreenBoundsPort, task.getBounds());
@@ -667,8 +652,6 @@ public class TaskTests extends WindowTestsBase {
final Task task = new TaskBuilder(mSupervisor).setDisplay(display).build();
final Configuration inOutConfig = new Configuration();
final Configuration parentConfig = new Configuration();
- final int longSide = 1200;
- final int shortSide = 600;
final Rect parentBounds = new Rect(0, 0, 250, 500);
final Rect parentAppBounds = new Rect(0, 0, 250, 480);
parentConfig.windowConfiguration.setBounds(parentBounds);
@@ -689,14 +672,17 @@ public class TaskTests extends WindowTestsBase {
// If bounds are overridden, config properties should be made to match. Surface hierarchy
// will crop for policy.
inOutConfig.setToDefaults();
+ final int longSide = 960;
+ final int shortSide = 540;
+ parentConfig.densityDpi = 192;
final Rect largerPortraitBounds = new Rect(0, 0, shortSide, longSide);
inOutConfig.windowConfiguration.setBounds(largerPortraitBounds);
task.computeConfigResourceOverrides(inOutConfig, parentConfig);
// The override bounds are beyond the parent, the out appBounds should not be intersected
// by parent appBounds.
assertEquals(largerPortraitBounds, inOutConfig.windowConfiguration.getAppBounds());
- assertEquals(longSide, inOutConfig.screenHeightDp * parentConfig.densityDpi / 160);
- assertEquals(shortSide, inOutConfig.screenWidthDp * parentConfig.densityDpi / 160);
+ assertEquals(800, inOutConfig.screenHeightDp); // 960/(192/160) = 800
+ assertEquals(450, inOutConfig.screenWidthDp); // 540/(192/160) = 450
inOutConfig.setToDefaults();
// Landscape bounds.
@@ -716,16 +702,17 @@ public class TaskTests extends WindowTestsBase {
// Without limiting to be inside the parent bounds, the out screen size should keep relative
// to the input bounds.
final ActivityRecord activity = new ActivityBuilder(mAtm).setTask(task).build();
- final ActivityRecord.CompatDisplayInsets compatIntsets =
+ final ActivityRecord.CompatDisplayInsets compatInsets =
new ActivityRecord.CompatDisplayInsets(
display, activity, /* fixedOrientationBounds= */ null);
- task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatIntsets);
+ task.computeConfigResourceOverrides(inOutConfig, parentConfig, compatInsets);
assertEquals(largerLandscapeBounds, inOutConfig.windowConfiguration.getAppBounds());
- assertEquals((shortSide - statusBarHeight) * DENSITY_DEFAULT / parentConfig.densityDpi,
- inOutConfig.screenHeightDp);
- assertEquals(longSide * DENSITY_DEFAULT / parentConfig.densityDpi,
- inOutConfig.screenWidthDp);
+ final float density = parentConfig.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
+ final int expectedHeightDp = (int) ((shortSide - statusBarHeight) / density + 0.5f);
+ assertEquals(expectedHeightDp, inOutConfig.screenHeightDp);
+ final int expectedWidthDp = (int) (longSide / density + 0.5f);
+ assertEquals(expectedWidthDp, inOutConfig.screenWidthDp);
assertEquals(Configuration.ORIENTATION_LANDSCAPE, inOutConfig.orientation);
}
@@ -1443,25 +1430,6 @@ public class TaskTests extends WindowTestsBase {
}
@Test
- public void testMoveToFront_moveAdjacentTask() {
- final Task task1 =
- createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
- final Task task2 =
- createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
- spyOn(task2);
-
- task1.setAdjacentTaskFragment(task2, false /* moveTogether */);
- task1.moveToFront("" /* reason */);
- verify(task2, never()).moveToFrontInner(anyString(), isNull());
-
- // Reset adjacent tasks to move together.
- task1.setAdjacentTaskFragment(null, false /* moveTogether */);
- task1.setAdjacentTaskFragment(task2, true /* moveTogether */);
- task1.moveToFront("" /* reason */);
- verify(task2).moveToFrontInner(anyString(), isNull());
- }
-
- @Test
public void testResumeTask_doNotResumeTaskFragmentBehindTranslucent() {
final Task task = createTask(mDisplayContent);
final TaskFragment tfBehind = createTaskFragmentWithParentTask(
diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
index ea98b6b17e83..92457c73b315 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java
@@ -26,7 +26,6 @@ import android.os.IBinder;
import android.os.PowerManager.GoToSleepReason;
import android.os.PowerManager.WakeReason;
import android.util.proto.ProtoOutputStream;
-import android.view.IWindowManager;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.animation.Animation;
@@ -50,8 +49,7 @@ class TestWindowManagerPolicy implements WindowManagerPolicy {
}
@Override
- public void init(Context context, IWindowManager windowManager,
- WindowManagerFuncs windowManagerFuncs) {
+ public void init(Context context, WindowManagerFuncs windowManagerFuncs) {
}
public void setDefaultDisplay(DisplayContentInfo displayContentInfo) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 6c1c0865446e..c323e02ad149 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -30,6 +30,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static android.view.WindowManager.TRANSIT_CLOSE;
import static android.view.WindowManager.TRANSIT_OPEN;
import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.window.TransitionInfo.FLAG_IS_EMBEDDED;
import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
import static android.window.TransitionInfo.FLAG_SHOW_WALLPAPER;
import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
@@ -61,8 +62,10 @@ import android.util.ArrayMap;
import android.util.ArraySet;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizer;
+import android.window.ITaskFragmentOrganizer;
import android.window.ITaskOrganizer;
import android.window.ITransitionPlayer;
+import android.window.TaskFragmentOrganizer;
import android.window.TransitionInfo;
import androidx.test.filters.SmallTest;
@@ -85,9 +88,14 @@ import java.util.function.Function;
@Presubmit
@RunWith(WindowTestRunner.class)
public class TransitionTests extends WindowTestsBase {
+ final SurfaceControl.Transaction mMockT = mock(SurfaceControl.Transaction.class);
private Transition createTestTransition(int transitType) {
- TransitionController controller = mock(TransitionController.class);
+ TransitionTracer tracer = mock(TransitionTracer.class);
+ final TransitionController controller = new TransitionController(
+ mock(ActivityTaskManagerService.class), mock(TaskSnapshotController.class),
+ mock(TransitionTracer.class));
+
final BLASTSyncEngine sync = createTestBLASTSyncEngine();
final Transition t = new Transition(transitType, 0 /* flags */, controller, sync);
t.startCollecting(0 /* timeoutMs */);
@@ -121,7 +129,8 @@ public class TransitionTests extends WindowTestsBase {
participants.add(oldTask);
participants.add(newTask);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
+ mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -129,7 +138,7 @@ public class TransitionTests extends WindowTestsBase {
participants.add(opening);
participants.add(closing);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -137,7 +146,7 @@ public class TransitionTests extends WindowTestsBase {
// Check combined prune and promote
participants.remove(newTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -145,7 +154,7 @@ public class TransitionTests extends WindowTestsBase {
// Check multi promote
participants.remove(oldTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -186,7 +195,8 @@ public class TransitionTests extends WindowTestsBase {
participants.add(opening);
participants.add(opening2);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
+ mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(newTask.mRemoteToken.toWindowContainerToken()));
@@ -195,7 +205,7 @@ public class TransitionTests extends WindowTestsBase {
// Check that unchanging but visible descendant of sibling prevents promotion
participants.remove(opening2);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(newNestedTask.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(oldTask.mRemoteToken.toWindowContainerToken()));
@@ -232,7 +242,8 @@ public class TransitionTests extends WindowTestsBase {
participants.add(showing);
participants.add(showing2);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
+ mMockT);
assertEquals(1, info.getChanges().size());
assertEquals(transit, info.getType());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
@@ -240,14 +251,14 @@ public class TransitionTests extends WindowTestsBase {
// Check that organized tasks get reported even if not top
makeTaskOrganized(showTask);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
assertEquals(2, info.getChanges().size());
assertNotNull(info.getChange(tda.mRemoteToken.toWindowContainerToken()));
assertNotNull(info.getChange(showTask.mRemoteToken.toWindowContainerToken()));
// Even if DisplayArea explicitly participating
participants.add(tda);
targets = Transition.calculateTargets(participants, changes);
- info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ info = Transition.calculateTransitionInfo(transit, flags, targets, changes, mMockT);
assertEquals(2, info.getChanges().size());
}
@@ -271,7 +282,7 @@ public class TransitionTests extends WindowTestsBase {
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges);
+ 0, 0, targets, transition.mChanges, mMockT);
assertEquals(2, info.getChanges().size());
// There was an existence change on open, so it should be OPEN rather than SHOW
assertEquals(TRANSIT_OPEN,
@@ -308,7 +319,7 @@ public class TransitionTests extends WindowTestsBase {
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges);
+ 0, 0, targets, transition.mChanges, mMockT);
assertEquals(taskCount, info.getChanges().size());
// verify order is top-to-bottem
for (int i = 0; i < taskCount; ++i) {
@@ -358,7 +369,7 @@ public class TransitionTests extends WindowTestsBase {
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges);
+ 0, 0, targets, transition.mChanges, mMockT);
// verify that wallpaper is at bottom
assertEquals(taskCount + 1, info.getChanges().size());
// The wallpaper is not organized, so it won't have a token; however, it will be marked
@@ -392,7 +403,7 @@ public class TransitionTests extends WindowTestsBase {
ArrayList<WindowContainer> targets = Transition.calculateTargets(
transition.mParticipants, transition.mChanges);
TransitionInfo info = Transition.calculateTransitionInfo(
- 0, 0, targets, transition.mChanges);
+ 0, 0, targets, transition.mChanges, mMockT);
// The wallpaper is not organized, so it won't have a token; however, it will be marked
// as IS_WALLPAPER
assertEquals(FLAG_IS_WALLPAPER, info.getChanges().get(0).getFlags());
@@ -474,7 +485,8 @@ public class TransitionTests extends WindowTestsBase {
participants.add(changeTask);
final ArrayList<WindowContainer> targets =
Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
+ mMockT);
// Root changes should always be considered independent
assertTrue(isIndependent(
info.getChange(openTask.mRemoteToken.toWindowContainerToken()), info));
@@ -526,7 +538,8 @@ public class TransitionTests extends WindowTestsBase {
participants.add(oldTask);
participants.add(newTask);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
+ mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -566,7 +579,8 @@ public class TransitionTests extends WindowTestsBase {
participants.add(oldTask);
participants.add(newTask);
ArrayList<WindowContainer> targets = Transition.calculateTargets(participants, changes);
- TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes);
+ TransitionInfo info = Transition.calculateTransitionInfo(transit, flags, targets, changes,
+ mMockT);
assertEquals(2, info.getChanges().size());
assertEquals(transit, info.getType());
@@ -577,7 +591,7 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testTimeout() {
final TransitionController controller = new TransitionController(mAtm,
- mock(TaskSnapshotController.class));
+ mock(TaskSnapshotController.class), mock(TransitionTracer.class));
final BLASTSyncEngine sync = new BLASTSyncEngine(mWm);
final CountDownLatch latch = new CountDownLatch(1);
// When the timeout is reached, it will finish the sync-group and notify transaction ready.
@@ -608,7 +622,7 @@ public class TransitionTests extends WindowTestsBase {
final int flags = 0;
final TransitionInfo info = Transition.calculateTransitionInfo(transition.mType, flags,
Transition.calculateTargets(transition.mParticipants, transition.mChanges),
- transition.mChanges);
+ transition.mChanges, mMockT);
transition.abort();
return info.getChanges().get(0);
};
@@ -654,6 +668,11 @@ public class TransitionTests extends WindowTestsBase {
mDisplayContent.setLastHasContent();
mDisplayContent.requestChangeTransitionIfNeeded(1 /* any changes */,
null /* displayChange */);
+ assertEquals(WindowContainer.SYNC_STATE_NONE, statusBar.mSyncState);
+ assertEquals(WindowContainer.SYNC_STATE_NONE, navBar.mSyncState);
+ assertEquals(WindowContainer.SYNC_STATE_NONE, screenDecor.mSyncState);
+ assertEquals(WindowContainer.SYNC_STATE_WAITING_FOR_DRAW, ime.mSyncState);
+
final AsyncRotationController asyncRotationController =
mDisplayContent.getAsyncRotationController();
assertNotNull(asyncRotationController);
@@ -667,6 +686,7 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(ime.mToken.inTransition());
assertTrue(task.inTransition());
assertTrue(asyncRotationController.isTargetToken(decorToken));
+ assertTrue(asyncRotationController.shouldFreezeInsetsPosition(navBar));
screenDecor.setOrientationChanging(false);
// Status bar finishes drawing before the start transaction. Its fade-in animation will be
@@ -767,7 +787,7 @@ public class TransitionTests extends WindowTestsBase {
player.start();
player.finish();
- app.getTask().clearSyncState();
+ app.getTask().finishSync(mWm.mTransactionFactory.get(), false /* cancel */);
// The open transition is finished. Continue to play seamless display change transition,
// so the previous async rotation controller should still exist.
@@ -777,6 +797,10 @@ public class TransitionTests extends WindowTestsBase {
assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
assertNotNull(mDisplayContent.getAsyncRotationController());
+ // The app is still in transition, so the callback should be no-op.
+ mDisplayContent.mTransitionController.dispatchLegacyAppTransitionFinished(app);
+ assertTrue(mDisplayContent.hasTopFixedRotationLaunchingApp());
+
statusBar.setOrientationChanging(true);
player.startTransition();
// Non-app windows should not be collected.
@@ -814,8 +838,11 @@ public class TransitionTests extends WindowTestsBase {
player.onTransactionReady(mDisplayContent.getSyncTransaction());
final DisplayRotation displayRotation = mDisplayContent.getDisplayRotation();
+ final RemoteDisplayChangeController displayChangeController = mDisplayContent
+ .mRemoteDisplayChangeController;
spyOn(displayRotation);
- doReturn(true).when(displayRotation).isWaitingForRemoteRotation();
+ spyOn(displayChangeController);
+ doReturn(true).when(displayChangeController).isWaitingForRemoteDisplayChange();
doReturn(prevRotation + 1).when(displayRotation).rotationForOrientation(
anyInt() /* orientation */, anyInt() /* lastRotation */);
// Rotation update is skipped while the recents animation is running.
@@ -831,7 +858,8 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testIntermediateVisibility() {
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
- final TransitionController controller = new TransitionController(mAtm, snapshotController);
+ final TransitionController controller = new TransitionController(mAtm, snapshotController,
+ mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* appThread */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
@@ -895,7 +923,8 @@ public class TransitionTests extends WindowTestsBase {
@Test
public void testTransientLaunch() {
final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
- final TransitionController controller = new TransitionController(mAtm, snapshotController);
+ final TransitionController controller = new TransitionController(mAtm, snapshotController,
+ mock(TransitionTracer.class));
final ITransitionPlayer player = new ITransitionPlayer.Default();
controller.registerTransitionPlayer(player, null /* appThread */);
final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
@@ -955,6 +984,67 @@ public class TransitionTests extends WindowTestsBase {
verify(snapshotController, times(1)).recordTaskSnapshot(eq(task1), eq(false));
}
+ @Test
+ public void testNotReadyPushPop() {
+ final TaskSnapshotController snapshotController = mock(TaskSnapshotController.class);
+ final TransitionController controller = new TransitionController(mAtm, snapshotController,
+ mock(TransitionTracer.class));
+ final ITransitionPlayer player = new ITransitionPlayer.Default();
+ controller.registerTransitionPlayer(player, null /* appThread */);
+ final Transition openTransition = controller.createTransition(TRANSIT_OPEN);
+
+ // Start out with task2 visible and set up a transition that closes task2 and opens task1
+ final Task task1 = createTask(mDisplayContent);
+ openTransition.collectExistenceChange(task1);
+
+ assertFalse(openTransition.allReady());
+
+ openTransition.setAllReady();
+
+ openTransition.deferTransitionReady();
+ assertFalse(openTransition.allReady());
+
+ openTransition.continueTransitionReady();
+ assertTrue(openTransition.allReady());
+ }
+
+ @Test
+ public void testIsEmbeddedChange() {
+ final Transition transition = createTestTransition(TRANSIT_OPEN);
+ final ArrayMap<WindowContainer, Transition.ChangeInfo> changes = transition.mChanges;
+ final ArraySet<WindowContainer> participants = transition.mParticipants;
+
+ final Task task = createTask(mDisplayContent);
+ final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+ mAtm.mTaskFragmentOrganizerController.registerOrganizer(
+ ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
+ final TaskFragment embeddedTf = new TaskFragmentBuilder(mAtm)
+ .setParentTask(task)
+ .createActivityCount(2)
+ .setOrganizer(organizer)
+ .build();
+ final ActivityRecord closingActivity = embeddedTf.getBottomMostActivity();
+ final ActivityRecord openingActivity = embeddedTf.getTopMostActivity();
+ // Start states.
+ changes.put(embeddedTf, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(closingActivity, new Transition.ChangeInfo(true /* vis */, false /* exChg */));
+ changes.put(openingActivity, new Transition.ChangeInfo(false /* vis */, true /* exChg */));
+ // End states.
+ closingActivity.mVisibleRequested = false;
+ openingActivity.mVisibleRequested = true;
+
+ participants.add(closingActivity);
+ participants.add(openingActivity);
+ final ArrayList<WindowContainer> targets = Transition.calculateTargets(
+ participants, changes);
+ final TransitionInfo info = Transition.calculateTransitionInfo(
+ transition.mType, 0 /* flags */, targets, changes, mMockT);
+
+ assertEquals(2, info.getChanges().size());
+ assertTrue((info.getChanges().get(0).getFlags() & FLAG_IS_EMBEDDED) != 0);
+ assertTrue((info.getChanges().get(1).getFlags() & FLAG_IS_EMBEDDED) != 0);
+ }
+
private static void makeTaskOrganized(Task... tasks) {
final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
for (Task t : tasks) {
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 1f68608d2ce6..fba4ff1f1c25 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -35,6 +35,7 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
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.anyBoolean;
import static org.mockito.ArgumentMatchers.anyFloat;
@@ -82,10 +83,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
assertFalse(dc.mWallpaperController.canScreenshotWallpaper());
// No wallpaper WSA Surface
- WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
- true, dc, true /* ownerCanManageAppTokens */);
- WindowState wallpaperWindow = createWindow(null /* parent */, TYPE_WALLPAPER,
- wallpaperWindowToken, "wallpaperWindow");
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
assertFalse(dc.mWallpaperController.canScreenshotWallpaper());
// Wallpaper with not visible WSA surface.
@@ -107,13 +105,10 @@ public class WallpaperControllerTests extends WindowTestsBase {
@Test
public void testWallpaperSizeWithFixedTransform() {
// No wallpaper
- final DisplayContent dc = createNewDisplay();
+ final DisplayContent dc = mDisplayContent;
// No wallpaper WSA Surface
- WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
- true, dc, true /* ownerCanManageAppTokens */);
- WindowState wallpaperWindow = createWindow(null /* parent */, TYPE_WALLPAPER,
- wallpaperWindowToken, "wallpaperWindow");
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
WindowManager.LayoutParams attrs = wallpaperWindow.getAttrs();
Rect bounds = dc.getBounds();
@@ -153,7 +148,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
final WmDisplayCutout cutout = dc.calculateDisplayCutoutForRotation(Surface.ROTATION_0);
final DisplayFrames displayFrames = new DisplayFrames(dc.getDisplayId(), new InsetsState(),
info, cutout, RoundedCorners.NO_ROUNDED_CORNERS, new PrivacyIndicatorBounds());
- wallpaperWindowToken.applyFixedRotationTransform(info, displayFrames, config);
+ wallpaperWindow.mToken.applyFixedRotationTransform(info, displayFrames, config);
// Check that the wallpaper has the same frame in landscape than in portrait
assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getConfiguration().orientation);
@@ -163,10 +158,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
@Test
public void testWallpaperZoom() throws RemoteException {
final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, dc, true /* ownerCanManageAppTokens */);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
wallpaperWindow.getAttrs().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
@@ -189,10 +181,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
@Test
public void testWallpaperZoom_shouldNotScaleWallpaper() throws RemoteException {
final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, dc, true /* ownerCanManageAppTokens */);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
wallpaperWindow.getAttrs().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
@@ -219,11 +208,7 @@ public class WallpaperControllerTests extends WindowTestsBase {
@Test
public void testWallpaperZoom_multipleCallers() {
final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
- final WallpaperWindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm,
- mock(IBinder.class), true, dc,
- true /* ownerCanManageAppTokens */);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, wallpaperWindowToken,
- "wallpaperWindow");
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
wallpaperWindow.getAttrs().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS;
@@ -261,6 +246,22 @@ public class WallpaperControllerTests extends WindowTestsBase {
assertEquals(otherWindowInitialZoom, wallpaperWindow.mWallpaperZoomOut, .01f);
}
+ @Test
+ public void testUpdateWallpaperTarget() {
+ final DisplayContent dc = mDisplayContent;
+ final WindowState homeWin = createWallpaperTargetWindow(dc);
+ final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
+ doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
+ mWm.setRecentsAnimationController(recentsController);
+
+ dc.mWallpaperController.adjustWallpaperWindows();
+ assertEquals(appWin, dc.mWallpaperController.getWallpaperTarget());
+ // The wallpaper target is gone, so it should adjust to the next target.
+ appWin.removeImmediately();
+ assertEquals(homeWin, dc.mWallpaperController.getWallpaperTarget());
+ }
+
/**
* Tests that the windowing mode of the wallpaper window must always be fullscreen.
*/
@@ -278,37 +279,51 @@ public class WallpaperControllerTests extends WindowTestsBase {
assertEquals(WINDOWING_MODE_FULLSCREEN, token.getWindowingMode());
}
- @UseTestDisplay(addWindows = W_ACTIVITY)
@Test
public void testFixedRotationRecentsAnimatingTask() {
+ final WindowState wallpaperWindow = createWallpaperWindow(mDisplayContent);
+ final WallpaperWindowToken wallpaperToken = wallpaperWindow.mToken.asWallpaperToken();
+ final WindowState appWin = createWindow(null, TYPE_BASE_APPLICATION, "app");
+ makeWindowVisible(appWin);
+ final ActivityRecord r = appWin.mActivityRecord;
final RecentsAnimationController recentsController = mock(RecentsAnimationController.class);
- doReturn(true).when(recentsController).isWallpaperVisible(eq(mAppWindow));
+ doReturn(true).when(recentsController).isWallpaperVisible(eq(appWin));
mWm.setRecentsAnimationController(recentsController);
- mAppWindow.mActivityRecord.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
+ r.applyFixedRotationTransform(mDisplayContent.getDisplayInfo(),
mDisplayContent.mDisplayFrames, mDisplayContent.getConfiguration());
- mAppWindow.mActivityRecord.mVisibleRequested = true;
+ // Invisible requested activity should not share its rotation transform.
+ r.mVisibleRequested = false;
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
+ assertFalse(wallpaperToken.hasFixedRotationTransform());
- assertEquals(mAppWindow, mDisplayContent.mWallpaperController.getWallpaperTarget());
// Wallpaper should link the transform of its target.
- assertTrue(mAppWindow.mActivityRecord.hasFixedRotationTransform());
-
- mAppWindow.mActivityRecord.finishFixedRotationTransform();
- // Invisible requested activity should not share its rotation transform.
- mAppWindow.mActivityRecord.mVisibleRequested = false;
+ r.mVisibleRequested = true;
mDisplayContent.mWallpaperController.adjustWallpaperWindows();
+ assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
+ assertTrue(r.hasFixedRotationTransform());
+ assertTrue(wallpaperToken.hasFixedRotationTransform());
- assertFalse(mAppWindow.mActivityRecord.hasFixedRotationTransform());
+ // The case with shell transition.
+ registerTestTransitionPlayer();
+ final Transition t = r.mTransitionController.createTransition(TRANSIT_OPEN);
+ final ActivityRecord recents = mock(ActivityRecord.class);
+ t.collect(r.getTask());
+ r.mTransitionController.setTransientLaunch(recents, r.getTask());
+ // The activity in restore-below task should not be the target if keyguard is not locked.
+ mDisplayContent.mWallpaperController.adjustWallpaperWindows();
+ assertNotEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
+ // The activity in restore-below task should be the target if keyguard is occluded.
+ doReturn(true).when(mDisplayContent).isKeyguardLocked();
+ mDisplayContent.mWallpaperController.adjustWallpaperWindows();
+ assertEquals(appWin, mDisplayContent.mWallpaperController.getWallpaperTarget());
}
@Test
public void testWallpaperTokenVisibility() {
final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
- final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class),
- true, dc, true /* ownerCanManageAppTokens */);
- final WindowState wallpaperWindow = createWindow(null, TYPE_WALLPAPER, token,
- "wallpaperWindow");
+ final WindowState wallpaperWindow = createWallpaperWindow(dc);
+ final WallpaperWindowToken token = wallpaperWindow.mToken.asWallpaperToken();
wallpaperWindow.setHasSurface(true);
// Set-up mock shell transitions
@@ -350,6 +365,13 @@ public class WallpaperControllerTests extends WindowTestsBase {
assertTrue(token.isVisible());
}
+ private WindowState createWallpaperWindow(DisplayContent dc) {
+ final WindowToken wallpaperWindowToken = new WallpaperWindowToken(mWm, mock(IBinder.class),
+ true /* explicit */, dc, true /* ownerCanManageAppTokens */);
+ return createWindow(null /* parent */, TYPE_WALLPAPER, wallpaperWindowToken,
+ "wallpaperWindow");
+ }
+
private WindowState createWallpaperTargetWindow(DisplayContent dc) {
final ActivityRecord homeActivity = new ActivityBuilder(mWm.mAtmService)
.setTask(dc.getDefaultTaskDisplayArea().getRootHomeTask())
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
index 1715a295ded3..c8ea70c5d650 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java
@@ -22,8 +22,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_2;
+import static android.view.InsetsState.ITYPE_BOTTOM_GENERIC_OVERLAY;
+import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
import static android.view.WindowManager.TRANSIT_CLOSE;
@@ -1302,40 +1302,42 @@ public class WindowContainerTests extends WindowTestsBase {
TYPE_BASE_APPLICATION);
attrs2.setTitle("AppWindow2");
activity2.addWindow(createWindowState(attrs2, activity2));
- Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
- Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
-
- rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
- container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
-
- InsetsSource navigationBarInsetsProvider1Source = new InsetsSource(
- ITYPE_LOCAL_NAVIGATION_BAR_1);
- navigationBarInsetsProvider1Source.setFrame(navigationBarInsetsRect1);
- navigationBarInsetsProvider1Source.setVisible(true);
- InsetsSource navigationBarInsetsProvider2Source = new InsetsSource(
- ITYPE_LOCAL_NAVIGATION_BAR_2);
- navigationBarInsetsProvider2Source.setFrame(navigationBarInsetsRect2);
- navigationBarInsetsProvider2Source.setVisible(true);
+ Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
+ Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
+
+ rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1,
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
+ container.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2,
+ new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
+
+ InsetsSource genericOverlayInsetsProvider1Source = new InsetsSource(
+ ITYPE_TOP_GENERIC_OVERLAY);
+ genericOverlayInsetsProvider1Source.setFrame(genericOverlayInsetsRect1);
+ genericOverlayInsetsProvider1Source.setVisible(true);
+ InsetsSource genericOverlayInsetsProvider2Source = new InsetsSource(
+ ITYPE_BOTTOM_GENERIC_OVERLAY);
+ genericOverlayInsetsProvider2Source.setFrame(genericOverlayInsetsRect2);
+ genericOverlayInsetsProvider2Source.setVisible(true);
activity0.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY));
}, true);
activity1.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
- assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
+ assertEquals(genericOverlayInsetsRect2,
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
activity2.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
- assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
+ assertEquals(genericOverlayInsetsRect2,
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
}
@@ -1344,7 +1346,7 @@ public class WindowContainerTests extends WindowTestsBase {
/*
___ rootTask ________________________________________
| | |
- activity0 navigationBarInsetsProvider1 navigationBarInsetsProvider2
+ activity0 genericOverlayInsetsProvider1 genericOverlayInsetsProvider2
*/
final Task rootTask = createTask(mDisplayContent);
@@ -1355,22 +1357,22 @@ public class WindowContainerTests extends WindowTestsBase {
attrs.setTitle("AppWindow0");
activity0.addWindow(createWindowState(attrs, activity0));
- Rect navigationBarInsetsRect1 = new Rect(0, 200, 1080, 700);
- Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
+ Rect genericOverlayInsetsRect1 = new Rect(0, 200, 1080, 700);
+ Rect genericOverlayInsetsRect2 = new Rect(0, 0, 1080, 200);
- rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect1,
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
activity0.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect1,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+ assertEquals(genericOverlayInsetsRect1,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
}, true);
- rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ rootTask.addLocalRectInsetsSourceProvider(genericOverlayInsetsRect2,
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
activity0.forAllWindows(window -> {
- assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1).getFrame());
+ assertEquals(genericOverlayInsetsRect2,
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY).getFrame());
}, true);
}
@@ -1412,30 +1414,32 @@ public class WindowContainerTests extends WindowTestsBase {
Rect navigationBarInsetsRect2 = new Rect(0, 0, 1080, 200);
rootTask.addLocalRectInsetsSourceProvider(navigationBarInsetsRect1,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
container.addLocalRectInsetsSourceProvider(navigationBarInsetsRect2,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_2});
+ new int[]{ITYPE_BOTTOM_GENERIC_OVERLAY});
mDisplayContent.getInsetsStateController().onPostLayout();
- rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ rootTask.removeLocalInsetsSourceProvider(new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mDisplayContent.getInsetsStateController().onPostLayout();
activity0.forAllWindows(window -> {
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2));
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY));
}, true);
activity1.forAllWindows(window -> {
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
activity2.forAllWindows(window -> {
assertEquals(null,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_1));
+ window.getInsetsState().peekSource(ITYPE_TOP_GENERIC_OVERLAY));
assertEquals(navigationBarInsetsRect2,
- window.getInsetsState().peekSource(ITYPE_LOCAL_NAVIGATION_BAR_2).getFrame());
+ window.getInsetsState().peekSource(ITYPE_BOTTOM_GENERIC_OVERLAY)
+ .getFrame());
}, true);
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
index ea18e58fe999..739e783e7c1b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowLayoutTests.java
@@ -72,7 +72,7 @@ public class WindowLayoutTests {
private static final Insets WATERFALL_INSETS = Insets.of(6, 0, 12, 0);
private final WindowLayout mWindowLayout = new WindowLayout();
- private final ClientWindowFrames mOutFrames = new ClientWindowFrames();
+ private final ClientWindowFrames mFrames = new ClientWindowFrames();
private WindowManager.LayoutParams mAttrs;
private InsetsState mState;
@@ -82,7 +82,6 @@ public class WindowLayoutTests {
private int mRequestedWidth;
private int mRequestedHeight;
private InsetsVisibilities mRequestedVisibilities;
- private Rect mAttachedWindowFrame;
private float mCompatScale;
@Before
@@ -100,14 +99,14 @@ public class WindowLayoutTests {
mRequestedWidth = DISPLAY_WIDTH;
mRequestedHeight = DISPLAY_HEIGHT;
mRequestedVisibilities = new InsetsVisibilities();
- mAttachedWindowFrame = null;
mCompatScale = 1f;
+ mFrames.attachedFrame = null;
}
private void computeFrames() {
mWindowLayout.computeFrames(mAttrs, mState, mDisplayCutoutSafe, mWindowBounds,
mWindowingMode, mRequestedWidth, mRequestedHeight, mRequestedVisibilities,
- mAttachedWindowFrame, mCompatScale, mOutFrames);
+ mCompatScale, mFrames);
}
private void addDisplayCutout() {
@@ -145,9 +144,9 @@ public class WindowLayoutTests {
public void defaultParams() {
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -156,9 +155,9 @@ public class WindowLayoutTests {
mRequestedHeight = UNSPECIFIED_LENGTH;
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -172,9 +171,9 @@ public class WindowLayoutTests {
mAttrs.gravity = Gravity.LEFT | Gravity.TOP;
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertRect(0, STATUS_BAR_HEIGHT, width, STATUS_BAR_HEIGHT + height, mFrames.frame);
}
@Test
@@ -186,11 +185,24 @@ public class WindowLayoutTests {
computeFrames();
assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertRect(0, top, DISPLAY_WIDTH, DISPLAY_HEIGHT - NAVIGATION_BAR_HEIGHT,
- mOutFrames.frame);
+ mFrames.frame);
+ }
+
+ @Test
+ public void attachedFrame() {
+ final int bottom = (DISPLAY_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT) / 2;
+ mFrames.attachedFrame = new Rect(0, STATUS_BAR_HEIGHT, DISPLAY_WIDTH, bottom);
+ mRequestedWidth = UNSPECIFIED_LENGTH;
+ mRequestedHeight = UNSPECIFIED_LENGTH;
+ computeFrames();
+
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertEquals(mFrames.attachedFrame, mFrames.parentFrame);
+ assertEquals(mFrames.attachedFrame, mFrames.frame);
}
@Test
@@ -198,9 +210,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(WindowInsets.Type.statusBars());
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame);
}
@Test
@@ -208,9 +220,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(WindowInsets.Type.navigationBars());
computeFrames();
- assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(0, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -218,9 +230,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
@Test
@@ -228,9 +240,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsSides(WindowInsets.Side.all());
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -238,9 +250,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsSides(WindowInsets.Side.TOP);
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, 0, mFrames.frame);
}
@Test
@@ -248,9 +260,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsSides(0);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
@Test
@@ -259,9 +271,9 @@ public class WindowLayoutTests {
mState.getSource(ITYPE_NAVIGATION_BAR).setVisible(false);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
@Test
@@ -271,9 +283,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsIgnoringVisibility(true);
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.frame);
}
@Test
@@ -284,9 +296,9 @@ public class WindowLayoutTests {
mAttrs.privateFlags |= PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME;
computeFrames();
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mOutFrames.displayFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mOutFrames.parentFrame);
- assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mOutFrames.frame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, NAVIGATION_BAR_HEIGHT, mFrames.displayFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.parentFrame);
+ assertInsetByTopBottom(STATUS_BAR_HEIGHT, IME_HEIGHT, mFrames.frame);
}
@Test
@@ -297,11 +309,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, DISPLAY_CUTOUT_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -312,11 +324,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -327,9 +339,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.frame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame);
}
@Test
@@ -344,9 +356,9 @@ public class WindowLayoutTests {
mAttrs.privateFlags |= PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
computeFrames();
- assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mOutFrames.displayFrame);
- assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mOutFrames.parentFrame);
- assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mOutFrames.frame);
+ assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.displayFrame);
+ assertRect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT, mFrames.parentFrame);
+ assertRect(0, 0, DISPLAY_WIDTH, height + DISPLAY_CUTOUT_HEIGHT, mFrames.frame);
}
@Test
@@ -359,11 +371,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -373,9 +385,9 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.displayFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.parentFrame);
- assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mOutFrames.frame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.displayFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.parentFrame);
+ assertInsetBy(WATERFALL_INSETS.left, 0, WATERFALL_INSETS.right, 0, mFrames.frame);
}
@Test
@@ -386,11 +398,11 @@ public class WindowLayoutTests {
computeFrames();
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.displayFrame);
+ mFrames.displayFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.parentFrame);
+ mFrames.parentFrame);
assertInsetBy(WATERFALL_INSETS.left, STATUS_BAR_HEIGHT, WATERFALL_INSETS.right, 0,
- mOutFrames.frame);
+ mFrames.frame);
}
@Test
@@ -400,8 +412,8 @@ public class WindowLayoutTests {
mAttrs.setFitInsetsTypes(0);
computeFrames();
- assertInsetByTopBottom(0, 0, mOutFrames.displayFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.parentFrame);
- assertInsetByTopBottom(0, 0, mOutFrames.frame);
+ assertInsetByTopBottom(0, 0, mFrames.displayFrame);
+ assertInsetByTopBottom(0, 0, mFrames.parentFrame);
+ assertInsetByTopBottom(0, 0, mFrames.frame);
}
}
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 a0c20c2c6b68..1a64f5e3a356 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -49,6 +49,7 @@ import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
@@ -60,6 +61,7 @@ import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.View;
import android.view.WindowManager;
+import android.window.WindowContainerToken;
import androidx.test.filters.SmallTest;
@@ -281,7 +283,7 @@ public class WindowManagerServiceTests extends WindowTestsBase {
mWm.addWindow(session, new TestIWindow(), params, View.VISIBLE, DEFAULT_DISPLAY,
UserHandle.USER_SYSTEM, new InsetsVisibilities(), null, new InsetsState(),
- new InsetsSourceControl[0]);
+ new InsetsSourceControl[0], new Rect());
verify(mWm.mWindowContextListenerController, never()).registerWindowContainerListener(any(),
any(), anyInt(), anyInt(), any());
@@ -316,4 +318,76 @@ public class WindowManagerServiceTests extends WindowTestsBase {
verify(mWm.mInputManager).setInTouchMode(
!currentTouchMode, callingPid, callingUid, /* hasPermission= */ false);
}
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
+ Binder cookie = new Binder("test cookie");
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isNull();
+
+ final ActivityRecord testActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+
+ wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isNull();
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
+ final Binder cookie = new Binder("ginger cookie");
+ final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
+ setupActivityWithLaunchCookie(cookie, launchRootTask);
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
+ assertThat(wct).isEqualTo(launchRootTask);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
+ final Binder cookie1 = new Binder("ginger cookie");
+ final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
+ setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+
+ setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
+ mock(WindowContainerToken.class));
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
+ assertThat(wct).isEqualTo(launchRootTask1);
+ }
+
+ @Test
+ public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
+ setupActivityWithLaunchCookie(new Binder("ginger cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
+ mock(WindowContainerToken.class));
+
+ setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
+ mock(WindowContainerToken.class));
+
+ WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+ new Binder("some other cookie"));
+ assertThat(wct).isNull();
+ }
+
+ private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+ final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
+ when(remoteToken.toWindowContainerToken()).thenReturn(wct);
+ final ActivityRecord testActivity = new ActivityBuilder(mAtm)
+ .setCreateTask(true)
+ .build();
+ testActivity.mLaunchCookie = launchCookie;
+ testActivity.getTask().mRemoteToken = remoteToken;
+ }
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 40ca2506fab1..84c2c551de85 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -30,7 +30,7 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
-import static android.view.InsetsState.ITYPE_LOCAL_NAVIGATION_BAR_1;
+import static android.view.InsetsState.ITYPE_TOP_GENERIC_OVERLAY;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -223,7 +223,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTaskVanished(organizer, false /* expectVanished */, rootTask);
+ verify(organizer, times(0)).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@@ -297,7 +297,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
// Ensure events dispatch to organizer.
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
- assertTaskVanished(organizer, true /* expectVanished */, rootTask);
+ verify(organizer, times(0)).onTaskVanished(any());
assertFalse(rootTask.isOrganized());
}
@@ -341,7 +341,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+ verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
@@ -395,6 +395,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
assertFalse(task2.isAttached());
// Normal task should keep.
assertTrue(task.isAttached());
+ verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
@@ -439,7 +440,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents();
verify(organizer, times(3))
.onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
- assertTaskVanished(organizer2, true /* expectVanished */, rootTask, rootTask2, rootTask3);
+ verify(organizer2, times(0)).onTaskVanished(any());
}
@Test
@@ -458,6 +459,23 @@ public class WindowOrganizerTests extends WindowTestsBase {
}
@Test
+ public void testRegisterTaskOrganizerWithExistingTasks_noSurfaceControl()
+ throws RemoteException {
+ final Task rootTask = createRootTask();
+ final Task task = createTask(rootTask);
+ final Task rootTask2 = createRootTask();
+ final Task task2 = createTask(rootTask2);
+ rootTask2.setSurfaceControl(null);
+ ArrayList<TaskAppearedInfo> existingTasks = new ArrayList<>();
+ final ITaskOrganizer organizer = registerMockOrganizer(existingTasks);
+ assertContainsTasks(existingTasks, rootTask);
+
+ // Verify we don't get onTaskAppeared if we are returned the tasks
+ verify(organizer, never())
+ .onTaskAppeared(any(RunningTaskInfo.class), any(SurfaceControl.class));
+ }
+
+ @Test
public void testTaskTransaction() {
removeGlobalMinSizeRestriction();
final Task rootTask = new TaskBuilder(mSupervisor)
@@ -522,7 +540,10 @@ public class WindowOrganizerTests extends WindowTestsBase {
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode());
- assertEquals(WINDOWING_MODE_PINNED, rootTask.getWindowingMode());
+ // Get the root task from the PIP activity record again, since the PIP root task may have
+ // changed when the activity entered PIP mode.
+ final Task pipRootTask = record.getRootTask();
+ assertEquals(WINDOWING_MODE_PINNED, pipRootTask.getWindowingMode());
}
@Test
@@ -687,7 +708,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
final RunningTaskInfo info2 = task2.getTaskInfo();
WindowContainerTransaction wct = new WindowContainerTransaction();
- wct.setAdjacentRoots(info1.token, info2.token, false /* moveTogether */);
+ wct.setAdjacentRoots(info1.token, info2.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(task1.getAdjacentTaskFragment(), task2);
assertEquals(task2.getAdjacentTaskFragment(), task1);
@@ -697,8 +718,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertEquals(dc.getDefaultTaskDisplayArea().mLaunchAdjacentFlagRootTask, task1);
- task1.setAdjacentTaskFragment(null, false /* moveTogether */);
- task2.setAdjacentTaskFragment(null, false /* moveTogether */);
+ task1.setAdjacentTaskFragment(null);
+ task2.setAdjacentTaskFragment(null);
wct = new WindowContainerTransaction();
wct.clearLaunchAdjacentFlagRoot(info1.token);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
@@ -758,11 +779,11 @@ public class WindowOrganizerTests extends WindowTestsBase {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), navigationBarInsetsProviderRect,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders
- .valueAt(0).getSource().getType()).isEqualTo(ITYPE_LOCAL_NAVIGATION_BAR_1);
+ .valueAt(0).getSource().getType()).isEqualTo(ITYPE_TOP_GENERIC_OVERLAY);
}
@Test
@@ -778,12 +799,12 @@ public class WindowOrganizerTests extends WindowTestsBase {
final WindowContainerTransaction wct = new WindowContainerTransaction();
wct.addRectInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
.toWindowContainerToken(), navigationBarInsetsProviderRect,
- new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
final WindowContainerTransaction wct2 = new WindowContainerTransaction();
wct2.removeInsetsProvider(navigationBarInsetsReceiverTask.mRemoteToken
- .toWindowContainerToken(), new int[]{ITYPE_LOCAL_NAVIGATION_BAR_1});
+ .toWindowContainerToken(), new int[]{ITYPE_TOP_GENERIC_OVERLAY});
mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct2);
assertThat(navigationBarInsetsReceiverTask.mLocalInsetsSourceProviders.size()).isEqualTo(0);
@@ -1015,6 +1036,8 @@ public class WindowOrganizerTests extends WindowTestsBase {
final ActivityRecord record = createActivityRecordWithParentTask(mDisplayContent,
WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
record.info.flags |= ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
+ record.setPictureInPictureParams(new PictureInPictureParams.Builder()
+ .setAutoEnterEnabled(true).build());
spyOn(record);
doReturn(true).when(record).checkEnterPictureInPictureState(any(), anyBoolean());
@@ -1259,7 +1282,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_APPEARED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1279,7 +1302,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
rootTask.removeImmediately();
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(0, pendingEvents.size());
}
@@ -1297,7 +1320,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription"));
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1306,7 +1329,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
record.setTaskDescription(new ActivityManager.TaskDescription("TestDescription2"));
waitUntilHandlersIdle();
- pendingEvents = getTaskPendingEvent(rootTask);
+ pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_INFO_CHANGED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription2",
@@ -1329,7 +1352,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
rootTask.removeImmediately();
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
assertEquals("TestDescription",
@@ -1350,7 +1373,7 @@ public class WindowOrganizerTests extends WindowTestsBase {
rootTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
@@ -1370,14 +1393,16 @@ public class WindowOrganizerTests extends WindowTestsBase {
new IRequestFinishCallback.Default());
waitUntilHandlersIdle();
- ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(rootTask);
+ ArrayList<PendingTaskEvent> pendingEvents = getTaskPendingEvent(organizer, rootTask);
assertEquals(1, pendingEvents.size());
assertEquals(PendingTaskEvent.EVENT_VANISHED, pendingEvents.get(0).mEventType);
}
- private ArrayList<PendingTaskEvent> getTaskPendingEvent(Task task) {
+ private ArrayList<PendingTaskEvent> getTaskPendingEvent(ITaskOrganizer organizer, Task task) {
ArrayList<PendingTaskEvent> total =
- mWm.mAtmService.mTaskOrganizerController.getPendingEventList();
+ mWm.mAtmService.mTaskOrganizerController
+ .getTaskOrganizerPendingEvents(organizer.asBinder())
+ .getPendingEventList();
ArrayList<PendingTaskEvent> result = new ArrayList();
for (int i = 0; i < total.size(); i++) {
@@ -1499,7 +1524,11 @@ public class WindowOrganizerTests extends WindowTestsBase {
verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
clearInvocations(mWm.mAtmService.mRootWindowContainer);
- t.setWindowingMode(wct, WINDOWING_MODE_FULLSCREEN);
+ // The token for the PIP root task may have changed when the task entered PIP mode, so do
+ // not reuse the one from above.
+ final WindowContainerToken newToken =
+ record.getRootTask().mRemoteToken.toWindowContainerToken();
+ t.setWindowingMode(newToken, WINDOWING_MODE_FULLSCREEN);
mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
verify(mWm.mAtmService.mRootWindowContainer).resumeFocusedTasksTopActivities();
}
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 746f2b50ac3a..3abf7ce665ae 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -21,6 +21,7 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -39,24 +40,30 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.Manifest;
+import android.app.ActivityManager;
+import android.app.ClientTransactionHandler;
import android.app.IApplicationThread;
+import android.app.servertransaction.ConfigurationChangeItem;
import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.LocaleList;
+import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mockito;
@@ -282,6 +289,39 @@ public class WindowProcessControllerTests extends WindowTestsBase {
}
@Test
+ public void testCachedStateConfigurationChange() throws RemoteException {
+ final ClientLifecycleManager clientManager = mAtm.getLifecycleManager();
+ doNothing().when(clientManager).scheduleTransaction(any(), any());
+ final IApplicationThread thread = mWpc.getThread();
+ final Configuration newConfig = new Configuration(mWpc.getConfiguration());
+ newConfig.densityDpi += 100;
+ // Non-cached state will send the change directly.
+ mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
+ clearInvocations(clientManager);
+ mWpc.onConfigurationChanged(newConfig);
+ verify(clientManager).scheduleTransaction(eq(thread), any());
+
+ // Cached state won't send the change.
+ clearInvocations(clientManager);
+ mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_CACHED_ACTIVITY);
+ newConfig.densityDpi += 100;
+ mWpc.onConfigurationChanged(newConfig);
+ verify(clientManager, never()).scheduleTransaction(eq(thread), any());
+
+ // Cached -> non-cached will send the previous deferred config immediately.
+ mWpc.setReportedProcState(ActivityManager.PROCESS_STATE_RECEIVER);
+ final ArgumentCaptor<ConfigurationChangeItem> captor =
+ ArgumentCaptor.forClass(ConfigurationChangeItem.class);
+ verify(clientManager).scheduleTransaction(eq(thread), captor.capture());
+ final ClientTransactionHandler client = mock(ClientTransactionHandler.class);
+ captor.getValue().preExecute(client, null /* token */);
+ final ArgumentCaptor<Configuration> configCaptor =
+ ArgumentCaptor.forClass(Configuration.class);
+ verify(client).updatePendingConfiguration(configCaptor.capture());
+ assertEquals(newConfig, configCaptor.getValue());
+ }
+
+ @Test
public void testComputeOomAdjFromActivities() {
final ActivityRecord activity = createActivityRecord(mWpc);
activity.mVisibleRequested = true;
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 20935f1d199e..446ec8b2680b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -712,6 +712,25 @@ public class WindowStateTests extends WindowTestsBase {
// Keyguard host window should be always contained. The drawn app or app with starting
// window are unnecessary to draw.
assertEquals(Arrays.asList(keyguardHostWindow, startingWindow), outWaitingForDrawn);
+
+ // No need to wait for a window of invisible activity even if the window has surface.
+ final WindowState invisibleApp = mAppWindow;
+ invisibleApp.mActivityRecord.mVisibleRequested = false;
+ invisibleApp.mActivityRecord.allDrawn = false;
+ outWaitingForDrawn.clear();
+ invisibleApp.requestDrawIfNeeded(outWaitingForDrawn);
+ assertTrue(outWaitingForDrawn.isEmpty());
+
+ // Drawn state should not be changed for insets change when screen is off.
+ spyOn(mWm.mPolicy);
+ doReturn(false).when(mWm.mPolicy).isScreenOn();
+ makeWindowVisibleAndDrawn(startingApp);
+ startingApp.getConfiguration().orientation = 0; // Reset to be the same as last reported.
+ startingApp.getWindowFrames().setInsetsChanged(true);
+ startingApp.updateResizingWindowIfNeeded();
+ assertTrue(mWm.mResizingWindows.contains(startingApp));
+ assertTrue(startingApp.isDrawn());
+ assertFalse(startingApp.getOrientationChanging());
}
@UseTestDisplay(addWindows = W_ABOVE_ACTIVITY)
@@ -984,6 +1003,7 @@ public class WindowStateTests extends WindowTestsBase {
assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
// Verify the IME insets is visible on app, but not for app2 during app task switching.
+ mDisplayContent.computeImeTargetIfNeeded(app.mActivityRecord);
assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
}
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 20fbda428381..0cbf1b2c7cc8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -64,7 +64,6 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityOptions;
-import android.app.IApplicationThread;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -229,14 +228,28 @@ class WindowTestsBase extends SystemServiceTestsBase {
// {@link com.android.internal.R.dimen.config_fixedOrientationLetterboxAspectRatio}, is set
// on some device form factors.
mAtm.mWindowManager.mLetterboxConfiguration.setFixedOrientationLetterboxAspectRatio(0);
- // Ensure letterbox position multiplier is not overridden on any device target.
+ // Ensure letterbox horizontal position multiplier is not overridden on any device target.
// {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
// may be set on some device form factors.
mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
- // Ensure letterbox reachability treatment isn't overridden on any device target.
- // {@link com.android.internal.R.bool.config_letterboxIsReachabilityEnabled},
+ // Ensure letterbox vertical position multiplier is not overridden on any device target.
+ // {@link com.android.internal.R.dimen.config_letterboxHorizontalPositionMultiplier},
+ // may be set on some device form factors.
+ mAtm.mWindowManager.mLetterboxConfiguration.setLetterboxVerticalPositionMultiplier(0.5f);
+ // Ensure letterbox horizontal reachability treatment isn't overridden on any device target.
+ // {@link com.android.internal.R.bool.config_letterboxIsHorizontalReachabilityEnabled},
// may be set on some device form factors.
- mAtm.mWindowManager.mLetterboxConfiguration.setIsReachabilityEnabled(false);
+ mAtm.mWindowManager.mLetterboxConfiguration.setIsHorizontalReachabilityEnabled(false);
+ // Ensure letterbox vertical reachability treatment isn't overridden on any device target.
+ // {@link com.android.internal.R.bool.config_letterboxIsVerticalReachabilityEnabled},
+ // may be set on some device form factors.
+ mAtm.mWindowManager.mLetterboxConfiguration.setIsVerticalReachabilityEnabled(false);
+ // Ensure aspect ratio for unresizable apps isn't overridden on any device target.
+ // {@link com.android.internal.R.bool
+ // .config_letterboxIsSplitScreenAspectRatioForUnresizableAppsEnabled}, may be set on some
+ // device form factors.
+ mAtm.mWindowManager.mLetterboxConfiguration
+ .setIsSplitScreenAspectRatioForUnresizableAppsEnabled(false);
checkDeviceSpecificOverridesNotApplied();
}
@@ -246,7 +259,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
// Revert back to device overrides.
mAtm.mWindowManager.mLetterboxConfiguration.resetFixedOrientationLetterboxAspectRatio();
mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxHorizontalPositionMultiplier();
- mAtm.mWindowManager.mLetterboxConfiguration.resetIsReachabilityEnabled();
+ mAtm.mWindowManager.mLetterboxConfiguration.resetLetterboxVerticalPositionMultiplier();
+ mAtm.mWindowManager.mLetterboxConfiguration.resetIsHorizontalReachabilityEnabled();
+ mAtm.mWindowManager.mLetterboxConfiguration.resetIsVerticalReachabilityEnabled();
+ mAtm.mWindowManager.mLetterboxConfiguration
+ .resetIsSplitScreenAspectRatioForUnresizableAppsEnabled();
}
/**
@@ -794,7 +811,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
}
@Override
- public void topFocusedWindowChanged(String packageName,
+ public void topFocusedWindowChanged(ComponentName component,
InsetsVisibilities requestedVisibilities) {
}
};
@@ -915,13 +932,15 @@ class WindowTestsBase extends SystemServiceTestsBase {
*/
protected static class ActivityBuilder {
static final int DEFAULT_FAKE_UID = 12345;
+ static final String DEFAULT_PROCESS_NAME = "procName";
+ static int sProcNameSeq;
private final ActivityTaskManagerService mService;
private ComponentName mComponent;
private String mTargetActivity;
private Task mTask;
- private String mProcessName = "name";
+ private String mProcessName = DEFAULT_PROCESS_NAME;
private String mAffinity;
private int mUid = DEFAULT_FAKE_UID;
private boolean mCreateTask = false;
@@ -1109,6 +1128,9 @@ class WindowTestsBase extends SystemServiceTestsBase {
aInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT;
aInfo.applicationInfo.packageName = mComponent.getPackageName();
aInfo.applicationInfo.uid = mUid;
+ if (DEFAULT_PROCESS_NAME.equals(mProcessName)) {
+ mProcessName += ++sProcNameSeq;
+ }
aInfo.processName = mProcessName;
aInfo.packageName = mComponent.getPackageName();
aInfo.name = mComponent.getClassName();
@@ -1173,16 +1195,11 @@ class WindowTestsBase extends SystemServiceTestsBase {
if (mWpc != null) {
wpc = mWpc;
} else {
- wpc = new WindowProcessController(mService,
- aInfo.applicationInfo, mProcessName, mUid,
- UserHandle.getUserId(mUid), mock(Object.class),
- mock(WindowProcessListener.class));
- wpc.setThread(mock(IApplicationThread.class));
+ final WindowProcessController p = mService.getProcessController(mProcessName, mUid);
+ wpc = p != null ? p : SystemServicesTestRule.addProcess(
+ mService, aInfo.applicationInfo, mProcessName, 0 /* pid */);
}
- wpc.setThread(mock(IApplicationThread.class));
activity.setProcess(wpc);
- doReturn(wpc).when(mService).getProcessController(
- activity.processName, activity.info.applicationInfo.uid);
// Resume top activities to make sure all other signals in the system are connected.
mService.mRootWindowContainer.resumeFocusedTasksTopActivities();
@@ -1550,7 +1567,7 @@ class WindowTestsBase extends SystemServiceTestsBase {
mSecondary = mService.mTaskOrganizerController.createRootTask(
display, WINDOWING_MODE_MULTI_WINDOW, null);
- mPrimary.setAdjacentTaskFragment(mSecondary, true);
+ mPrimary.setAdjacentTaskFragment(mSecondary);
display.getDefaultTaskDisplayArea().setLaunchAdjacentFlagRootTask(mSecondary);
final Rect primaryBounds = new Rect();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
index 2df1d23c0497..77fca451547d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ZOrderingTests.java
@@ -328,7 +328,6 @@ public class ZOrderingTests extends WindowTestsBase {
assertWindowHigher(mImeWindow, imeSystemOverlayTarget);
assertWindowHigher(mImeWindow, mChildAppWindowAbove);
assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mImeWindow, mDockedDividerWindow);
// The IME has a higher base layer than the status bar so we may expect it to go
// above the status bar once they are both in the Non-App layer, as past versions of this
@@ -349,7 +348,6 @@ public class ZOrderingTests extends WindowTestsBase {
assertWindowHigher(mImeWindow, mChildAppWindowAbove);
assertWindowHigher(mImeWindow, mAppWindow);
- assertWindowHigher(mImeWindow, mDockedDividerWindow);
assertWindowHigher(mImeWindow, mStatusBarWindow);
// And, IME dialogs should always have an higher layer than the IME.
@@ -489,77 +487,6 @@ public class ZOrderingTests extends WindowTestsBase {
}
@Test
- public void testDockedDividerPosition() {
- final Task pinnedTask =
- createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
- final WindowState pinnedWindow =
- createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
-
- final Task belowTask =
- createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowState belowTaskWindow =
- createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
-
- final Task splitScreenTask1 =
- createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
- final WindowState splitWindow1 =
- createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
- final Task splitScreenTask2 =
- createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
- final WindowState splitWindow2 =
- createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
- splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
- splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
-
- final Task aboveTask =
- createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowState aboveTaskWindow =
- createAppWindow(aboveTask, ACTIVITY_TYPE_STANDARD, "aboveTaskWindow");
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- assertWindowHigher(splitWindow1, belowTaskWindow);
- assertWindowHigher(splitWindow2, belowTaskWindow);
- assertWindowHigher(mDockedDividerWindow, splitWindow1);
- assertWindowHigher(mDockedDividerWindow, splitWindow2);
- assertWindowHigher(aboveTaskWindow, mDockedDividerWindow);
- assertWindowHigher(pinnedWindow, aboveTaskWindow);
- }
-
-
- @Test
- public void testDockedDividerPosition_noAboveTask() {
- final Task pinnedTask =
- createTask(mDisplayContent, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
- final WindowState pinnedWindow =
- createAppWindow(pinnedTask, ACTIVITY_TYPE_STANDARD, "pinnedWindow");
-
- final Task belowTask =
- createTask(mDisplayContent, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
- final WindowState belowTaskWindow =
- createAppWindow(belowTask, ACTIVITY_TYPE_STANDARD, "belowTaskWindow");
-
- final Task splitScreenTask1 =
- createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
- final WindowState splitWindow1 =
- createAppWindow(splitScreenTask1, ACTIVITY_TYPE_STANDARD, "splitWindow1");
- final Task splitScreenTask2 =
- createTask(mDisplayContent, WINDOWING_MODE_MULTI_WINDOW, ACTIVITY_TYPE_STANDARD);
- final WindowState splitWindow2 =
- createAppWindow(splitScreenTask2, ACTIVITY_TYPE_STANDARD, "splitWindow2");
- splitScreenTask1.setAdjacentTaskFragment(splitScreenTask2, true /* moveTogether */);
- splitScreenTask2.setAdjacentTaskFragment(splitScreenTask1, true /* moveTogether */);
-
- mDisplayContent.assignChildLayers(mTransaction);
-
- assertWindowHigher(splitWindow1, belowTaskWindow);
- assertWindowHigher(splitWindow2, belowTaskWindow);
- assertWindowHigher(mDockedDividerWindow, splitWindow1);
- assertWindowHigher(mDockedDividerWindow, splitWindow2);
- assertWindowHigher(pinnedWindow, mDockedDividerWindow);
- }
-
- @Test
public void testAttachNavBarWhenEnteringRecents_expectNavBarHigherThanIme() {
// create RecentsAnimationController
IRecentsAnimationRunner mockRunner = mock(IRecentsAnimationRunner.class);
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index cc33f88d396d..26a1e9d8af11 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -16,6 +16,7 @@
package com.android.server.usage;
+import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.usage.TimeSparseArray;
import android.app.usage.UsageEvents;
@@ -24,6 +25,7 @@ import android.app.usage.UsageStatsManager;
import android.os.Build;
import android.os.SystemProperties;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.AtomicFile;
import android.util.Slog;
import android.util.SparseArray;
@@ -55,8 +57,11 @@ import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* Provides an interface to query for UsageStat data from a Protocol Buffer database.
@@ -1252,6 +1257,10 @@ public class UsageStatsDatabase {
Slog.wtf(TAG, "Attempting to backup UsageStats as XML with version " + version);
return null;
}
+ if (version < 1 || version > BACKUP_VERSION) {
+ Slog.wtf(TAG, "Attempting to backup UsageStats with an unknown version: " + version);
+ return null;
+ }
synchronized (mLock) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
if (KEY_USAGE_STATS.equals(key)) {
@@ -1300,14 +1309,26 @@ public class UsageStatsDatabase {
}
return baos.toByteArray();
}
+ }
+ /**
+ * Updates the set of packages given to only include those that have been used within the
+ * given timeframe (as defined by {@link UsageStats#getLastTimePackageUsed()}).
+ */
+ private void calculatePackagesUsedWithinTimeframe(
+ IntervalStats stats, Set<String> packagesList, long timeframeMs) {
+ for (UsageStats stat : stats.packageStats.values()) {
+ if (stat.getLastTimePackageUsed() > timeframeMs) {
+ packagesList.add(stat.mPackageName);
+ }
+ }
}
/**
* @hide
*/
@VisibleForTesting
- public void applyRestoredPayload(String key, byte[] payload) {
+ public @NonNull Set<String> applyRestoredPayload(String key, byte[] payload) {
synchronized (mLock) {
if (KEY_USAGE_STATS.equals(key)) {
// Read stats files for the current device configs
@@ -1320,12 +1341,15 @@ public class UsageStatsDatabase {
IntervalStats yearlyConfigSource =
getLatestUsageStats(UsageStatsManager.INTERVAL_YEARLY);
+ final Set<String> packagesRestored = new ArraySet<>();
try {
DataInputStream in = new DataInputStream(new ByteArrayInputStream(payload));
int backupDataVersion = in.readInt();
// Can't handle this backup set
- if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) return;
+ if (backupDataVersion < 1 || backupDataVersion > BACKUP_VERSION) {
+ return packagesRestored;
+ }
// Delete all stats files
// Do this after reading version and before actually restoring
@@ -1333,10 +1357,14 @@ public class UsageStatsDatabase {
deleteDirectoryContents(mIntervalDirs[i]);
}
+ // 90 days before today in epoch
+ final long timeframe = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(90);
int fileCount = in.readInt();
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
+ packagesRestored.addAll(stats.packageStats.keySet());
stats = mergeStats(stats, dailyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_DAILY, stats);
}
@@ -1345,6 +1373,7 @@ public class UsageStatsDatabase {
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
stats = mergeStats(stats, weeklyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_WEEKLY, stats);
}
@@ -1353,6 +1382,7 @@ public class UsageStatsDatabase {
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
stats = mergeStats(stats, monthlyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_MONTHLY, stats);
}
@@ -1361,6 +1391,7 @@ public class UsageStatsDatabase {
for (int i = 0; i < fileCount; i++) {
IntervalStats stats = deserializeIntervalStats(getIntervalStatsBytes(in),
backupDataVersion);
+ calculatePackagesUsedWithinTimeframe(stats, packagesRestored, timeframe);
stats = mergeStats(stats, yearlyConfigSource);
putUsageStats(UsageStatsManager.INTERVAL_YEARLY, stats);
}
@@ -1370,7 +1401,9 @@ public class UsageStatsDatabase {
} finally {
indexFilesLocked();
}
+ return packagesRestored;
}
+ return Collections.EMPTY_SET;
}
}
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 3d4610f9e330..4d4d1ff95b41 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -3047,7 +3047,8 @@ public class UsageStatsService extends SystemService implements
if (userStats == null) {
return; // user was stopped or removed
}
- userStats.applyRestoredPayload(key, payload);
+ final Set<String> restoredApps = userStats.applyRestoredPayload(key, payload);
+ mAppStandby.restoreAppsToRare(restoredApps, user);
}
}
}
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index c609add0b5d7..34c6c1623eec 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -63,6 +63,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import java.util.Set;
/**
* A per-user UsageStatsService. All methods are meant to be called with the main lock held
@@ -1374,8 +1375,8 @@ class UserUsageStatsService {
return mDatabase.getBackupPayload(key);
}
- void applyRestoredPayload(String key, byte[] payload){
+ Set<String> applyRestoredPayload(String key, byte[] payload) {
checkAndGetTimeLocked();
- mDatabase.applyRestoredPayload(key, payload);
+ return mDatabase.applyRestoredPayload(key, payload);
}
}
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
index 42d446d058b4..25db81fa2667 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/HotwordDetectionConnection.java
@@ -31,11 +31,18 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERV
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_INIT_RESULT_REPORTED__RESULT__CALLBACK_INIT_STATE_UNKNOWN_TIMEOUT;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__AUDIO_SERVICE_DIED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTION_SERVICE_RESTARTED__REASON__SCHEDULE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__CALLBACK_UPDATE_STATE_AFTER_TIMEOUT;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_BIND_SERVICE_FAIL;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__REQUEST_UPDATE_STATE;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION;
+import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__NORMAL_DETECTOR;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__DETECTOR_TYPE__TRUSTED_DETECTOR_DSP;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED;
@@ -46,11 +53,9 @@ import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPH
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED_FROM_RESTART;
import static com.android.internal.util.FrameworkStatsLog.HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
-import static com.android.server.voiceinteraction.SoundTriggerSessionPermissionsDecorator.enforcePermissionForPreflight;
import android.annotation.NonNull;
import android.annotation.Nullable;
-import android.app.AppOpsManager;
import android.content.ComponentName;
import android.content.ContentCaptureOptions;
import android.content.Context;
@@ -148,6 +153,13 @@ final class HotwordDetectionConnection {
private static final int METRICS_KEYPHRASE_TRIGGERED_REJECT_UNEXPECTED_CALLBACK =
HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECT_UNEXPECTED_CALLBACK;
+ private static final int METRICS_EXTERNAL_SOURCE_DETECTED =
+ HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECTED;
+ private static final int METRICS_EXTERNAL_SOURCE_REJECTED =
+ HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_REJECTED;
+ private static final int METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION =
+ HOTWORD_DETECTOR_EVENTS__EVENT__EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION;
+
private final Executor mAudioCopyExecutor = Executors.newCachedThreadPool();
// TODO: This may need to be a Handler(looper)
private final ScheduledExecutorService mScheduledExecutorService =
@@ -384,6 +396,10 @@ final class HotwordDetectionConnection {
}
void updateStateLocked(PersistableBundle options, SharedMemory sharedMemory) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__APP_REQUEST_UPDATE_STATE,
+ mVoiceInteractionServiceUid);
+
// Prevent doing the init late, so restart is handled equally to a clean process start.
// TODO(b/191742511): this logic needs a test
if (!mUpdateStateAfterStartFinished.get()
@@ -424,14 +440,23 @@ final class HotwordDetectionConnection {
Slog.d(TAG, "onDetected");
}
synchronized (mLock) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__DETECTED);
if (!mPerformingSoftwareHotwordDetection) {
Slog.i(TAG, "Hotword detection has already completed");
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_UNEXPECTED_CALLBACK);
return;
}
mPerformingSoftwareHotwordDetection = false;
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ METRICS_KEYPHRASE_TRIGGERED_DETECT_SECURITY_EXCEPTION);
mSoftwareCallback.onError();
return;
}
@@ -451,6 +476,9 @@ final class HotwordDetectionConnection {
if (DEBUG) {
Slog.wtf(TAG, "onRejected");
}
+ HotwordMetricsLogger.writeKeyphraseTriggerEvent(
+ mDetectorType,
+ HOTWORD_DETECTOR_KEYPHRASE_TRIGGERED__RESULT__REJECTED);
// onRejected isn't allowed here, and we are not expecting it.
}
};
@@ -462,6 +490,9 @@ final class HotwordDetectionConnection {
null,
null,
internalCallback));
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__START_SOFTWARE_DETECTION,
+ mVoiceInteractionServiceUid);
}
public void startListeningFromExternalSource(
@@ -893,6 +924,9 @@ final class HotwordDetectionConnection {
@Override
public void onRejected(HotwordRejectedResult result)
throws RemoteException {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_EXTERNAL_SOURCE_REJECTED,
+ mVoiceInteractionServiceUid);
mScheduledExecutorService.schedule(
() -> {
bestEffortClose(serviceAudioSink, audioSource);
@@ -914,6 +948,9 @@ final class HotwordDetectionConnection {
@Override
public void onDetected(HotwordDetectedResult triggerResult)
throws RemoteException {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_EXTERNAL_SOURCE_DETECTED,
+ mVoiceInteractionServiceUid);
mScheduledExecutorService.schedule(
() -> {
bestEffortClose(serviceAudioSink, audioSource);
@@ -924,6 +961,9 @@ final class HotwordDetectionConnection {
try {
enforcePermissionsForDataDelivery();
} catch (SecurityException e) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ METRICS_EXTERNAL_SOURCE_DETECT_SECURITY_EXCEPTION,
+ mVoiceInteractionServiceUid);
callback.onError();
return;
}
@@ -944,6 +984,9 @@ final class HotwordDetectionConnection {
// A copy of this has been created and passed to the hotword validator
bestEffortClose(serviceAudioSource);
});
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__START_EXTERNAL_SOURCE_DETECTION,
+ mVoiceInteractionServiceUid);
}
private class ServiceConnectionFactory {
@@ -1004,7 +1047,12 @@ final class HotwordDetectionConnection {
return;
}
mIsBound = connected;
- if (connected && !mIsLoggedFirstConnect) {
+
+ if (!connected) {
+ HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
+ HOTWORD_DETECTOR_EVENTS__EVENT__ON_DISCONNECTED,
+ mVoiceInteractionServiceUid);
+ } else if (!mIsLoggedFirstConnect) {
mIsLoggedFirstConnect = true;
HotwordMetricsLogger.writeDetectorEvent(mDetectorType,
HOTWORD_DETECTOR_EVENTS__EVENT__ON_CONNECTED,
@@ -1162,11 +1210,12 @@ final class HotwordDetectionConnection {
// TODO: Share this code with SoundTriggerMiddlewarePermission.
private void enforcePermissionsForDataDelivery() {
Binder.withCleanCallingIdentity(() -> {
- enforcePermissionForPreflight(mContext, mVoiceInteractorIdentity, RECORD_AUDIO);
- int hotwordOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_RECORD_AUDIO_HOTWORD);
- mContext.getSystemService(AppOpsManager.class).noteOpNoThrow(hotwordOp,
- mVoiceInteractorIdentity.uid, mVoiceInteractorIdentity.packageName,
- mVoiceInteractorIdentity.attributionTag, OP_MESSAGE);
+ // Hack to make sure we show the mic privacy-indicator since the Trusted Hotword
+ // requirement isn't being enforced for now. Normally, we would note the HOTWORD op here
+ // instead.
+ enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
+ RECORD_AUDIO, OP_MESSAGE);
+
enforcePermissionForDataDelivery(mContext, mVoiceInteractorIdentity,
CAPTURE_AUDIO_HOTWORD, OP_MESSAGE);
});
diff --git a/telephony/common/com/android/internal/telephony/SmsApplication.java b/telephony/common/com/android/internal/telephony/SmsApplication.java
index 4924a82c385f..423022599de6 100644
--- a/telephony/common/com/android/internal/telephony/SmsApplication.java
+++ b/telephony/common/com/android/internal/telephony/SmsApplication.java
@@ -1146,4 +1146,35 @@ public final class SmsApplication {
}
return null;
}
+
+ /**
+ * Check if a package is default mms app (or equivalent, like bluetooth)
+ *
+ * @param context context from the calling app
+ * @param packageName the name of the package to be checked
+ * @return true if the package is default mms app or bluetooth
+ */
+ @UnsupportedAppUsage
+ public static boolean isDefaultMmsApplication(Context context, String packageName) {
+ if (packageName == null) {
+ return false;
+ }
+ String defaultMmsPackage = getDefaultMmsApplicationPackageName(context);
+ String bluetoothPackageName = context.getResources()
+ .getString(com.android.internal.R.string.config_systemBluetoothStack);
+
+ if ((defaultMmsPackage != null && defaultMmsPackage.equals(packageName))
+ || bluetoothPackageName.equals(packageName)) {
+ return true;
+ }
+ return false;
+ }
+
+ private static String getDefaultMmsApplicationPackageName(Context context) {
+ ComponentName component = getDefaultMmsApplication(context, false);
+ if (component != null) {
+ return component.getPackageName();
+ }
+ return null;
+ }
}
diff --git a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
index 24dfbd028d03..a004cc3a1642 100644
--- a/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
+++ b/telephony/java/android/telephony/PhoneNumberFormattingTextWatcher.java
@@ -21,6 +21,7 @@ import android.os.Build;
import android.text.Editable;
import android.text.Selection;
import android.text.TextWatcher;
+import android.text.style.TtsSpan;
import com.android.i18n.phonenumbers.AsYouTypeFormatter;
import com.android.i18n.phonenumbers.PhoneNumberUtil;
@@ -119,6 +120,13 @@ public class PhoneNumberFormattingTextWatcher implements TextWatcher {
}
mSelfChange = false;
}
+
+ //remove previous TTS spans
+ TtsSpan[] ttsSpans = s.getSpans(0, s.length(), TtsSpan.class);
+ for (TtsSpan ttsSpan : ttsSpans) {
+ s.removeSpan(ttsSpan);
+ }
+
PhoneNumberUtils.ttsSpanAsPhoneNumber(s, 0, s.length());
}
diff --git a/telephony/java/android/telephony/UiccSlotInfo.java b/telephony/java/android/telephony/UiccSlotInfo.java
index 06c5b5cfeda1..5e02532e85a8 100644
--- a/telephony/java/android/telephony/UiccSlotInfo.java
+++ b/telephony/java/android/telephony/UiccSlotInfo.java
@@ -129,7 +129,7 @@ public class UiccSlotInfo implements Parcelable {
this.mLogicalSlotIdx = logicalSlotIdx;
this.mIsExtendedApduSupported = isExtendedApduSupported;
this.mIsRemovable = false;
- this.mPortList = null;
+ this.mPortList = new ArrayList<UiccPortInfo>();
}
/**
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 081a74a58342..3ed87e1b9fe2 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -2190,11 +2190,10 @@ public class ApnSetting implements Parcelable {
}
if ((mApnTypeBitmask & TYPE_MMS) != 0 && !TextUtils.isEmpty(mMmsProxyAddress)
&& mMmsProxyAddress.startsWith("http")) {
- if (Build.IS_DEBUGGABLE) {
- throw new IllegalArgumentException("mms proxy(" + mMmsProxyAddress
- + ") should be a hostname, not a url");
- }
- return null;
+ Log.wtf(LOG_TAG,"mms proxy(" + mMmsProxyAddress
+ + ") should be a hostname, not a url");
+ Uri mMmsProxyAddressUri = Uri.parse(mMmsProxyAddress);
+ mMmsProxyAddress = mMmsProxyAddressUri.getHost();
}
return new ApnSetting(this);
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
index 7f309e1974e1..315c40ffa9ba 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/CommonAssertions.kt
@@ -18,10 +18,13 @@
package com.android.server.wm.flicker
import com.android.server.wm.flicker.helpers.WindowUtils
+import com.android.server.wm.flicker.traces.region.RegionSubject
import com.android.server.wm.traces.common.FlickerComponentName
-val LAUNCHER_COMPONENT = FlickerComponentName("com.google.android.apps.nexuslauncher",
- "com.google.android.apps.nexuslauncher.NexusLauncherActivity")
+val LAUNCHER_COMPONENT = FlickerComponentName(
+ "com.google.android.apps.nexuslauncher",
+ "com.google.android.apps.nexuslauncher.NexusLauncherActivity"
+)
/**
* Checks that [FlickerComponentName.STATUS_BAR] window is visible and above the app windows in
@@ -109,9 +112,9 @@ fun FlickerTestParameter.statusBarLayerIsVisible() {
fun FlickerTestParameter.navBarLayerPositionStart() {
assertLayersStart {
val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ ?: throw RuntimeException("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
}
@@ -122,9 +125,9 @@ fun FlickerTestParameter.navBarLayerPositionStart() {
fun FlickerTestParameter.navBarLayerPositionEnd() {
assertLayersEnd {
val display = this.entry.displays.minByOrNull { it.id }
- ?: throw RuntimeException("There is no display!")
+ ?: throw RuntimeException("There is no display!")
this.visibleRegion(FlickerComponentName.NAV_BAR)
- .coversExactly(WindowUtils.getNavigationBarPosition(display))
+ .coversExactly(WindowUtils.getNavigationBarPosition(display, isGesturalNavigation))
}
}
@@ -173,6 +176,33 @@ fun FlickerTestParameter.statusBarLayerRotatesScales() {
}
/**
+ * Asserts that the visibleRegion of the [FlickerComponentName.SNAPSHOT] layer can cover
+ * the visibleRegion of the given app component exactly
+ */
+fun FlickerTestParameter.snapshotStartingWindowLayerCoversExactlyOnApp(
+ component: FlickerComponentName) {
+ assertLayers {
+ invoke("snapshotStartingWindowLayerCoversExactlyOnApp") {
+ val snapshotLayers = it.subjects.filter { subject ->
+ subject.name.contains(
+ FlickerComponentName.SNAPSHOT.toLayerName()) && subject.isVisible
+ }
+ // Verify the size of snapshotRegion covers appVisibleRegion exactly in animation.
+ if (snapshotLayers.isNotEmpty()) {
+ val visibleAreas = snapshotLayers.mapNotNull { snapshotLayer ->
+ snapshotLayer.layer?.visibleRegion
+ }.toTypedArray()
+ val snapshotRegion = RegionSubject.assertThat(visibleAreas, this, timestamp)
+ val appVisibleRegion = it.visibleRegion(component)
+ if (snapshotRegion.region.isNotEmpty) {
+ snapshotRegion.coversExactly(appVisibleRegion.region)
+ }
+ }
+ }
+ }
+}
+
+/**
* Asserts that:
* [originalLayer] is visible at the start of the trace
* [originalLayer] becomes invisible during the trace and (in the same entry) [newLayer]
@@ -216,11 +246,11 @@ fun FlickerTestParameter.replacesLayer(
assertLayersStart {
this.isVisible(originalLayer)
- .isInvisible(newLayer)
+ .isInvisible(newLayer)
}
assertLayersEnd {
this.isInvisible(originalLayer)
- .isVisible(newLayer)
+ .isVisible(newLayer)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
index 6257484be9bd..2e29b3e314ca 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/CloseImeEditorPopupDialogTest.kt
@@ -59,7 +59,7 @@ class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter)
}
transitions {
imeTestApp.dismissDialog(wmHelper)
- instrumentation.uiAutomation.syncInputTransactions()
+ wmHelper.waitImeGone()
}
teardown {
eachRun {
@@ -91,7 +91,7 @@ class CloseImeEditorPopupDialogTest(private val testSpec: FlickerTestParameter)
.then()
.isVisible(FlickerComponentName.IME_SNAPSHOT)
.then()
- .isInvisible(FlickerComponentName.IME_SNAPSHOT)
+ .isInvisible(FlickerComponentName.IME_SNAPSHOT, isOptional = true)
.isInvisible(FlickerComponentName.IME)
}
}
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
index 78aea1f1fb17..88fb1a220910 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/OpenImeWindowFromFixedOrientationAppTest.kt
@@ -17,23 +17,25 @@
package com.android.server.wm.flicker.ime
import android.app.Instrumentation
+import android.platform.test.annotations.Postsubmit
import android.platform.test.annotations.Presubmit
import android.platform.test.annotations.RequiresDevice
import android.view.Surface
import android.view.WindowManagerPolicyConstants
import androidx.test.filters.FlakyTest
import androidx.test.platform.app.InstrumentationRegistry
+import com.android.launcher3.tapl.LauncherInstrumentation
import com.android.server.wm.flicker.FlickerBuilderProvider
import com.android.server.wm.flicker.FlickerParametersRunnerFactory
import com.android.server.wm.flicker.FlickerTestParameter
import com.android.server.wm.flicker.FlickerTestParameterFactory
import com.android.server.wm.flicker.annotation.Group2
import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper
import com.android.server.wm.flicker.helpers.setRotation
-import com.android.server.wm.flicker.navBarLayerRotatesAndScales
+import com.android.server.wm.flicker.navBarLayerPositionEnd
import com.android.server.wm.flicker.navBarWindowIsVisible
+import com.android.server.wm.flicker.snapshotStartingWindowLayerCoversExactlyOnApp
import com.android.server.wm.flicker.statusBarLayerRotatesScales
import com.android.server.wm.flicker.statusBarWindowIsVisible
import org.junit.FixMethodOrder
@@ -43,7 +45,8 @@ import org.junit.runners.MethodSorters
import org.junit.runners.Parameterized
/**
- * Test IME window layer will become visible when switching from the fixed orientation activity.
+ * Test IME window layer will become visible when switching from the fixed orientation activity
+ * (e.g. Launcher activity).
* To run this test: `atest FlickerTests:OpenImeWindowFromFixedOrientationAppTest`
*/
@RequiresDevice
@@ -53,24 +56,31 @@ import org.junit.runners.Parameterized
@Group2
class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTestParameter) {
private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
- private val fixedOrientationApp = FixedOrientationAppHelper(instrumentation)
private val imeTestApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation)
+ private val taplInstrumentation = LauncherInstrumentation()
@FlickerBuilderProvider
fun buildFlicker(): FlickerBuilder {
return FlickerBuilder(instrumentation).apply {
setup {
+ test {
+ // Launch the activity with expecting IME will be shown.
+ imeTestApp.launchViaIntent(wmHelper)
+ }
eachRun {
- fixedOrientationApp.launchViaIntent(wmHelper)
- this.setRotation(Surface.ROTATION_90)
+ // Swiping out the IME activity to home.
+ taplInstrumentation.goHome()
+ wmHelper.waitForHomeActivityVisible()
}
}
transitions {
+ // Bring the exist IME activity to the front in landscape mode device rotation.
+ setRotation(Surface.ROTATION_90)
imeTestApp.launchViaIntent(wmHelper)
}
teardown {
test {
- fixedOrientationApp.exit(wmHelper)
+ imeTestApp.exit(wmHelper)
}
}
}
@@ -90,7 +100,7 @@ class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTest
@Presubmit
@Test
- fun navBarLayerRotatesAndScales() = testSpec.navBarLayerRotatesAndScales()
+ fun navBarLayerRotatesAndScales() = testSpec.navBarLayerPositionEnd()
@FlakyTest(bugId = 206753786)
fun statusBarLayerRotatesScales() = testSpec.statusBarLayerRotatesScales()
@@ -99,6 +109,12 @@ class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTest
@Test
fun imeLayerBecomesVisible() = testSpec.imeLayerBecomesVisible()
+ @Postsubmit
+ @Test
+ fun snapshotStartingWindowLayerCoversExactlyOnApp() {
+ testSpec.snapshotStartingWindowLayerCoversExactlyOnApp(imeTestApp.component)
+ }
+
companion object {
/**
* Creates the test configurations.
@@ -112,7 +128,7 @@ class OpenImeWindowFromFixedOrientationAppTest(private val testSpec: FlickerTest
return FlickerTestParameterFactory.getInstance()
.getConfigNonRotationTests(
repetitions = 3,
- supportedRotations = listOf(Surface.ROTATION_0),
+ supportedRotations = listOf(Surface.ROTATION_90),
supportedNavigationModes = listOf(
WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY
)
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 43aa4b151548..3e2130dc480f 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -45,6 +45,7 @@
android:theme="@style/CutoutShortEdges"
android:taskAffinity="com.android.server.wm.flicker.testapp.ImeActivityAutoFocus"
android:windowSoftInputMode="stateVisible"
+ android:configChanges="orientation|screenSize"
android:label="ImeAppAutoFocus"
android:exported="true">
<intent-filter>
diff --git a/tests/WindowInsetsTests/res/layout/controller_activity.xml b/tests/WindowInsetsTests/res/layout/controller_activity.xml
index d51a4ddd43e8..5550eab61a33 100644
--- a/tests/WindowInsetsTests/res/layout/controller_activity.xml
+++ b/tests/WindowInsetsTests/res/layout/controller_activity.xml
@@ -88,7 +88,7 @@
<TextView
android:id="@+id/textViewControllableInsets"
- android:layout_width="wrap_content"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp" />
diff --git a/tests/WindowInsetsTests/res/values/strings.xml b/tests/WindowInsetsTests/res/values/strings.xml
index d6355f5a0464..516d4584426e 100644
--- a/tests/WindowInsetsTests/res/values/strings.xml
+++ b/tests/WindowInsetsTests/res/values/strings.xml
@@ -22,7 +22,7 @@
<!-- The item positions should match the flag values respectively. -->
<string-array name="behaviors">
- <item>BEHAVIOR_SHOW_BARS_BY_TOUCH</item>
+ <item>BEHAVIOR_SHOW_BARS_BY_TOUCH (deprecated)</item>
<item>BEHAVIOR_DEFAULT</item>
<item>BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE</item>
</string-array>
diff --git a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
index 95fd959e5587..e6b60cfbe84f 100644
--- a/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
+++ b/tests/WindowInsetsTests/src/com/google/android/test/windowinsetstests/ControllerActivity.java
@@ -83,7 +83,51 @@ public class ControllerActivity extends Activity implements View.OnApplyWindowIn
final View contentView = findViewById(R.id.content);
contentView.setOnApplyWindowInsetsListener(this);
contentView.getWindowInsetsController().addOnControllableInsetsChangedListener(
- (c, types) -> mTextControllableInsets.setText("ControllableInsetsTypes=" + types));
+ (c, types) -> mTextControllableInsets.setText(
+ "ControllableInsetsTypes:\n" + insetsTypesToString(types)));
+ }
+
+ private static String insetsTypesToString(int types) {
+ if (types == 0) {
+ return "none";
+ }
+ final StringBuilder sb = new StringBuilder();
+ if ((types & Type.statusBars()) != 0) {
+ types &= ~Type.statusBars();
+ sb.append("statusBars ");
+ }
+ if ((types & Type.navigationBars()) != 0) {
+ types &= ~Type.navigationBars();
+ sb.append("navigationBars ");
+ }
+ if ((types & Type.captionBar()) != 0) {
+ types &= ~Type.captionBar();
+ sb.append("captionBar ");
+ }
+ if ((types & Type.ime()) != 0) {
+ types &= ~Type.ime();
+ sb.append("ime ");
+ }
+ if ((types & Type.systemGestures()) != 0) {
+ types &= ~Type.systemGestures();
+ sb.append("systemGestures ");
+ }
+ if ((types & Type.mandatorySystemGestures()) != 0) {
+ types &= ~Type.mandatorySystemGestures();
+ sb.append("mandatorySystemGestures ");
+ }
+ if ((types & Type.tappableElement()) != 0) {
+ types &= ~Type.tappableElement();
+ sb.append("tappableElement ");
+ }
+ if ((types & Type.displayCutout()) != 0) {
+ types &= ~Type.displayCutout();
+ sb.append("displayCutout ");
+ }
+ if (types != 0) {
+ sb.append("unknownTypes:").append(types);
+ }
+ return sb.toString();
}
@Override
diff --git a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
index 1fe13fe97fbe..06cbeb5368a5 100644
--- a/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
+++ b/tests/permission/src/com/android/framework/permission/tests/WindowManagerPermissionTests.java
@@ -124,16 +124,6 @@ public class WindowManagerPermissionTests extends TestCase {
@SmallTest
public void testSET_ORIENTATION() {
try {
- mWm.updateRotation(true, false);
- fail("IWindowManager.updateRotation did not throw SecurityException as"
- + " expected");
- } catch (SecurityException e) {
- // expected
- } catch (RemoteException e) {
- fail("Unexpected remote exception");
- }
-
- try {
mWm.freezeRotation(-1);
fail("IWindowManager.freezeRotation did not throw SecurityException as"
+ " expected");